Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support IPv6 addresses during fingerprinting #2536

Merged
merged 10 commits into from
Apr 10, 2017
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 3 additions & 3 deletions api/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
93 changes: 53 additions & 40 deletions client/fingerprint/network.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fingerprint

import (
"errors"
"fmt"
"log"
"net"
Expand Down Expand Up @@ -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:
Expand All @@ -69,66 +69,79 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to set device name

Copy link
Contributor Author

@diptanu diptanu Apr 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}

// If the ip is link-local then we ignore it
if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a test around these and IPv4/6 CIDRs

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
Expand All @@ -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 {
Expand Down
17 changes: 15 additions & 2 deletions client/fingerprint/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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))
}
}
4 changes: 3 additions & 1 deletion website/source/docs/agent/configuration/client.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down