Skip to content

Commit

Permalink
Merge pull request #3089 from hashicorp/b-link-local
Browse files Browse the repository at this point in the history
Handle interfaces that only have link-local addrs
  • Loading branch information
dadgar committed Aug 24, 2017
2 parents 46e6547 + 77c1ebd commit 66a6b5c
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 7 deletions.
35 changes: 29 additions & 6 deletions client/fingerprint/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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{
Expand All @@ -128,19 +138,32 @@ 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"
} else {
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
}

Expand Down
145 changes: 145 additions & 0 deletions client/fingerprint/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -109,6 +127,10 @@ func (n *NetworkInterfaceDetectorMultipleInterfaces) InterfaceByName(name string
intf = &eth1
case "eth2":
intf = &eth2
case "eth3":
intf = &eth3
case "eth4":
intf = &eth4
}
if intf != nil {
return intf, nil
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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")
}
}
15 changes: 14 additions & 1 deletion website/source/docs/agent/configuration/client.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 66a6b5c

Please sign in to comment.