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 }