diff --git a/client/fingerprint/network.go b/client/fingerprint/network.go index bfc59c7e8307..beeca77b9f64 100644 --- a/client/fingerprint/network.go +++ b/client/fingerprint/network.go @@ -13,6 +13,12 @@ const ( // defaultNetworkSpeed is the speed set if the network link speed could not // be detected. defaultNetworkSpeed = 1000 + + // networkDisallowLinkLocalOption/Default are used to allow the operator to + // decide how the fingerprinter handles an interface that only contains link + // local addresses. + networkDisallowLinkLocalOption = "fingerprint.network.disallow_link_local" + networkDisallowLinkLocalDefault = false ) // NetworkFingerprint is used to fingerprint the Network capabilities of a node @@ -84,7 +90,8 @@ func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) } // Create the network resources from the interface - nwResources, err := f.createNetworkResources(mbits, intf) + disallowLinkLocal := cfg.ReadBoolDefault(networkDisallowLinkLocalOption, networkDisallowLinkLocalDefault) + nwResources, err := f.createNetworkResources(mbits, intf, disallowLinkLocal) if err != nil { return false, err } @@ -105,13 +112,16 @@ func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) } // createNetworkResources creates network resources for every IP -func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.Interface) ([]*structs.NetworkResource, error) { +func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.Interface, disallowLinkLocal bool) ([]*structs.NetworkResource, error) { // Find the interface with the name addrs, err := f.interfaceDetector.Addrs(intf) if err != nil { return nil, err } + nwResources := make([]*structs.NetworkResource, 0) + linkLocals := make([]*structs.NetworkResource, 0) + for _, addr := range addrs { // Create a new network resource newNetwork := &structs.NetworkResource{ @@ -128,10 +138,6 @@ func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.In ip = v.IP } - // If the ip is link-local then we ignore it - if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { - continue - } newNetwork.IP = ip.String() if ip.To4() != nil { newNetwork.CIDR = newNetwork.IP + "/32" @@ -139,8 +145,25 @@ func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.In newNetwork.CIDR = newNetwork.IP + "/128" } + // If the ip is link-local then we ignore it unless the user allows it + // and we detect nothing else + if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { + linkLocals = append(linkLocals, newNetwork) + continue + } + nwResources = append(nwResources, newNetwork) } + + if len(nwResources) == 0 && len(linkLocals) != 0 { + if disallowLinkLocal { + f.logger.Printf("[DEBUG] fingerprint.network: ignoring detected link-local address on interface %v", intf.Name) + return nwResources, nil + } + + return linkLocals, nil + } + return nwResources, nil } diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index 62124a0f5ec9..cb17d9e7eb6c 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -46,6 +46,24 @@ var ( 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 @@ -109,6 +127,10 @@ func (n *NetworkInterfaceDetectorMultipleInterfaces) InterfaceByName(name string intf = ð1 case "eth2": intf = ð2 + case "eth3": + intf = ð3 + case "eth4": + intf = ð4 } if intf != nil { return intf, nil @@ -140,6 +162,18 @@ func (n *NetworkInterfaceDetectorMultipleInterfaces) Addrs(intf *net.Interface) 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) } @@ -321,3 +355,114 @@ func TestNetworkFingerPrint_excludelo_down_interfaces(t *testing.T) { 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") + } +} diff --git a/website/source/docs/agent/configuration/client.html.md b/website/source/docs/agent/configuration/client.html.md index d06c05b81c26..feda9a1002c5 100644 --- a/website/source/docs/agent/configuration/client.html.md +++ b/website/source/docs/agent/configuration/client.html.md @@ -92,7 +92,7 @@ client { "client", like `"/opt/nomad/client"`. This must be an absolute path. - `gc_interval` `(string: "1m")` - Specifies the interval at which Nomad - attempts to garbage collect terminal allocation directories. + attempts to garbage collect terminal allocation directories. - `gc_disk_usage_threshold` `(float: 80)` - Specifies the disk usage percent which Nomad tries to maintain by garbage collecting terminal allocations. @@ -268,6 +268,19 @@ see the [drivers documentation](/docs/drivers/index.html). } ``` +- `"fingerprint.network.disallow_link_local"` `(string: "false")` - Specifies + whether the network fingerprinter should ignore link-local addresses in the + case that no globally routable address is found. The fingerprinter will always + prefer globally routable addresses. + + ```hcl + client { + options = { + "fingerprint.network.disallow_link_local" = "true" + } + } + ``` + ### `reserved` Parameters - `cpu` `(int: 0)` - Specifies the amount of CPU to reserve, in MHz.