diff --git a/CHANGELOG.md b/CHANGELOG.md index 6934aee5143..0cb429b1ead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ IMPROVEMENTS: * driver/docker: Allow setting container IP with user defined networks [GH-2535] + * client: Fingerprint all routable addresses on an interface including IPv6 + addresses [GH-2536] BUG FIXES: * server: Reject non-TLS clients when TLS enabled [GH-2525] diff --git a/api/resources.go b/api/resources.go index 14c6a4fd72b..8d3f27c6cd2 100644 --- a/api/resources.go +++ b/api/resources.go @@ -66,12 +66,12 @@ type Port struct { // NetworkResource is used to describe required network // resources of a given task. type NetworkResource struct { - Public bool + Device string CIDR string - ReservedPorts []Port - DynamicPorts []Port IP string MBits *int + ReservedPorts []Port + DynamicPorts []Port } func (n *NetworkResource) Canonicalize() { diff --git a/client/fingerprint/network.go b/client/fingerprint/network.go index eceb213e43f..bfc59c7e830 100644 --- a/client/fingerprint/network.go +++ b/client/fingerprint/network.go @@ -1,7 +1,6 @@ package fingerprint import ( - "errors" "fmt" "log" "net" @@ -56,10 +55,11 @@ func NewNetworkFingerprint(logger *log.Logger) Fingerprint { } func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { - // newNetwork is populated and addded to the Nodes resources - newNetwork := &structs.NetworkResource{} - var ip string + if node.Resources == nil { + node.Resources = &structs.Resources{} + } + // Find the named interface intf, err := f.findInterface(cfg.NetworkInterface) switch { case err != nil: @@ -69,52 +69,57 @@ func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) return false, nil } - if ip, err = f.ipAddress(intf); err != nil { - return false, fmt.Errorf("Unable to find IP address of interface: %s, err: %v", intf.Name, err) - } - - newNetwork.Device = intf.Name - node.Attributes["unique.network.ip-address"] = ip - newNetwork.IP = ip - newNetwork.CIDR = newNetwork.IP + "/32" - - f.logger.Printf("[DEBUG] fingerprint.network: Detected interface %v with IP %v during fingerprinting", intf.Name, ip) - + // Record the throughput of the interface + var mbits int throughput := f.linkSpeed(intf.Name) if cfg.NetworkSpeed != 0 { - newNetwork.MBits = cfg.NetworkSpeed - f.logger.Printf("[DEBUG] fingerprint.network: setting link speed to user configured speed: %d", newNetwork.MBits) + mbits = cfg.NetworkSpeed + f.logger.Printf("[DEBUG] fingerprint.network: setting link speed to user configured speed: %d", mbits) } else if throughput != 0 { - newNetwork.MBits = throughput - f.logger.Printf("[DEBUG] fingerprint.network: link speed for %v set to %v", intf.Name, newNetwork.MBits) + mbits = throughput + f.logger.Printf("[DEBUG] fingerprint.network: link speed for %v set to %v", intf.Name, mbits) } else { - newNetwork.MBits = defaultNetworkSpeed + mbits = defaultNetworkSpeed f.logger.Printf("[DEBUG] fingerprint.network: link speed could not be detected and no speed specified by user. Defaulting to %d", defaultNetworkSpeed) } - if node.Resources == nil { - node.Resources = &structs.Resources{} + // Create the network resources from the interface + nwResources, err := f.createNetworkResources(mbits, intf) + if err != nil { + return false, err } - node.Resources.Networks = append(node.Resources.Networks, newNetwork) + // Add the network resources to the node + node.Resources.Networks = nwResources + for _, nwResource := range nwResources { + f.logger.Printf("[DEBUG] fingerprint.network: Detected interface %v with IP: %v", intf.Name, nwResource.IP) + } + + // Deprecated, setting the first IP as unique IP for the node + if len(nwResources) > 0 { + node.Attributes["unique.network.ip-address"] = nwResources[0].IP + } // return true, because we have a network connection return true, nil } -// Gets the ipv4 addr for a network interface -func (f *NetworkFingerprint) ipAddress(intf *net.Interface) (string, error) { - var addrs []net.Addr - var err error - - if addrs, err = f.interfaceDetector.Addrs(intf); err != nil { - return "", err - } - - if len(addrs) == 0 { - return "", errors.New(fmt.Sprintf("Interface %s has no IP address", intf.Name)) +// createNetworkResources creates network resources for every IP +func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.Interface) ([]*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) for _, addr := range addrs { + // Create a new network resource + newNetwork := &structs.NetworkResource{ + Device: intf.Name, + MBits: throughput, + } + + // Find the IP Addr and the CIDR from the Address var ip net.IP switch v := (addr).(type) { case *net.IPNet: @@ -122,13 +127,21 @@ func (f *NetworkFingerprint) ipAddress(intf *net.Interface) (string, error) { case *net.IPAddr: 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 { - return ip.String(), nil + newNetwork.CIDR = newNetwork.IP + "/32" + } else { + newNetwork.CIDR = newNetwork.IP + "/128" } - } - - return "", fmt.Errorf("Couldn't parse IP address for interface %s", intf.Name) + nwResources = append(nwResources, newNetwork) + } + return nwResources, nil } // Checks if the device is marked UP by the operator @@ -138,8 +151,8 @@ func (f *NetworkFingerprint) isDeviceEnabled(intf *net.Interface) bool { // Checks if the device has any IP address configured func (f *NetworkFingerprint) deviceHasIpAddress(intf *net.Interface) bool { - _, err := f.ipAddress(intf) - return err == nil + addrs, err := f.interfaceDetector.Addrs(intf) + return err == nil && len(addrs) != 0 } func (n *NetworkFingerprint) isDeviceLoopBackOrPointToPoint(intf *net.Interface) bool { diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index 84e0a764228..eaa638b5678 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -126,8 +126,9 @@ func (n *NetworkInterfaceDetectorMultipleInterfaces) Addrs(intf *net.Interface) if intf.Name == "eth0" { _, ipnet1, _ := net.ParseCIDR("100.64.0.11/10") - _, ipnet2, _ := net.ParseCIDR("2005:DB6::/48") - return []net.Addr{ipnet1, ipnet2}, nil + _, 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" { @@ -307,4 +308,16 @@ func TestNetworkFingerPrint_excludelo_down_interfaces(t *testing.T) { if net.MBits == 0 { t.Fatal("Expected Network Resource to have a non-zero bandwith") } + + // 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)) + } } diff --git a/website/source/docs/agent/configuration/client.html.md b/website/source/docs/agent/configuration/client.html.md index 38cf283175e..d622f843c82 100644 --- a/website/source/docs/agent/configuration/client.html.md +++ b/website/source/docs/agent/configuration/client.html.md @@ -51,7 +51,9 @@ client { - `network_interface` `(string: "lo | lo0")` - Specifies the name of the interface to force network fingerprinting on. This defaults to the loopback - interface. + interface. All addresses on the interface are fingerprinted except the ones + which are scoped local for IPv6. The scheduler is going to pick one of the + many addresses which have been fingerprinted. - `network_speed` `(int: 0)` - Specifies an override for the network link speed. This value, if set, overrides any detected or defaulted link speed. Most