github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/fingerprint/network.go (about)

     1  package fingerprint
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"strings"
     7  
     8  	log "github.com/hashicorp/go-hclog"
     9  	sockaddr "github.com/hashicorp/go-sockaddr"
    10  	"github.com/hashicorp/go-sockaddr/template"
    11  	"github.com/hashicorp/nomad/client/config"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  )
    14  
    15  const (
    16  	// defaultNetworkSpeed is the speed set if the network link speed could not
    17  	// be detected.
    18  	defaultNetworkSpeed = 1000
    19  
    20  	// networkDisallowLinkLocalOption/Default are used to allow the operator to
    21  	// decide how the fingerprinter handles an interface that only contains link
    22  	// local addresses.
    23  	networkDisallowLinkLocalOption  = "fingerprint.network.disallow_link_local"
    24  	networkDisallowLinkLocalDefault = false
    25  )
    26  
    27  // NetworkFingerprint is used to fingerprint the Network capabilities of a node
    28  type NetworkFingerprint struct {
    29  	StaticFingerprinter
    30  	logger            log.Logger
    31  	interfaceDetector NetworkInterfaceDetector
    32  }
    33  
    34  // An interface to isolate calls to various api in net package
    35  // This facilitates testing where we can implement
    36  // fake interfaces and addresses to test varios code paths
    37  type NetworkInterfaceDetector interface {
    38  	Interfaces() ([]net.Interface, error)
    39  	InterfaceByName(name string) (*net.Interface, error)
    40  	Addrs(intf *net.Interface) ([]net.Addr, error)
    41  }
    42  
    43  // Implements the interface detector which calls net directly
    44  type DefaultNetworkInterfaceDetector struct {
    45  }
    46  
    47  func (b *DefaultNetworkInterfaceDetector) Interfaces() ([]net.Interface, error) {
    48  	return net.Interfaces()
    49  }
    50  
    51  func (b *DefaultNetworkInterfaceDetector) InterfaceByName(name string) (*net.Interface, error) {
    52  	return net.InterfaceByName(name)
    53  }
    54  
    55  func (b *DefaultNetworkInterfaceDetector) Addrs(intf *net.Interface) ([]net.Addr, error) {
    56  	return intf.Addrs()
    57  }
    58  
    59  // NewNetworkFingerprint returns a new NetworkFingerprinter with the given
    60  // logger
    61  func NewNetworkFingerprint(logger log.Logger) Fingerprint {
    62  	f := &NetworkFingerprint{logger: logger.Named("network"), interfaceDetector: &DefaultNetworkInterfaceDetector{}}
    63  	return f
    64  }
    65  
    66  func (f *NetworkFingerprint) Fingerprint(req *FingerprintRequest, resp *FingerprintResponse) error {
    67  	cfg := req.Config
    68  
    69  	// Find the named interface
    70  	intf, err := f.findInterface(cfg.NetworkInterface)
    71  	switch {
    72  	case err != nil:
    73  		return fmt.Errorf("Error while detecting network interface %s during fingerprinting: %v",
    74  			cfg.NetworkInterface,
    75  			err)
    76  	case intf == nil:
    77  		// No interface could be found
    78  		return nil
    79  	}
    80  
    81  	// Create a sub-logger with common values to help with debugging
    82  	logger := f.logger.With("interface", intf.Name)
    83  
    84  	// Record the throughput of the interface
    85  	var mbits int
    86  	throughput := f.linkSpeed(intf.Name)
    87  	if cfg.NetworkSpeed != 0 {
    88  		mbits = cfg.NetworkSpeed
    89  		logger.Debug("setting link speed to user configured speed", "mbits", mbits)
    90  	} else if throughput != 0 {
    91  		mbits = throughput
    92  		logger.Debug("link speed detected", "mbits", mbits)
    93  	} else {
    94  		mbits = defaultNetworkSpeed
    95  		logger.Debug("link speed could not be detected and no speed specified by user, falling back to default speed", "mbits", defaultNetworkSpeed)
    96  	}
    97  
    98  	// Create the network resources from the interface
    99  	disallowLinkLocal := cfg.ReadBoolDefault(networkDisallowLinkLocalOption, networkDisallowLinkLocalDefault)
   100  	nwResources, err := f.createNetworkResources(mbits, intf, disallowLinkLocal)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	// COMPAT(0.10): Remove in 0.10
   106  	resp.Resources = &structs.Resources{
   107  		Networks: nwResources,
   108  	}
   109  
   110  	resp.NodeResources = &structs.NodeResources{
   111  		Networks: nwResources,
   112  	}
   113  
   114  	for _, nwResource := range nwResources {
   115  		logger.Debug("detected interface IP", "IP", nwResource.IP)
   116  	}
   117  
   118  	// Deprecated, setting the first IP as unique IP for the node
   119  	if len(nwResources) > 0 {
   120  		resp.AddAttribute("unique.network.ip-address", nwResources[0].IP)
   121  	}
   122  
   123  	ifaces, err := f.interfaceDetector.Interfaces()
   124  	if err != nil {
   125  		return err
   126  	}
   127  	nodeNetResources, err := f.createNodeNetworkResources(ifaces, disallowLinkLocal, req.Config)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	resp.NodeResources.NodeNetworks = nodeNetResources
   132  
   133  	resp.Detected = true
   134  
   135  	return nil
   136  }
   137  
   138  func (f *NetworkFingerprint) createNodeNetworkResources(ifaces []net.Interface, disallowLinkLocal bool, conf *config.Config) ([]*structs.NodeNetworkResource, error) {
   139  	nets := make([]*structs.NodeNetworkResource, 0)
   140  	for _, iface := range ifaces {
   141  		speed := f.linkSpeed(iface.Name)
   142  		if speed == 0 {
   143  			speed = defaultNetworkSpeed
   144  			f.logger.Debug("link speed could not be detected, falling back to default speed", "interface", iface.Name, "mbits", defaultNetworkSpeed)
   145  		}
   146  
   147  		newNetwork := &structs.NodeNetworkResource{
   148  			Mode:       "host",
   149  			Device:     iface.Name,
   150  			MacAddress: iface.HardwareAddr.String(),
   151  			Speed:      speed,
   152  		}
   153  		addrs, err := f.interfaceDetector.Addrs(&iface)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		var networkAddrs, linkLocalAddrs []structs.NodeNetworkAddress
   158  		for _, addr := range addrs {
   159  			// Find the IP Addr and the CIDR from the Address
   160  			var ip net.IP
   161  			var family structs.NodeNetworkAF
   162  			switch v := (addr).(type) {
   163  			case *net.IPNet:
   164  				ip = v.IP
   165  			case *net.IPAddr:
   166  				ip = v.IP
   167  			}
   168  
   169  			if ip.To4() != nil {
   170  				family = structs.NodeNetworkAF_IPv4
   171  			} else {
   172  				family = structs.NodeNetworkAF_IPv6
   173  			}
   174  			for _, alias := range deriveAddressAliases(iface, ip, conf) {
   175  				newAddr := structs.NodeNetworkAddress{
   176  					Address: ip.String(),
   177  					Family:  family,
   178  					Alias:   alias,
   179  				}
   180  
   181  				if hostNetwork, ok := conf.HostNetworks[alias]; ok {
   182  					newAddr.ReservedPorts = hostNetwork.ReservedPorts
   183  				}
   184  
   185  				if newAddr.Alias != "" {
   186  					if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
   187  						linkLocalAddrs = append(linkLocalAddrs, newAddr)
   188  					} else {
   189  						networkAddrs = append(networkAddrs, newAddr)
   190  					}
   191  				}
   192  			}
   193  		}
   194  
   195  		if len(networkAddrs) == 0 && len(linkLocalAddrs) > 0 {
   196  			if disallowLinkLocal {
   197  				f.logger.Debug("ignoring detected link-local address on interface", "interface", iface.Name)
   198  			} else {
   199  				newNetwork.Addresses = linkLocalAddrs
   200  			}
   201  		} else {
   202  			newNetwork.Addresses = networkAddrs
   203  		}
   204  
   205  		if len(newNetwork.Addresses) > 0 {
   206  			nets = append(nets, newNetwork)
   207  		}
   208  	}
   209  	return nets, nil
   210  }
   211  
   212  func deriveAddressAliases(iface net.Interface, addr net.IP, config *config.Config) (aliases []string) {
   213  	for name, conf := range config.HostNetworks {
   214  		var cidrMatch, ifaceMatch bool
   215  		if conf.CIDR != "" {
   216  			for _, cidr := range strings.Split(conf.CIDR, ",") {
   217  				_, ipnet, err := net.ParseCIDR(cidr)
   218  				if err != nil {
   219  					continue
   220  				}
   221  
   222  				if ipnet.Contains(addr) {
   223  					cidrMatch = true
   224  					break
   225  				}
   226  			}
   227  		} else {
   228  			cidrMatch = true
   229  		}
   230  		if conf.Interface != "" {
   231  			ifaceName, err := template.Parse(conf.Interface)
   232  			if err != nil {
   233  				continue
   234  			}
   235  
   236  			if ifaceName == iface.Name {
   237  				ifaceMatch = true
   238  			}
   239  		} else {
   240  			ifaceMatch = true
   241  		}
   242  		if cidrMatch && ifaceMatch {
   243  			aliases = append(aliases, name)
   244  		}
   245  	}
   246  
   247  	if len(aliases) > 0 {
   248  		return
   249  	}
   250  
   251  	if config.NetworkInterface != "" {
   252  		if config.NetworkInterface == iface.Name {
   253  			return []string{"default"}
   254  		}
   255  	} else if ri, err := sockaddr.NewRouteInfo(); err == nil {
   256  		defaultIface, err := ri.GetDefaultInterfaceName()
   257  		if err == nil && iface.Name == defaultIface {
   258  			return []string{"default"}
   259  		}
   260  	}
   261  
   262  	return
   263  }
   264  
   265  // createNetworkResources creates network resources for every IP
   266  func (f *NetworkFingerprint) createNetworkResources(throughput int, intf *net.Interface, disallowLinkLocal bool) ([]*structs.NetworkResource, error) {
   267  	// Find the interface with the name
   268  	addrs, err := f.interfaceDetector.Addrs(intf)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	nwResources := make([]*structs.NetworkResource, 0)
   274  	linkLocals := make([]*structs.NetworkResource, 0)
   275  
   276  	for _, addr := range addrs {
   277  		// Create a new network resource
   278  		newNetwork := &structs.NetworkResource{
   279  			Mode:   "host",
   280  			Device: intf.Name,
   281  			MBits:  throughput,
   282  		}
   283  
   284  		// Find the IP Addr and the CIDR from the Address
   285  		var ip net.IP
   286  		switch v := (addr).(type) {
   287  		case *net.IPNet:
   288  			ip = v.IP
   289  		case *net.IPAddr:
   290  			ip = v.IP
   291  		}
   292  
   293  		newNetwork.IP = ip.String()
   294  		if ip.To4() != nil {
   295  			newNetwork.CIDR = newNetwork.IP + "/32"
   296  		} else {
   297  			newNetwork.CIDR = newNetwork.IP + "/128"
   298  		}
   299  
   300  		// If the ip is link-local then we ignore it unless the user allows it
   301  		// and we detect nothing else
   302  		if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
   303  			linkLocals = append(linkLocals, newNetwork)
   304  			continue
   305  		}
   306  
   307  		nwResources = append(nwResources, newNetwork)
   308  	}
   309  
   310  	if len(nwResources) == 0 && len(linkLocals) != 0 {
   311  		if disallowLinkLocal {
   312  			f.logger.Debug("ignoring detected link-local address on interface", "interface", intf.Name)
   313  			return nwResources, nil
   314  		}
   315  
   316  		return linkLocals, nil
   317  	}
   318  
   319  	return nwResources, nil
   320  }
   321  
   322  // Returns the interface with the name passed by user. If the name is blank, we
   323  // use the interface attached to the default route.
   324  func (f *NetworkFingerprint) findInterface(deviceName string) (*net.Interface, error) {
   325  	// If we aren't given a device, look it up by using the interface with the default route
   326  	if deviceName == "" {
   327  		ri, err := sockaddr.NewRouteInfo()
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  
   332  		defaultIfName, err := ri.GetDefaultInterfaceName()
   333  		if err != nil {
   334  			return nil, err
   335  		}
   336  		if defaultIfName == "" {
   337  			return nil, fmt.Errorf("no network_interface given and failed to determine interface attached to default route")
   338  		}
   339  		deviceName = defaultIfName
   340  	}
   341  
   342  	return f.interfaceDetector.InterfaceByName(deviceName)
   343  }