Skip to content

Commit

Permalink
Merge pull request #2536 from hashicorp/f-ipv6-fingerprint
Browse files Browse the repository at this point in the history
Support IPv6 addresses during fingerprinting
  • Loading branch information
diptanu authored Apr 10, 2017
2 parents 83be4ad + b0a20b4 commit 915e4e7
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 46 deletions.
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
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() {
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

0 comments on commit 915e4e7

Please sign in to comment.