2015-09-18 21:56:27 +00:00
|
|
|
package fingerprint
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2015-09-23 03:56:31 +00:00
|
|
|
"net"
|
2015-09-18 21:56:27 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/client/config"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
|
|
)
|
|
|
|
|
2016-11-15 21:55:51 +00:00
|
|
|
const (
|
|
|
|
// defaultNetworkSpeed is the speed set if the network link speed could not
|
|
|
|
// be detected.
|
|
|
|
defaultNetworkSpeed = 1000
|
2017-08-23 22:32:22 +00:00
|
|
|
|
|
|
|
// 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
|
2016-11-15 21:55:51 +00:00
|
|
|
)
|
|
|
|
|
2015-09-23 15:12:56 +00:00
|
|
|
// NetworkFingerprint is used to fingerprint the Network capabilities of a node
|
|
|
|
type NetworkFingerprint struct {
|
2015-11-05 21:46:02 +00:00
|
|
|
StaticFingerprinter
|
2015-10-28 22:58:40 +00:00
|
|
|
logger *log.Logger
|
|
|
|
interfaceDetector NetworkInterfaceDetector
|
|
|
|
}
|
|
|
|
|
2015-10-29 18:05:58 +00:00
|
|
|
// An interface to isolate calls to various api in net package
|
|
|
|
// This facilitates testing where we can implement
|
|
|
|
// fake interfaces and addresses to test varios code paths
|
2015-10-28 22:58:40 +00:00
|
|
|
type NetworkInterfaceDetector interface {
|
|
|
|
Interfaces() ([]net.Interface, error)
|
|
|
|
InterfaceByName(name string) (*net.Interface, error)
|
2015-10-29 18:01:15 +00:00
|
|
|
Addrs(intf *net.Interface) ([]net.Addr, error)
|
2015-10-28 22:58:40 +00:00
|
|
|
}
|
|
|
|
|
2015-10-29 18:05:58 +00:00
|
|
|
// Implements the interface detector which calls net directly
|
2015-10-29 22:55:49 +00:00
|
|
|
type DefaultNetworkInterfaceDetector struct {
|
2015-10-28 22:58:40 +00:00
|
|
|
}
|
|
|
|
|
2015-10-29 22:55:49 +00:00
|
|
|
func (b *DefaultNetworkInterfaceDetector) Interfaces() ([]net.Interface, error) {
|
2015-10-28 22:58:40 +00:00
|
|
|
return net.Interfaces()
|
|
|
|
}
|
|
|
|
|
2015-10-29 22:55:49 +00:00
|
|
|
func (b *DefaultNetworkInterfaceDetector) InterfaceByName(name string) (*net.Interface, error) {
|
2015-10-28 22:58:40 +00:00
|
|
|
return net.InterfaceByName(name)
|
2015-09-18 21:56:27 +00:00
|
|
|
}
|
|
|
|
|
2015-10-29 22:55:49 +00:00
|
|
|
func (b *DefaultNetworkInterfaceDetector) Addrs(intf *net.Interface) ([]net.Addr, error) {
|
2015-10-29 18:01:15 +00:00
|
|
|
return intf.Addrs()
|
|
|
|
}
|
|
|
|
|
2016-03-23 00:12:30 +00:00
|
|
|
// NewNetworkFingerprint returns a new NetworkFingerprinter with the given
|
2015-09-23 15:12:56 +00:00
|
|
|
// logger
|
2016-03-23 00:12:30 +00:00
|
|
|
func NewNetworkFingerprint(logger *log.Logger) Fingerprint {
|
2015-10-29 22:55:49 +00:00
|
|
|
f := &NetworkFingerprint{logger: logger, interfaceDetector: &DefaultNetworkInterfaceDetector{}}
|
2015-09-18 21:56:27 +00:00
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
2015-09-23 15:12:56 +00:00
|
|
|
func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
|
2017-04-06 08:06:32 +00:00
|
|
|
if node.Resources == nil {
|
|
|
|
node.Resources = &structs.Resources{}
|
|
|
|
}
|
2015-10-26 15:11:39 +00:00
|
|
|
|
2017-04-06 08:06:32 +00:00
|
|
|
// Find the named interface
|
2015-10-28 22:48:08 +00:00
|
|
|
intf, err := f.findInterface(cfg.NetworkInterface)
|
2016-07-11 03:18:57 +00:00
|
|
|
switch {
|
|
|
|
case err != nil:
|
2015-10-28 22:03:11 +00:00
|
|
|
return false, fmt.Errorf("Error while detecting network interface during fingerprinting: %v", err)
|
2016-07-11 03:18:57 +00:00
|
|
|
case intf == nil:
|
|
|
|
// No interface could be found
|
2015-11-19 03:01:39 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2017-04-06 08:06:32 +00:00
|
|
|
// Record the throughput of the interface
|
|
|
|
var mbits int
|
2016-11-15 21:55:51 +00:00
|
|
|
throughput := f.linkSpeed(intf.Name)
|
|
|
|
if cfg.NetworkSpeed != 0 {
|
2017-04-06 08:06:32 +00:00
|
|
|
mbits = cfg.NetworkSpeed
|
|
|
|
f.logger.Printf("[DEBUG] fingerprint.network: setting link speed to user configured speed: %d", mbits)
|
2016-11-15 21:55:51 +00:00
|
|
|
} else if throughput != 0 {
|
2017-04-06 08:06:32 +00:00
|
|
|
mbits = throughput
|
|
|
|
f.logger.Printf("[DEBUG] fingerprint.network: link speed for %v set to %v", intf.Name, mbits)
|
2015-10-03 00:32:11 +00:00
|
|
|
} else {
|
2017-04-06 08:06:32 +00:00
|
|
|
mbits = defaultNetworkSpeed
|
2016-11-15 21:55:51 +00:00
|
|
|
f.logger.Printf("[DEBUG] fingerprint.network: link speed could not be detected and no speed specified by user. Defaulting to %d", defaultNetworkSpeed)
|
2015-09-23 04:22:23 +00:00
|
|
|
}
|
|
|
|
|
2017-04-06 20:42:32 +00:00
|
|
|
// Create the network resources from the interface
|
2017-08-23 22:32:22 +00:00
|
|
|
disallowLinkLocal := cfg.ReadBoolDefault(networkDisallowLinkLocalOption, networkDisallowLinkLocalDefault)
|
|
|
|
nwResources, err := f.createNetworkResources(mbits, intf, disallowLinkLocal)
|
2017-04-06 20:42:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
2015-09-18 21:56:27 +00:00
|
|
|
}
|
|
|
|
|
2017-04-06 20:42:32 +00:00
|
|
|
// Add the network resources to the node
|
2017-04-08 01:28:22 +00:00
|
|
|
node.Resources.Networks = nwResources
|
2017-04-06 20:42:32 +00:00
|
|
|
for _, nwResource := range nwResources {
|
2017-04-08 01:28:22 +00:00
|
|
|
f.logger.Printf("[DEBUG] fingerprint.network: Detected interface %v with IP: %v", intf.Name, nwResource.IP)
|
2017-04-06 20:42:32 +00:00
|
|
|
}
|
2017-04-06 08:06:32 +00:00
|
|
|
|
2017-04-10 18:31:14 +00:00
|
|
|
// Deprecated, setting the first IP as unique IP for the node
|
2017-04-06 20:42:32 +00:00
|
|
|
if len(nwResources) > 0 {
|
|
|
|
node.Attributes["unique.network.ip-address"] = nwResources[0].IP
|
2017-04-06 08:06:32 +00:00
|
|
|
}
|
2015-09-23 04:22:23 +00:00
|
|
|
|
2015-09-18 21:56:27 +00:00
|
|
|
// return true, because we have a network connection
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2017-04-06 20:42:32 +00:00
|
|
|
// createNetworkResources creates network resources for every IP
|
2017-08-23 22:32:22 +00:00
|
|
|
func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.Interface, disallowLinkLocal bool) ([]*structs.NetworkResource, error) {
|
2017-04-06 20:42:32 +00:00
|
|
|
// Find the interface with the name
|
|
|
|
addrs, err := f.interfaceDetector.Addrs(intf)
|
|
|
|
if err != nil {
|
2017-04-06 08:06:32 +00:00
|
|
|
return nil, err
|
2015-10-01 15:31:47 +00:00
|
|
|
}
|
2017-08-23 22:32:22 +00:00
|
|
|
|
2017-04-07 23:04:36 +00:00
|
|
|
nwResources := make([]*structs.NetworkResource, 0)
|
2017-08-23 22:32:22 +00:00
|
|
|
linkLocals := make([]*structs.NetworkResource, 0)
|
|
|
|
|
2017-04-07 23:04:36 +00:00
|
|
|
for _, addr := range addrs {
|
2017-04-06 20:42:32 +00:00
|
|
|
// Create a new network resource
|
|
|
|
newNetwork := &structs.NetworkResource{
|
|
|
|
Device: intf.Name,
|
|
|
|
MBits: throughput,
|
|
|
|
}
|
2015-10-01 15:31:47 +00:00
|
|
|
|
2017-04-06 20:42:32 +00:00
|
|
|
// Find the IP Addr and the CIDR from the Address
|
2017-04-07 23:04:36 +00:00
|
|
|
var ip net.IP
|
2015-10-28 20:38:28 +00:00
|
|
|
switch v := (addr).(type) {
|
|
|
|
case *net.IPNet:
|
2017-04-07 23:04:36 +00:00
|
|
|
ip = v.IP
|
2015-10-28 20:38:28 +00:00
|
|
|
case *net.IPAddr:
|
2017-04-07 23:04:36 +00:00
|
|
|
ip = v.IP
|
2015-10-28 20:38:28 +00:00
|
|
|
}
|
2017-04-07 23:04:36 +00:00
|
|
|
|
|
|
|
newNetwork.IP = ip.String()
|
|
|
|
if ip.To4() != nil {
|
|
|
|
newNetwork.CIDR = newNetwork.IP + "/32"
|
|
|
|
} else {
|
|
|
|
newNetwork.CIDR = newNetwork.IP + "/128"
|
|
|
|
}
|
|
|
|
|
2017-08-23 22:32:22 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-04-07 23:04:36 +00:00
|
|
|
nwResources = append(nwResources, newNetwork)
|
2015-10-01 15:31:47 +00:00
|
|
|
}
|
2017-08-23 22:32:22 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-04-06 20:42:32 +00:00
|
|
|
return nwResources, nil
|
2015-10-01 15:31:47 +00:00
|
|
|
}
|
|
|
|
|
2015-10-28 21:03:33 +00:00
|
|
|
// Checks if the device is marked UP by the operator
|
2015-10-27 21:14:25 +00:00
|
|
|
func (f *NetworkFingerprint) isDeviceEnabled(intf *net.Interface) bool {
|
|
|
|
return intf.Flags&net.FlagUp != 0
|
|
|
|
}
|
2015-09-28 23:23:30 +00:00
|
|
|
|
2015-10-28 21:03:33 +00:00
|
|
|
// Checks if the device has any IP address configured
|
2015-10-27 21:14:25 +00:00
|
|
|
func (f *NetworkFingerprint) deviceHasIpAddress(intf *net.Interface) bool {
|
2017-04-06 20:42:32 +00:00
|
|
|
addrs, err := f.interfaceDetector.Addrs(intf)
|
|
|
|
return err == nil && len(addrs) != 0
|
2015-10-27 21:14:25 +00:00
|
|
|
}
|
2015-09-18 21:56:27 +00:00
|
|
|
|
2015-10-27 21:14:25 +00:00
|
|
|
func (n *NetworkFingerprint) isDeviceLoopBackOrPointToPoint(intf *net.Interface) bool {
|
2015-10-29 22:14:13 +00:00
|
|
|
return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0
|
2015-09-18 21:56:27 +00:00
|
|
|
}
|
2015-10-28 18:11:13 +00:00
|
|
|
|
2015-10-28 22:48:08 +00:00
|
|
|
// Returns the interface with the name passed by user
|
|
|
|
// If the name is blank then it iterates through all the devices
|
|
|
|
// and finds one which is routable and marked as UP
|
|
|
|
// It excludes PPP and lo devices unless they are specifically asked
|
|
|
|
func (f *NetworkFingerprint) findInterface(deviceName string) (*net.Interface, error) {
|
2015-10-28 22:03:11 +00:00
|
|
|
var err error
|
|
|
|
|
2015-10-28 18:11:13 +00:00
|
|
|
if deviceName != "" {
|
2015-10-28 22:58:40 +00:00
|
|
|
return f.interfaceDetector.InterfaceByName(deviceName)
|
2015-10-28 18:11:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var intfs []net.Interface
|
|
|
|
|
2015-10-28 22:58:40 +00:00
|
|
|
if intfs, err = f.interfaceDetector.Interfaces(); err != nil {
|
2015-10-28 18:11:13 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:10:02 +00:00
|
|
|
// Since we aren't given an interface to use we have to pick using a
|
|
|
|
// heuristic. To choose between interfaces we avoid down interfaces, those
|
|
|
|
// that are loopback, point to point, or don't have an address. Of the
|
|
|
|
// remaining interfaces we rank them positively for each global unicast
|
|
|
|
// address and slightly negatively for a link local address. This makes
|
|
|
|
// us select interfaces that have more globally routable address over those
|
|
|
|
// that do not.
|
|
|
|
var bestChoice *net.Interface
|
|
|
|
bestScore := -1000
|
2015-10-28 18:11:13 +00:00
|
|
|
for _, intf := range intfs {
|
2017-11-13 23:10:02 +00:00
|
|
|
// We require a non-loopback interface that is enabled with a valid
|
|
|
|
// address.
|
|
|
|
if !f.isDeviceEnabled(&intf) || f.isDeviceLoopBackOrPointToPoint(&intf) || !f.deviceHasIpAddress(&intf) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
addrs, err := f.interfaceDetector.Addrs(&intf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-10-28 18:11:13 +00:00
|
|
|
}
|
2015-10-28 22:48:08 +00:00
|
|
|
|
2017-11-13 23:10:02 +00:00
|
|
|
seenLinkLocal := false
|
|
|
|
intfScore := 0
|
|
|
|
for _, addr := range addrs {
|
|
|
|
// 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 !seenLinkLocal && (ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()) {
|
|
|
|
intfScore -= 1
|
|
|
|
seenLinkLocal = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if ip.IsGlobalUnicast() {
|
|
|
|
intfScore += 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if intfScore > bestScore {
|
|
|
|
// Make a copy on the stack so we grab a pointer to the correct
|
|
|
|
// interface
|
|
|
|
local := intf
|
|
|
|
bestChoice = &local
|
|
|
|
bestScore = intfScore
|
|
|
|
}
|
2015-10-28 22:48:08 +00:00
|
|
|
}
|
2017-11-13 23:10:02 +00:00
|
|
|
|
|
|
|
return bestChoice, nil
|
2015-10-28 18:11:13 +00:00
|
|
|
}
|