github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/network/discovery.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/errors"
    10  )
    11  
    12  // GetObservedNetworkConfig uses the given source to find all available network
    13  // interfaces and their assigned addresses, and returns the result as
    14  // []params.NetworkConfig. In addition to what the source returns, a few
    15  // additional transformations are done:
    16  //
    17  //   - On any OS, the state (UP/DOWN) of each interface and the DeviceIndex field,
    18  //     will be correctly populated. Loopback interfaces are also properly detected
    19  //     and will have InterfaceType set as LoopbackInterface.
    20  //   - On Linux only, the InterfaceType field will be reliably detected for a few
    21  //     types: BondInterface, BridgeInterface, VLAN_8021QInterface.
    22  //   - Also on Linux, for interfaces that are discovered to be ports on a bridge,
    23  //     the ParentInterfaceName will be populated with the name of the bridge.
    24  //   - ConfigType fields will be set to ConfigManual when no address is detected,
    25  //     or ConfigStatic when it is.
    26  //   - NICs that correspond to the internal port of an OVS-managed switch will
    27  //     have their type forced to bridge and their virtual port type set to
    28  //     OvsPort.
    29  //   - TODO: IPv6 link-local addresses will be ignored and treated as empty ATM.
    30  func GetObservedNetworkConfig(source ConfigSource) (InterfaceInfos, error) {
    31  	logger.Tracef("discovering observed machine network config...")
    32  
    33  	interfaces, err := source.Interfaces()
    34  	if err != nil {
    35  		return nil, errors.Annotate(err, "detecting network interfaces")
    36  	}
    37  	if len(interfaces) == 0 {
    38  		logger.Tracef("no network interfaces")
    39  		return nil, nil
    40  	}
    41  
    42  	knownOVSBridges, err := source.OvsManagedBridges()
    43  	if err != nil {
    44  		// NOTE(achilleasa): we will only get an error here if we do
    45  		// locate the OVS cli tools and get an error executing them.
    46  		return nil, errors.Annotate(err, "querying OVS bridges")
    47  	}
    48  
    49  	defaultRoute, defaultRouteDevice, err := source.DefaultRoute()
    50  	if err != nil {
    51  		return nil, errors.Annotate(err, "retrieving default route")
    52  	}
    53  
    54  	var configs InterfaceInfos
    55  	var bridgeNames []string
    56  	var noAddressesNics []string
    57  
    58  	for _, nic := range interfaces {
    59  		virtualPortType := NonVirtualPort
    60  		if knownOVSBridges.Contains(nic.Name()) {
    61  			virtualPortType = OvsPort
    62  		}
    63  
    64  		nicConfig := createInterfaceInfo(nic, virtualPortType)
    65  
    66  		if nicConfig.InterfaceName == defaultRouteDevice {
    67  			nicConfig.IsDefaultGateway = true
    68  			nicConfig.GatewayAddress = NewMachineAddress(defaultRoute.String()).AsProviderAddress()
    69  		}
    70  
    71  		// Collect all the bridge device names. We will use these to update all
    72  		// the parent device names for the bridge's port devices at the end.
    73  		if nic.Type() == BridgeDevice {
    74  			bridgeNames = append(bridgeNames, nic.Name())
    75  		}
    76  
    77  		nicAddrs, err := nic.Addresses()
    78  		if err != nil {
    79  			return nil, errors.Annotatef(err, "detecting addresses for %q", nic.Name())
    80  		}
    81  
    82  		if len(nicAddrs) > 0 {
    83  			// TODO (manadart 2021-05-07): This preserves prior behaviour,
    84  			// but is incorrect for DHCP configured devices.
    85  			// At present we do not store a config type against the device,
    86  			// only the addresses (which incorrectly default to static too).
    87  			// This could be corrected by interrogating the DHCP leases for
    88  			// the device, should we ever need that detail.
    89  			// At present we do not - we only use it to determine if an address
    90  			// has a configuration method of "loopback".
    91  			if nic.Type() != LoopbackDevice {
    92  				nicConfig.ConfigType = ConfigStatic
    93  			}
    94  
    95  			nicConfig.Addresses, err = addressesToConfig(nicConfig, nicAddrs)
    96  			if err != nil {
    97  				return nil, errors.Trace(err)
    98  			}
    99  		} else {
   100  			noAddressesNics = append(noAddressesNics, nic.Name())
   101  		}
   102  		configs = append(configs, nicConfig)
   103  	}
   104  	if len(noAddressesNics) > 0 {
   105  		logger.Debugf("no addresses observed on interfaces %q", strings.Join(noAddressesNics, ", "))
   106  	}
   107  
   108  	updateParentsForBridgePorts(configs, bridgeNames, source)
   109  	return configs, nil
   110  }
   111  
   112  func createInterfaceInfo(nic ConfigSourceNIC,
   113  	virtualPortType VirtualPortType,
   114  ) InterfaceInfo {
   115  	configType := ConfigManual
   116  	if nic.Type() == LoopbackDevice {
   117  		configType = ConfigLoopback
   118  	}
   119  
   120  	isUp := nic.IsUp()
   121  
   122  	// TODO (dimitern): Add DNS servers and search domains.
   123  	return InterfaceInfo{
   124  		DeviceIndex:     nic.Index(),
   125  		MACAddress:      nic.HardwareAddr().String(),
   126  		ConfigType:      configType,
   127  		MTU:             nic.MTU(),
   128  		InterfaceName:   nic.Name(),
   129  		InterfaceType:   nic.Type(),
   130  		NoAutoStart:     !isUp,
   131  		Disabled:        !isUp,
   132  		VirtualPortType: virtualPortType,
   133  		Origin:          OriginMachine,
   134  	}
   135  }
   136  
   137  func addressesToConfig(nic InterfaceInfo, nicAddrs []ConfigSourceAddr) ([]ProviderAddress, error) {
   138  	var res ProviderAddresses
   139  
   140  	for _, nicAddr := range nicAddrs {
   141  		if nicAddr == nil {
   142  			return nil, errors.Errorf("cannot parse nil address on interface %q", nic.InterfaceName)
   143  		}
   144  
   145  		ip := nicAddr.IP()
   146  
   147  		// TODO (macgreagoir): Skip IPv6 link-local until we decide how to handle them.
   148  		if ip.To4() == nil && ip.IsLinkLocalUnicast() {
   149  			logger.Tracef("skipping observed IPv6 link-local address %q on %q", ip, nic.InterfaceName)
   150  			continue
   151  		}
   152  
   153  		opts := []func(mutator AddressMutator){
   154  			WithConfigType(nic.ConfigType),
   155  			WithSecondary(nicAddr.IsSecondary()),
   156  		}
   157  
   158  		if ipNet := nicAddr.IPNet(); ipNet != nil && ipNet.Mask != nil {
   159  			opts = append(opts, WithCIDR(NetworkCIDRFromIPAndMask(ip, ipNet.Mask)))
   160  		}
   161  
   162  		// Constructing a core Address like this first,
   163  		// then converting, populates the scope and type.
   164  		res = append(res, NewMachineAddress(ip.String(), opts...).AsProviderAddress())
   165  	}
   166  
   167  	return res, nil
   168  }
   169  
   170  func updateParentsForBridgePorts(config []InterfaceInfo, bridgeNames []string, source ConfigSource) {
   171  	for _, bridgeName := range bridgeNames {
   172  		for _, portName := range source.GetBridgePorts(bridgeName) {
   173  			for i := range config {
   174  				if config[i].InterfaceName == portName {
   175  					config[i].ParentInterfaceName = bridgeName
   176  					break
   177  				}
   178  			}
   179  		}
   180  	}
   181  }