github.com/bigcommerce/nomad@v0.9.3-bc/client/fingerprint/network.go (about)

     1  package fingerprint
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  
     7  	log "github.com/hashicorp/go-hclog"
     8  	sockaddr "github.com/hashicorp/go-sockaddr"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  )
    11  
    12  const (
    13  	// defaultNetworkSpeed is the speed set if the network link speed could not
    14  	// be detected.
    15  	defaultNetworkSpeed = 1000
    16  
    17  	// networkDisallowLinkLocalOption/Default are used to allow the operator to
    18  	// decide how the fingerprinter handles an interface that only contains link
    19  	// local addresses.
    20  	networkDisallowLinkLocalOption  = "fingerprint.network.disallow_link_local"
    21  	networkDisallowLinkLocalDefault = false
    22  )
    23  
    24  // NetworkFingerprint is used to fingerprint the Network capabilities of a node
    25  type NetworkFingerprint struct {
    26  	StaticFingerprinter
    27  	logger            log.Logger
    28  	interfaceDetector NetworkInterfaceDetector
    29  }
    30  
    31  // An interface to isolate calls to various api in net package
    32  // This facilitates testing where we can implement
    33  // fake interfaces and addresses to test varios code paths
    34  type NetworkInterfaceDetector interface {
    35  	Interfaces() ([]net.Interface, error)
    36  	InterfaceByName(name string) (*net.Interface, error)
    37  	Addrs(intf *net.Interface) ([]net.Addr, error)
    38  }
    39  
    40  // Implements the interface detector which calls net directly
    41  type DefaultNetworkInterfaceDetector struct {
    42  }
    43  
    44  func (b *DefaultNetworkInterfaceDetector) Interfaces() ([]net.Interface, error) {
    45  	return net.Interfaces()
    46  }
    47  
    48  func (b *DefaultNetworkInterfaceDetector) InterfaceByName(name string) (*net.Interface, error) {
    49  	return net.InterfaceByName(name)
    50  }
    51  
    52  func (b *DefaultNetworkInterfaceDetector) Addrs(intf *net.Interface) ([]net.Addr, error) {
    53  	return intf.Addrs()
    54  }
    55  
    56  // NewNetworkFingerprint returns a new NetworkFingerprinter with the given
    57  // logger
    58  func NewNetworkFingerprint(logger log.Logger) Fingerprint {
    59  	f := &NetworkFingerprint{logger: logger.Named("network"), interfaceDetector: &DefaultNetworkInterfaceDetector{}}
    60  	return f
    61  }
    62  
    63  func (f *NetworkFingerprint) Fingerprint(req *FingerprintRequest, resp *FingerprintResponse) error {
    64  	cfg := req.Config
    65  
    66  	// Find the named interface
    67  	intf, err := f.findInterface(cfg.NetworkInterface)
    68  	switch {
    69  	case err != nil:
    70  		return fmt.Errorf("Error while detecting network interface %s during fingerprinting: %v",
    71  			cfg.NetworkInterface,
    72  			err)
    73  	case intf == nil:
    74  		// No interface could be found
    75  		return nil
    76  	}
    77  
    78  	// Record the throughput of the interface
    79  	var mbits int
    80  	throughput := f.linkSpeed(intf.Name)
    81  	if cfg.NetworkSpeed != 0 {
    82  		mbits = cfg.NetworkSpeed
    83  		f.logger.Debug("setting link speed to user configured speed", "mbits", mbits)
    84  	} else if throughput != 0 {
    85  		mbits = throughput
    86  		f.logger.Debug("link speed detected", "interface", intf.Name, "mbits", mbits)
    87  	} else {
    88  		mbits = defaultNetworkSpeed
    89  		f.logger.Debug("link speed could not be detected and no speed specified by user, falling back to default speed", "mbits", defaultNetworkSpeed)
    90  	}
    91  
    92  	// Create the network resources from the interface
    93  	disallowLinkLocal := cfg.ReadBoolDefault(networkDisallowLinkLocalOption, networkDisallowLinkLocalDefault)
    94  	nwResources, err := f.createNetworkResources(mbits, intf, disallowLinkLocal)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	// COMPAT(0.10): Remove in 0.10
   100  	resp.Resources = &structs.Resources{
   101  		Networks: nwResources,
   102  	}
   103  
   104  	resp.NodeResources = &structs.NodeResources{
   105  		Networks: nwResources,
   106  	}
   107  
   108  	for _, nwResource := range nwResources {
   109  		f.logger.Debug("detected interface IP", "interface", intf.Name, "IP", nwResource.IP)
   110  	}
   111  
   112  	// Deprecated, setting the first IP as unique IP for the node
   113  	if len(nwResources) > 0 {
   114  		resp.AddAttribute("unique.network.ip-address", nwResources[0].IP)
   115  	}
   116  	resp.Detected = true
   117  
   118  	return nil
   119  }
   120  
   121  // createNetworkResources creates network resources for every IP
   122  func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.Interface, disallowLinkLocal bool) ([]*structs.NetworkResource, error) {
   123  	// Find the interface with the name
   124  	addrs, err := f.interfaceDetector.Addrs(intf)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	nwResources := make([]*structs.NetworkResource, 0)
   130  	linkLocals := make([]*structs.NetworkResource, 0)
   131  
   132  	for _, addr := range addrs {
   133  		// Create a new network resource
   134  		newNetwork := &structs.NetworkResource{
   135  			Device: intf.Name,
   136  			MBits:  throughput,
   137  		}
   138  
   139  		// Find the IP Addr and the CIDR from the Address
   140  		var ip net.IP
   141  		switch v := (addr).(type) {
   142  		case *net.IPNet:
   143  			ip = v.IP
   144  		case *net.IPAddr:
   145  			ip = v.IP
   146  		}
   147  
   148  		newNetwork.IP = ip.String()
   149  		if ip.To4() != nil {
   150  			newNetwork.CIDR = newNetwork.IP + "/32"
   151  		} else {
   152  			newNetwork.CIDR = newNetwork.IP + "/128"
   153  		}
   154  
   155  		// If the ip is link-local then we ignore it unless the user allows it
   156  		// and we detect nothing else
   157  		if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
   158  			linkLocals = append(linkLocals, newNetwork)
   159  			continue
   160  		}
   161  
   162  		nwResources = append(nwResources, newNetwork)
   163  	}
   164  
   165  	if len(nwResources) == 0 && len(linkLocals) != 0 {
   166  		if disallowLinkLocal {
   167  			f.logger.Debug("ignoring detected link-local address on interface", "interface", intf.Name)
   168  			return nwResources, nil
   169  		}
   170  
   171  		return linkLocals, nil
   172  	}
   173  
   174  	return nwResources, nil
   175  }
   176  
   177  // Returns the interface with the name passed by user. If the name is blank, we
   178  // use the interface attached to the default route.
   179  func (f *NetworkFingerprint) findInterface(deviceName string) (*net.Interface, error) {
   180  	// If we aren't given a device, look it up by using the interface with the default route
   181  	if deviceName == "" {
   182  		ri, err := sockaddr.NewRouteInfo()
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  
   187  		defaultIfName, err := ri.GetDefaultInterfaceName()
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  		if defaultIfName == "" {
   192  			return nil, fmt.Errorf("no network_interface given and failed to determine interface attached to default route")
   193  		}
   194  		deviceName = defaultIfName
   195  	}
   196  
   197  	return f.interfaceDetector.InterfaceByName(deviceName)
   198  }