fd7d614ae4
This PR changes the fingerprint handling of network interfaces that only contain link local addresses. The new behavior is to prefer globally routable addresses and if none are detected, to fall back to link local addresses if the operator hasn't disallowed it. This gives us pre 0.6 behavior for interfaces with only link local addresses but 0.6+ behavior for IPv6 interfaces that will always have a link-local address. Fixes https://github.com/hashicorp/nomad/issues/3005 /cc diptanuc
469 lines
12 KiB
Go
469 lines
12 KiB
Go
package fingerprint
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/client/config"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
// Set skipOnlineTestEnvVar to a non-empty value to skip network tests. Useful
|
|
// when working offline (e.g. an airplane).
|
|
const skipOnlineTestsEnvVar = "TEST_NOMAD_SKIP_ONLINE_NET"
|
|
|
|
var (
|
|
lo = net.Interface{
|
|
Index: 2,
|
|
MTU: 65536,
|
|
Name: "lo",
|
|
HardwareAddr: []byte{23, 43, 54, 54},
|
|
Flags: net.FlagUp | net.FlagLoopback,
|
|
}
|
|
|
|
eth0 = net.Interface{
|
|
Index: 3,
|
|
MTU: 1500,
|
|
Name: "eth0",
|
|
HardwareAddr: []byte{23, 44, 54, 67},
|
|
Flags: net.FlagUp | net.FlagMulticast | net.FlagBroadcast,
|
|
}
|
|
|
|
eth1 = net.Interface{
|
|
Index: 4,
|
|
MTU: 1500,
|
|
Name: "eth1",
|
|
HardwareAddr: []byte{23, 44, 54, 69},
|
|
Flags: net.FlagMulticast | net.FlagBroadcast,
|
|
}
|
|
|
|
eth2 = net.Interface{
|
|
Index: 4,
|
|
MTU: 1500,
|
|
Name: "eth2",
|
|
HardwareAddr: []byte{23, 44, 54, 70},
|
|
Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast,
|
|
}
|
|
|
|
// One link local address
|
|
eth3 = net.Interface{
|
|
Index: 4,
|
|
MTU: 1500,
|
|
Name: "eth3",
|
|
HardwareAddr: []byte{23, 44, 54, 71},
|
|
Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast,
|
|
}
|
|
|
|
// One link local address and one globally routable address
|
|
eth4 = net.Interface{
|
|
Index: 4,
|
|
MTU: 1500,
|
|
Name: "eth4",
|
|
HardwareAddr: []byte{23, 44, 54, 72},
|
|
Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast,
|
|
}
|
|
)
|
|
|
|
// A fake network detector which returns no devices
|
|
type NetworkIntefaceDetectorNoDevices struct {
|
|
}
|
|
|
|
func (f *NetworkIntefaceDetectorNoDevices) Interfaces() ([]net.Interface, error) {
|
|
return make([]net.Interface, 0), nil
|
|
}
|
|
|
|
func (f *NetworkIntefaceDetectorNoDevices) InterfaceByName(name string) (*net.Interface, error) {
|
|
return nil, fmt.Errorf("Device with name %s doesn't exist", name)
|
|
}
|
|
|
|
func (f *NetworkIntefaceDetectorNoDevices) Addrs(intf *net.Interface) ([]net.Addr, error) {
|
|
return nil, fmt.Errorf("No interfaces found for device %v", intf.Name)
|
|
}
|
|
|
|
// A fake network detector which returns only loopback
|
|
type NetworkInterfaceDetectorOnlyLo struct {
|
|
}
|
|
|
|
func (n *NetworkInterfaceDetectorOnlyLo) Interfaces() ([]net.Interface, error) {
|
|
return []net.Interface{lo}, nil
|
|
}
|
|
|
|
func (n *NetworkInterfaceDetectorOnlyLo) InterfaceByName(name string) (*net.Interface, error) {
|
|
if name == "lo" {
|
|
return &lo, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("No device with name %v found", name)
|
|
}
|
|
|
|
func (n *NetworkInterfaceDetectorOnlyLo) Addrs(intf *net.Interface) ([]net.Addr, error) {
|
|
if intf.Name == "lo" {
|
|
_, ipnet1, _ := net.ParseCIDR("127.0.0.1/8")
|
|
_, ipnet2, _ := net.ParseCIDR("2001:DB8::/48")
|
|
return []net.Addr{ipnet1, ipnet2}, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Can't find addresses for device: %v", intf.Name)
|
|
}
|
|
|
|
// A fake network detector which simulates the presence of multiple interfaces
|
|
type NetworkInterfaceDetectorMultipleInterfaces struct {
|
|
}
|
|
|
|
func (n *NetworkInterfaceDetectorMultipleInterfaces) Interfaces() ([]net.Interface, error) {
|
|
return []net.Interface{lo, eth0, eth1, eth2}, nil
|
|
}
|
|
|
|
func (n *NetworkInterfaceDetectorMultipleInterfaces) InterfaceByName(name string) (*net.Interface, error) {
|
|
var intf *net.Interface
|
|
switch name {
|
|
case "lo":
|
|
intf = &lo
|
|
case "eth0":
|
|
intf = ð0
|
|
case "eth1":
|
|
intf = ð1
|
|
case "eth2":
|
|
intf = ð2
|
|
case "eth3":
|
|
intf = ð3
|
|
case "eth4":
|
|
intf = ð4
|
|
}
|
|
if intf != nil {
|
|
return intf, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("No device with name %v found", name)
|
|
}
|
|
|
|
func (n *NetworkInterfaceDetectorMultipleInterfaces) Addrs(intf *net.Interface) ([]net.Addr, error) {
|
|
if intf.Name == "lo" {
|
|
_, ipnet1, _ := net.ParseCIDR("127.0.0.1/8")
|
|
_, ipnet2, _ := net.ParseCIDR("2001:DB8::/48")
|
|
return []net.Addr{ipnet1, ipnet2}, nil
|
|
}
|
|
|
|
if intf.Name == "eth0" {
|
|
_, ipnet1, _ := net.ParseCIDR("100.64.0.11/10")
|
|
_, ipnet2, _ := net.ParseCIDR("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64")
|
|
ipAddr, _ := net.ResolveIPAddr("ip6", "fe80::140c:9579:8037:f565")
|
|
return []net.Addr{ipnet1, ipnet2, ipAddr}, nil
|
|
}
|
|
|
|
if intf.Name == "eth1" {
|
|
_, ipnet1, _ := net.ParseCIDR("100.64.0.10/10")
|
|
_, ipnet2, _ := net.ParseCIDR("2003:DB8::/48")
|
|
return []net.Addr{ipnet1, ipnet2}, nil
|
|
}
|
|
|
|
if intf.Name == "eth2" {
|
|
return []net.Addr{}, nil
|
|
}
|
|
|
|
if intf.Name == "eth3" {
|
|
_, ipnet1, _ := net.ParseCIDR("169.254.155.20/32")
|
|
return []net.Addr{ipnet1}, nil
|
|
}
|
|
|
|
if intf.Name == "eth4" {
|
|
_, ipnet1, _ := net.ParseCIDR("169.254.155.20/32")
|
|
_, ipnet2, _ := net.ParseCIDR("100.64.0.10/10")
|
|
return []net.Addr{ipnet1, ipnet2}, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Can't find addresses for device: %v", intf.Name)
|
|
}
|
|
|
|
func TestNetworkFingerprint_basic(t *testing.T) {
|
|
if v := os.Getenv(skipOnlineTestsEnvVar); v != "" {
|
|
t.Skipf("Environment variable %+q not empty, skipping test", skipOnlineTestsEnvVar)
|
|
}
|
|
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &DefaultNetworkInterfaceDetector{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{NetworkSpeed: 101}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatalf("should apply (HINT: working offline? Set env %q=y", skipOnlineTestsEnvVar)
|
|
}
|
|
|
|
assertNodeAttributeContains(t, node, "unique.network.ip-address")
|
|
|
|
ip := node.Attributes["unique.network.ip-address"]
|
|
match := net.ParseIP(ip)
|
|
if match == nil {
|
|
t.Fatalf("Bad IP match: %s", ip)
|
|
}
|
|
|
|
if node.Resources == nil || len(node.Resources.Networks) == 0 {
|
|
t.Fatal("Expected to find Network Resources")
|
|
}
|
|
|
|
// Test at least the first Network Resource
|
|
net := node.Resources.Networks[0]
|
|
if net.IP == "" {
|
|
t.Fatal("Expected Network Resource to not be empty")
|
|
}
|
|
if net.CIDR == "" {
|
|
t.Fatal("Expected Network Resource to have a CIDR")
|
|
}
|
|
if net.Device == "" {
|
|
t.Fatal("Expected Network Resource to have a Device Name")
|
|
}
|
|
if net.MBits != 101 {
|
|
t.Fatalf("Expected Network Resource to have bandwidth %d; got %d", 101, net.MBits)
|
|
}
|
|
}
|
|
|
|
func TestNetworkFingerprint_no_devices(t *testing.T) {
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkIntefaceDetectorNoDevices{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{NetworkSpeed: 100}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if ok {
|
|
t.Fatalf("ok: %v", ok)
|
|
}
|
|
}
|
|
|
|
func TestNetworkFingerprint_default_device_absent(t *testing.T) {
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkInterfaceDetectorOnlyLo{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "eth0"}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err == nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if ok {
|
|
t.Fatalf("ok: %v", ok)
|
|
}
|
|
}
|
|
|
|
func TestNetworkFingerPrint_default_device(t *testing.T) {
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkInterfaceDetectorOnlyLo{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "lo"}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatalf("should apply")
|
|
}
|
|
|
|
assertNodeAttributeContains(t, node, "unique.network.ip-address")
|
|
|
|
ip := node.Attributes["unique.network.ip-address"]
|
|
match := net.ParseIP(ip)
|
|
if match == nil {
|
|
t.Fatalf("Bad IP match: %s", ip)
|
|
}
|
|
|
|
if node.Resources == nil || len(node.Resources.Networks) == 0 {
|
|
t.Fatal("Expected to find Network Resources")
|
|
}
|
|
|
|
// Test at least the first Network Resource
|
|
net := node.Resources.Networks[0]
|
|
if net.IP == "" {
|
|
t.Fatal("Expected Network Resource to not be empty")
|
|
}
|
|
if net.CIDR == "" {
|
|
t.Fatal("Expected Network Resource to have a CIDR")
|
|
}
|
|
if net.Device == "" {
|
|
t.Fatal("Expected Network Resource to have a Device Name")
|
|
}
|
|
if net.MBits == 0 {
|
|
t.Fatal("Expected Network Resource to have a non-zero bandwidth")
|
|
}
|
|
}
|
|
|
|
func TestNetworkFingerPrint_excludelo_down_interfaces(t *testing.T) {
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{NetworkSpeed: 100}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatalf("should apply")
|
|
}
|
|
|
|
assertNodeAttributeContains(t, node, "unique.network.ip-address")
|
|
|
|
ip := node.Attributes["unique.network.ip-address"]
|
|
match := net.ParseIP(ip)
|
|
if match == nil {
|
|
t.Fatalf("Bad IP match: %s", ip)
|
|
}
|
|
|
|
if node.Resources == nil || len(node.Resources.Networks) == 0 {
|
|
t.Fatal("Expected to find Network Resources")
|
|
}
|
|
|
|
// Test at least the first Network Resource
|
|
net := node.Resources.Networks[0]
|
|
if net.IP == "" {
|
|
t.Fatal("Expected Network Resource to have an IP")
|
|
}
|
|
if net.CIDR == "" {
|
|
t.Fatal("Expected Network Resource to have a CIDR")
|
|
}
|
|
if net.Device != "eth0" {
|
|
t.Fatal("Expected Network Resource to be eth0. Actual: ", net.Device)
|
|
}
|
|
if net.MBits == 0 {
|
|
t.Fatal("Expected Network Resource to have a non-zero bandwidth")
|
|
}
|
|
|
|
// Test the CIDR of the IPs
|
|
if node.Resources.Networks[0].CIDR != "100.64.0.0/32" {
|
|
t.Fatalf("bad CIDR: %v", node.Resources.Networks[0].CIDR)
|
|
}
|
|
if node.Resources.Networks[1].CIDR != "2001:db8:85a3::/128" {
|
|
t.Fatalf("bad CIDR: %v", node.Resources.Networks[1].CIDR)
|
|
}
|
|
// Ensure that the link local address isn't fingerprinted
|
|
if len(node.Resources.Networks) != 2 {
|
|
t.Fatalf("bad number of IPs %v", len(node.Resources.Networks))
|
|
}
|
|
}
|
|
|
|
func TestNetworkFingerPrint_LinkLocal_Allowed(t *testing.T) {
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "eth3"}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatalf("should apply")
|
|
}
|
|
|
|
assertNodeAttributeContains(t, node, "unique.network.ip-address")
|
|
|
|
ip := node.Attributes["unique.network.ip-address"]
|
|
match := net.ParseIP(ip)
|
|
if match == nil {
|
|
t.Fatalf("Bad IP match: %s", ip)
|
|
}
|
|
|
|
if node.Resources == nil || len(node.Resources.Networks) == 0 {
|
|
t.Fatal("Expected to find Network Resources")
|
|
}
|
|
|
|
// Test at least the first Network Resource
|
|
net := node.Resources.Networks[0]
|
|
if net.IP == "" {
|
|
t.Fatal("Expected Network Resource to not be empty")
|
|
}
|
|
if net.CIDR == "" {
|
|
t.Fatal("Expected Network Resource to have a CIDR")
|
|
}
|
|
if net.Device == "" {
|
|
t.Fatal("Expected Network Resource to have a Device Name")
|
|
}
|
|
if net.MBits == 0 {
|
|
t.Fatal("Expected Network Resource to have a non-zero bandwidth")
|
|
}
|
|
}
|
|
|
|
func TestNetworkFingerPrint_LinkLocal_Allowed_MixedIntf(t *testing.T) {
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "eth4"}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatalf("should apply")
|
|
}
|
|
|
|
assertNodeAttributeContains(t, node, "unique.network.ip-address")
|
|
|
|
ip := node.Attributes["unique.network.ip-address"]
|
|
match := net.ParseIP(ip)
|
|
if match == nil {
|
|
t.Fatalf("Bad IP match: %s", ip)
|
|
}
|
|
|
|
if node.Resources == nil || len(node.Resources.Networks) == 0 {
|
|
t.Fatal("Expected to find Network Resources")
|
|
}
|
|
|
|
// Test at least the first Network Resource
|
|
net := node.Resources.Networks[0]
|
|
if net.IP == "" {
|
|
t.Fatal("Expected Network Resource to not be empty")
|
|
}
|
|
if net.IP == "169.254.155.20" {
|
|
t.Fatalf("expected non-link local address; got %v", net.IP)
|
|
}
|
|
if net.CIDR == "" {
|
|
t.Fatal("Expected Network Resource to have a CIDR")
|
|
}
|
|
if net.Device == "" {
|
|
t.Fatal("Expected Network Resource to have a Device Name")
|
|
}
|
|
if net.MBits == 0 {
|
|
t.Fatal("Expected Network Resource to have a non-zero bandwidth")
|
|
}
|
|
}
|
|
|
|
func TestNetworkFingerPrint_LinkLocal_Disallowed(t *testing.T) {
|
|
f := &NetworkFingerprint{logger: testLogger(), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}}
|
|
node := &structs.Node{
|
|
Attributes: make(map[string]string),
|
|
}
|
|
cfg := &config.Config{
|
|
NetworkSpeed: 100,
|
|
NetworkInterface: "eth3",
|
|
Options: map[string]string{
|
|
networkDisallowLinkLocalOption: "true",
|
|
},
|
|
}
|
|
|
|
ok, err := f.Fingerprint(cfg, node)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatalf("should not apply")
|
|
}
|
|
}
|