github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/common/network.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"net"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/network"
    14  )
    15  
    16  var logger = loggo.GetLogger("juju.api.common")
    17  
    18  // NetworkConfigSource defines the necessary calls to obtain the network
    19  // configuration of a machine.
    20  type NetworkConfigSource interface {
    21  	// SysClassNetPath returns the Linux kernel userspace SYSFS path used by
    22  	// this source. DefaultNetworkConfigSource() uses network.SysClassNetPath.
    23  	SysClassNetPath() string
    24  
    25  	// Interfaces returns information about all network interfaces on the
    26  	// machine as []net.Interface.
    27  	Interfaces() ([]net.Interface, error)
    28  
    29  	// InterfaceAddresses returns information about all addresses assigned to
    30  	// the network interface with the given name.
    31  	InterfaceAddresses(name string) ([]net.Addr, error)
    32  
    33  	// DefaultRoute returns the gateway IP address and device name of the
    34  	// default route on the machine. If there is no default route (known),
    35  	// then zero values are returned.
    36  	DefaultRoute() (net.IP, string, error)
    37  }
    38  
    39  type netPackageConfigSource struct{}
    40  
    41  // SysClassNetPath implements NetworkConfigSource.
    42  func (n *netPackageConfigSource) SysClassNetPath() string {
    43  	return network.SysClassNetPath
    44  }
    45  
    46  // Interfaces implements NetworkConfigSource.
    47  func (n *netPackageConfigSource) Interfaces() ([]net.Interface, error) {
    48  	return net.Interfaces()
    49  }
    50  
    51  // InterfaceAddresses implements NetworkConfigSource.
    52  func (n *netPackageConfigSource) InterfaceAddresses(name string) ([]net.Addr, error) {
    53  	iface, err := net.InterfaceByName(name)
    54  	if err != nil {
    55  		return nil, errors.Trace(err)
    56  	}
    57  	return iface.Addrs()
    58  }
    59  
    60  // DefaultRoute implements NetworkConfigSource.
    61  func (n *netPackageConfigSource) DefaultRoute() (net.IP, string, error) {
    62  	return network.GetDefaultRoute()
    63  }
    64  
    65  // DefaultNetworkConfigSource returns a NetworkConfigSource backed by the net
    66  // package, to be used with GetObservedNetworkConfig().
    67  func DefaultNetworkConfigSource() NetworkConfigSource {
    68  	return &netPackageConfigSource{}
    69  }
    70  
    71  // GetObservedNetworkConfig uses the given source to find all available network
    72  // interfaces and their assigned addresses, and returns the result as
    73  // []params.NetworkConfig. In addition to what the source returns, a few
    74  // additional transformations are done:
    75  //
    76  // * On any OS, the state (UP/DOWN) of each interface and the DeviceIndex field,
    77  //   will be correctly populated. Loopback interfaces are also properly detected
    78  //   and will have InterfaceType set LoopbackInterface.
    79  // * On Linux only, the InterfaceType field will be reliably detected for a few
    80  //   types: BondInterface, BridgeInterface, VLAN_8021QInterface.
    81  // * Also on Linux, for interfaces that are discovered to be ports on a bridge,
    82  //   the ParentInterfaceName will be populated with the name of the bridge.
    83  // * ConfigType fields will be set to ConfigManual when no address is detected,
    84  //   or ConfigStatic when it is.
    85  // * TODO: IPv6 link-local addresses will be ignored and treated as empty ATM.
    86  //
    87  // Result entries will be grouped by InterfaceName, in the same order they are
    88  // returned by the given source.
    89  func GetObservedNetworkConfig(source NetworkConfigSource) ([]params.NetworkConfig, error) {
    90  	logger.Tracef("discovering observed machine network config...")
    91  
    92  	interfaces, err := source.Interfaces()
    93  	if err != nil {
    94  		return nil, errors.Annotate(err, "cannot get network interfaces")
    95  	}
    96  	if len(interfaces) == 0 {
    97  		logger.Tracef("no network interfaces")
    98  		return nil, nil
    99  	}
   100  
   101  	defaultRoute, defaultRouteDevice, err := source.DefaultRoute()
   102  	if err != nil {
   103  		return nil, errors.Annotate(err, "cannot get default route")
   104  	}
   105  	var namesOrder []string
   106  	nameToConfigs := make(map[string][]params.NetworkConfig)
   107  	sysClassNetPath := source.SysClassNetPath()
   108  	for _, nic := range interfaces {
   109  		nicType := network.ParseInterfaceType(sysClassNetPath, nic.Name)
   110  		nicConfig := interfaceToNetworkConfig(nic, nicType)
   111  		if nicConfig.InterfaceName == defaultRouteDevice {
   112  			nicConfig.IsDefaultGateway = true
   113  			nicConfig.GatewayAddress = defaultRoute.String()
   114  		}
   115  
   116  		if nicType == network.BridgeInterface {
   117  			updateParentForBridgePorts(nic.Name, sysClassNetPath, nameToConfigs)
   118  		}
   119  
   120  		seenSoFar := false
   121  		if existing, ok := nameToConfigs[nic.Name]; ok {
   122  			nicConfig.ParentInterfaceName = existing[0].ParentInterfaceName
   123  			// If only ParentInterfaceName was set in a previous iteration (e.g.
   124  			// if the bridge appeared before the port), treat the interface as
   125  			// not yet seen.
   126  			seenSoFar = existing[0].InterfaceName != ""
   127  		}
   128  
   129  		if !seenSoFar {
   130  			nameToConfigs[nic.Name] = []params.NetworkConfig(nil)
   131  			namesOrder = append(namesOrder, nic.Name)
   132  		}
   133  
   134  		addrs, err := source.InterfaceAddresses(nic.Name)
   135  		if err != nil {
   136  			return nil, errors.Annotatef(err, "cannot get interface %q addresses", nic.Name)
   137  		}
   138  
   139  		if len(addrs) == 0 {
   140  			logger.Infof("no addresses observed on interface %q", nic.Name)
   141  			nameToConfigs[nic.Name] = append(nameToConfigs[nic.Name], nicConfig)
   142  			continue
   143  		}
   144  
   145  		for _, addr := range addrs {
   146  			addressConfig, err := interfaceAddressToNetworkConfig(nic.Name, nicConfig.ConfigType, addr)
   147  			if err != nil {
   148  				return nil, errors.Trace(err)
   149  			}
   150  
   151  			// Need to copy nicConfig so only the fields relevant for the
   152  			// current address are updated.
   153  			nicConfigCopy := nicConfig
   154  			nicConfigCopy.Address = addressConfig.Address
   155  			nicConfigCopy.CIDR = addressConfig.CIDR
   156  			nicConfigCopy.ConfigType = addressConfig.ConfigType
   157  			nameToConfigs[nic.Name] = append(nameToConfigs[nic.Name], nicConfigCopy)
   158  		}
   159  	}
   160  
   161  	// Return all interfaces configs in input order.
   162  	var observedConfig []params.NetworkConfig
   163  	for _, name := range namesOrder {
   164  		observedConfig = append(observedConfig, nameToConfigs[name]...)
   165  	}
   166  	logger.Tracef("observed network config: %+v", observedConfig)
   167  	return observedConfig, nil
   168  }
   169  
   170  func interfaceToNetworkConfig(nic net.Interface, nicType network.InterfaceType) params.NetworkConfig {
   171  	configType := network.ConfigManual // assume manual initially, until we parse the address.
   172  	isUp := nic.Flags&net.FlagUp > 0
   173  	isLoopback := nic.Flags&net.FlagLoopback > 0
   174  	isUnknown := nicType == network.UnknownInterface
   175  
   176  	switch {
   177  	case isUnknown && isLoopback:
   178  		nicType = network.LoopbackInterface
   179  		configType = network.ConfigLoopback
   180  	case isUnknown:
   181  		nicType = network.EthernetInterface
   182  	}
   183  
   184  	return params.NetworkConfig{
   185  		DeviceIndex:   nic.Index,
   186  		MACAddress:    nic.HardwareAddr.String(),
   187  		ConfigType:    string(configType),
   188  		MTU:           nic.MTU,
   189  		InterfaceName: nic.Name,
   190  		InterfaceType: string(nicType),
   191  		NoAutoStart:   !isUp,
   192  		Disabled:      !isUp,
   193  	}
   194  }
   195  
   196  func updateParentForBridgePorts(bridgeName, sysClassNetPath string, nameToConfigs map[string][]params.NetworkConfig) {
   197  	ports := network.GetBridgePorts(sysClassNetPath, bridgeName)
   198  	for _, portName := range ports {
   199  		portConfigs, ok := nameToConfigs[portName]
   200  		if ok {
   201  			portConfigs[0].ParentInterfaceName = bridgeName
   202  		} else {
   203  			portConfigs = []params.NetworkConfig{{ParentInterfaceName: bridgeName}}
   204  		}
   205  		nameToConfigs[portName] = portConfigs
   206  	}
   207  }
   208  
   209  func interfaceAddressToNetworkConfig(interfaceName, configType string, address net.Addr) (params.NetworkConfig, error) {
   210  	config := params.NetworkConfig{
   211  		ConfigType: configType,
   212  	}
   213  
   214  	cidrAddress := address.String()
   215  	if cidrAddress == "" {
   216  		return config, nil
   217  	}
   218  
   219  	ip, ipNet, err := net.ParseCIDR(cidrAddress)
   220  	if err != nil {
   221  		logger.Tracef("cannot parse %q on interface %q as CIDR, trying as IP address: %v", cidrAddress, interfaceName, err)
   222  		if ip = net.ParseIP(cidrAddress); ip == nil {
   223  			return config, errors.Errorf("cannot parse IP address %q on interface %q", cidrAddress, interfaceName)
   224  		} else {
   225  			ipNet = &net.IPNet{IP: ip}
   226  		}
   227  	}
   228  	if ip.To4() == nil && ip.IsLinkLocalUnicast() {
   229  		// TODO(macgreagoir) IPv6. Skip link-local for now until we decide how to handle them.
   230  		logger.Tracef("skipping observed IPv6 link-local address %q on %q", ip, interfaceName)
   231  		return config, nil
   232  	}
   233  
   234  	if ipNet.Mask != nil {
   235  		config.CIDR = ipNet.String()
   236  	}
   237  	config.Address = ip.String()
   238  	if configType != string(network.ConfigLoopback) {
   239  		config.ConfigType = string(network.ConfigStatic)
   240  	}
   241  
   242  	// TODO(dimitern): Add DNS servers, search domains, and gateway
   243  	// later.
   244  
   245  	return config, nil
   246  }