Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2014 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package network | |
| import ( | |
| "bufio" | |
| "fmt" | |
| "net" | |
| "os" | |
| "regexp" | |
| "sort" | |
| "strings" | |
| "github.com/juju/errors" | |
| "github.com/juju/loggo" | |
| "github.com/juju/utils/set" | |
| ) | |
| var logger = loggo.GetLogger("juju.network") | |
| // SpaceInvalidChars is a regexp for validating that space names contain no | |
| // invalid characters. | |
| var SpaceInvalidChars = regexp.MustCompile("[^0-9a-z-]") | |
| // noAddress represents an error when an address is requested but not available. | |
| type noAddress struct { | |
| errors.Err | |
| } | |
| // NoAddressError returns an error which satisfies IsNoAddressError(). The given | |
| // addressKind specifies what kind of address(es) is(are) missing, usually | |
| // "private" or "public". | |
| func NoAddressError(addressKind string) error { | |
| newErr := errors.NewErr("no %s address(es)", addressKind) | |
| newErr.SetLocation(1) | |
| return &noAddress{newErr} | |
| } | |
| // IsNoAddressError reports whether err was created with NoAddressError(). | |
| func IsNoAddressError(err error) bool { | |
| err = errors.Cause(err) | |
| _, ok := err.(*noAddress) | |
| return ok | |
| } | |
| // Id defines a provider-specific network id. | |
| type Id string | |
| // AnySubnet when passed as a subnet id should be interpreted by the | |
| // providers as "the subnet id does not matter". It's up to the | |
| // provider how to handle this case - it might return an error. | |
| const AnySubnet Id = "" | |
| // UnknownId can be used whenever an Id is needed but not known. | |
| const UnknownId = "" | |
| // DefaultLXCBridge is the bridge that gets used for LXC containers | |
| const DefaultLXCBridge = "lxcbr0" | |
| // DefaultLXDBridge is the bridge that gets used for LXD containers | |
| const DefaultLXDBridge = "lxdbr0" | |
| // DefaultKVMBridge is the bridge that is set up by installing libvirt-bin | |
| // Note: we don't import this from 'container' to avoid import loops | |
| const DefaultKVMBridge = "virbr0" | |
| var dashPrefix = regexp.MustCompile("^-*") | |
| var dashSuffix = regexp.MustCompile("-*$") | |
| var multipleDashes = regexp.MustCompile("--+") | |
| // ConvertSpaceName converts names between provider space names and valid juju | |
| // space names. | |
| // TODO(mfoord): once MAAS space name rules are in sync with juju space name | |
| // rules this can go away. | |
| func ConvertSpaceName(name string, existing set.Strings) string { | |
| // First lower case and replace spaces with dashes. | |
| name = strings.Replace(name, " ", "-", -1) | |
| name = strings.ToLower(name) | |
| // Replace any character that isn't in the set "-", "a-z", "0-9". | |
| name = SpaceInvalidChars.ReplaceAllString(name, "") | |
| // Get rid of any dashes at the start as that isn't valid. | |
| name = dashPrefix.ReplaceAllString(name, "") | |
| // And any at the end. | |
| name = dashSuffix.ReplaceAllString(name, "") | |
| // Repleace multiple dashes with a single dash. | |
| name = multipleDashes.ReplaceAllString(name, "-") | |
| // Special case of when the space name was only dashes or invalid | |
| // characters! | |
| if name == "" { | |
| name = "empty" | |
| } | |
| // If this name is in use add a numerical suffix. | |
| if existing.Contains(name) { | |
| counter := 2 | |
| for existing.Contains(name + fmt.Sprintf("-%d", counter)) { | |
| counter += 1 | |
| } | |
| name = name + fmt.Sprintf("-%d", counter) | |
| } | |
| return name | |
| } | |
| // SubnetInfo describes the bare minimum information for a subnet, | |
| // which the provider knows about but juju might not yet. | |
| type SubnetInfo struct { | |
| // CIDR of the network, in 123.45.67.89/24 format. Can be empty if | |
| // unknown. | |
| CIDR string | |
| // ProviderId is a provider-specific network id. This the only | |
| // required field. | |
| ProviderId Id | |
| // VLANTag needs to be between 1 and 4094 for VLANs and 0 for | |
| // normal networks. It's defined by IEEE 802.1Q standard, and used | |
| // to define a VLAN network. For more information, see: | |
| // http://en.wikipedia.org/wiki/IEEE_802.1Q. | |
| VLANTag int | |
| // AvailabilityZones describes which availability zone(s) this | |
| // subnet is in. It can be empty if the provider does not support | |
| // availability zones. | |
| AvailabilityZones []string | |
| // SpaceProviderId holds the provider Id of the space associated with | |
| // this subnet. Can be empty if not supported. | |
| SpaceProviderId Id | |
| } | |
| type SpaceInfo struct { | |
| Name string | |
| ProviderId Id | |
| Subnets []SubnetInfo | |
| } | |
| type BySpaceName []SpaceInfo | |
| func (s BySpaceName) Len() int { return len(s) } | |
| func (s BySpaceName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
| func (s BySpaceName) Less(i, j int) bool { | |
| return s[i].Name < s[j].Name | |
| } | |
| // InterfaceConfigType defines valid network interface configuration | |
| // types. See interfaces(5) for details | |
| type InterfaceConfigType string | |
| const ( | |
| ConfigUnknown InterfaceConfigType = "" | |
| ConfigDHCP InterfaceConfigType = "dhcp" | |
| ConfigStatic InterfaceConfigType = "static" | |
| ConfigManual InterfaceConfigType = "manual" | |
| ConfigLoopback InterfaceConfigType = "loopback" | |
| ) | |
| // InterfaceType defines valid network interface types. | |
| type InterfaceType string | |
| const ( | |
| UnknownInterface InterfaceType = "" | |
| LoopbackInterface InterfaceType = "loopback" | |
| EthernetInterface InterfaceType = "ethernet" | |
| VLAN_8021QInterface InterfaceType = "802.1q" | |
| BondInterface InterfaceType = "bond" | |
| BridgeInterface InterfaceType = "bridge" | |
| ) | |
| // InterfaceInfo describes a single network interface available on an | |
| // instance. For providers that support networks, this will be | |
| // available at StartInstance() time. | |
| // TODO(mue): Rename to InterfaceConfig due to consistency later. | |
| type InterfaceInfo struct { | |
| // DeviceIndex specifies the order in which the network interface | |
| // appears on the host. The primary interface has an index of 0. | |
| DeviceIndex int | |
| // MACAddress is the network interface's hardware MAC address | |
| // (e.g. "aa:bb:cc:dd:ee:ff"). | |
| MACAddress string | |
| // CIDR of the network, in 123.45.67.89/24 format. | |
| CIDR string | |
| // ProviderId is a provider-specific NIC id. | |
| ProviderId Id | |
| // ProviderSubnetId is the provider-specific id for the associated | |
| // subnet. | |
| ProviderSubnetId Id | |
| // ProviderSpaceId is the provider-specific id for the associated space, if | |
| // known and supported. | |
| ProviderSpaceId Id | |
| // ProviderVLANId is the provider-specific id of the VLAN for this | |
| // interface. | |
| ProviderVLANId Id | |
| // ProviderAddressId is the provider-specific id of the assigned address. | |
| ProviderAddressId Id | |
| // AvailabilityZones describes the availability zones the associated | |
| // subnet is in. | |
| AvailabilityZones []string | |
| // VLANTag needs to be between 1 and 4094 for VLANs and 0 for | |
| // normal networks. It's defined by IEEE 802.1Q standard. | |
| VLANTag int | |
| // InterfaceName is the raw OS-specific network device name (e.g. | |
| // "eth1", even for a VLAN eth1.42 virtual interface). | |
| InterfaceName string | |
| // ParentInterfaceName is the name of the parent interface to use, if known. | |
| ParentInterfaceName string | |
| // InterfaceType is the type of the interface. | |
| InterfaceType InterfaceType | |
| // Disabled is true when the interface needs to be disabled on the | |
| // machine, e.g. not to configure it. | |
| Disabled bool | |
| // NoAutoStart is true when the interface should not be configured | |
| // to start automatically on boot. By default and for | |
| // backwards-compatibility, interfaces are configured to | |
| // auto-start. | |
| NoAutoStart bool | |
| // ConfigType determines whether the interface should be | |
| // configured via DHCP, statically, manually, etc. See | |
| // interfaces(5) for more information. | |
| ConfigType InterfaceConfigType | |
| // Address contains an optional static IP address to configure for | |
| // this network interface. The subnet mask to set will be inferred | |
| // from the CIDR value. | |
| Address Address | |
| // DNSServers contains an optional list of IP addresses and/or | |
| // hostnames to configure as DNS servers for this network | |
| // interface. | |
| DNSServers []Address | |
| // MTU is the Maximum Transmission Unit controlling the maximum size of the | |
| // protocol packats that the interface can pass through. It is only used | |
| // when > 0. | |
| MTU int | |
| // DNSSearchDomains contains the default DNS domain to use for non-FQDN | |
| // lookups. | |
| DNSSearchDomains []string | |
| // Gateway address, if set, defines the default gateway to | |
| // configure for this network interface. For containers this | |
| // usually is (one of) the host address(es). | |
| GatewayAddress Address | |
| // Routes defines a list of routes that should be added when this interface | |
| // is brought up, and removed when this interface is stopped. | |
| Routes []Route | |
| } | |
| // Route defines a single route to a subnet via a defined gateway. | |
| type Route struct { | |
| // DestinationCIDR is the subnet that we want a controlled route to. | |
| DestinationCIDR string | |
| // GatewayIP is the IP (v4 or v6) that should be used for traffic that is | |
| // bound for DestinationCIDR | |
| GatewayIP string | |
| // Metric is the weight to apply to this route. | |
| Metric int | |
| } | |
| // Validate that this Route is properly formed. | |
| func (r Route) Validate() error { | |
| // Make sure the CIDR is actually a CIDR not just an IP or hostname | |
| destinationIP, _, err := net.ParseCIDR(r.DestinationCIDR) | |
| if err != nil { | |
| return errors.Annotate(err, "DestinationCIDR not valid") | |
| } | |
| // Make sure the Gateway is just an IP, not a CIDR, etc. | |
| gatewayIP := net.ParseIP(r.GatewayIP) | |
| if gatewayIP == nil { | |
| return errors.Errorf("GatewayIP is not a valid IP address: %q", r.GatewayIP) | |
| } | |
| if r.Metric < 0 { | |
| return errors.Errorf("Metric is negative: %d", r.Metric) | |
| } | |
| // Make sure that either both are IPv4 or both are IPv6, not mixed. | |
| destIP4 := destinationIP.To4() | |
| gatewayIP4 := gatewayIP.To4() | |
| if destIP4 != nil { | |
| if gatewayIP4 == nil { | |
| return errors.Errorf("DestinationCIDR is IPv4 (%s) but GatewayIP is IPv6 (%s)", r.DestinationCIDR, r.GatewayIP) | |
| } | |
| } else { | |
| if gatewayIP4 != nil { | |
| return errors.Errorf("DestinationCIDR is IPv6 (%s) but GatewayIP is IPv4 (%s)", r.DestinationCIDR, r.GatewayIP) | |
| } | |
| } | |
| return nil | |
| } | |
| type interfaceInfoSlice []InterfaceInfo | |
| func (s interfaceInfoSlice) Len() int { return len(s) } | |
| func (s interfaceInfoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
| func (s interfaceInfoSlice) Less(i, j int) bool { | |
| iface1 := s[i] | |
| iface2 := s[j] | |
| return iface1.DeviceIndex < iface2.DeviceIndex | |
| } | |
| // SortInterfaceInfo sorts a slice of InterfaceInfo on DeviceIndex in ascending | |
| // order. | |
| func SortInterfaceInfo(interfaces []InterfaceInfo) { | |
| sort.Sort(interfaceInfoSlice(interfaces)) | |
| } | |
| // ActualInterfaceName returns raw interface name for raw interface (e.g. "eth0") and | |
| // virtual interface name for virtual interface (e.g. "eth0.42") | |
| func (i *InterfaceInfo) ActualInterfaceName() string { | |
| if i.VLANTag > 0 { | |
| return fmt.Sprintf("%s.%d", i.InterfaceName, i.VLANTag) | |
| } | |
| return i.InterfaceName | |
| } | |
| // IsVirtual returns true when the interface is a virtual device, as | |
| // opposed to a physical device (e.g. a VLAN or a network alias) | |
| func (i *InterfaceInfo) IsVirtual() bool { | |
| return i.VLANTag > 0 | |
| } | |
| // IsVLAN returns true when the interface is a VLAN interface. | |
| func (i *InterfaceInfo) IsVLAN() bool { | |
| return i.VLANTag > 0 | |
| } | |
| // CIDRAddress returns Address.Value combined with CIDR mask. | |
| func (i *InterfaceInfo) CIDRAddress() string { | |
| if i.CIDR == "" || i.Address.Value == "" { | |
| return "" | |
| } | |
| _, ipNet, err := net.ParseCIDR(i.CIDR) | |
| if err != nil { | |
| return errors.Trace(err).Error() | |
| } | |
| ip := net.ParseIP(i.Address.Value) | |
| if ip == nil { | |
| return errors.Errorf("cannot parse IP address %q", i.Address.Value).Error() | |
| } | |
| ipNet.IP = ip | |
| return ipNet.String() | |
| } | |
| // ProviderInterfaceInfo holds enough information to identify an | |
| // interface or link layer device to a provider so that it can be | |
| // queried or manipulated. Its initial purpose is to pass to | |
| // provider.ReleaseContainerAddresses. | |
| type ProviderInterfaceInfo struct { | |
| // InterfaceName is the raw OS-specific network device name (e.g. | |
| // "eth1", even for a VLAN eth1.42 virtual interface). | |
| InterfaceName string | |
| // ProviderId is a provider-specific NIC id. | |
| ProviderId Id | |
| // MACAddress is the network interface's hardware MAC address | |
| // (e.g. "aa:bb:cc:dd:ee:ff"). | |
| MACAddress string | |
| } | |
| // DeviceToBridge gives the information about a particular device that | |
| // should be bridged. | |
| type DeviceToBridge struct { | |
| // DeviceName is the name of the device on the machine that should | |
| // be bridged. | |
| DeviceName string | |
| // BridgeName is the name of the bridge that we want created. | |
| BridgeName string | |
| } | |
| // LXCNetDefaultConfig is the location of the default network config | |
| // of the lxc package. It's exported to allow cross-package testing. | |
| var LXCNetDefaultConfig = "/etc/default/lxc-net" | |
| // InterfaceByNameAddrs returns the addresses for the given interface | |
| // name. It's exported to facilitate cross-package testing. | |
| var InterfaceByNameAddrs = func(name string) ([]net.Addr, error) { | |
| iface, err := net.InterfaceByName(name) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return iface.Addrs() | |
| } | |
| type ipNetAndName struct { | |
| ipnet *net.IPNet | |
| name string | |
| } | |
| func addrMapToIPNetAndName(bridgeToAddrs map[string][]net.Addr) []ipNetAndName { | |
| ipNets := make([]ipNetAndName, 0, len(bridgeToAddrs)) | |
| for bridgeName, addrList := range bridgeToAddrs { | |
| for _, ifaceAddr := range addrList { | |
| ip, ipNet, err := net.ParseCIDR(ifaceAddr.String()) | |
| if err != nil { | |
| // Not a valid CIDR, check as an IP | |
| ip = net.ParseIP(ifaceAddr.String()) | |
| } | |
| if ip == nil { | |
| logger.Debugf("cannot parse %q as IP, ignoring", ifaceAddr) | |
| continue | |
| } | |
| if ipNet == nil { | |
| // convert the IP into an IPNet | |
| if ip.To4() != nil { | |
| _, ipNet, err = net.ParseCIDR(ip.String() + "/32") | |
| if err != nil { | |
| logger.Debugf("error creating a /32 CIDR for %q", ifaceAddr) | |
| } | |
| } else if ip.To16() != nil { | |
| _, ipNet, err = net.ParseCIDR(ip.String() + "/128") | |
| if err != nil { | |
| logger.Debugf("error creating a /128 CIDR for %q", ifaceAddr) | |
| } | |
| } else { | |
| logger.Debugf("failed to convert %q to a v4 or v6 address, ignoring", ifaceAddr) | |
| } | |
| } | |
| ipNets = append(ipNets, ipNetAndName{ipnet: ipNet, name: bridgeName}) | |
| } | |
| } | |
| return ipNets | |
| } | |
| // filterAddrs looks at all of the addresses in allAddresses and removes ones | |
| // that line up with removeAddresses. Note that net.Addr may be just an IP or | |
| // may be a CIDR. removeAddresses should be a map of 'bridge name' to list of | |
| // addresses, so that we can report why the address was filtered. | |
| func filterAddrs(allAddresses []Address, removeAddresses map[string][]net.Addr) []Address { | |
| filtered := make([]Address, 0, len(allAddresses)) | |
| // Convert all | |
| ipNets := addrMapToIPNetAndName(removeAddresses) | |
| for _, addr := range allAddresses { | |
| bridgeName := "" | |
| // Then check if it is in one of the CIDRs | |
| ip := net.ParseIP(addr.Value) | |
| if ip == nil { | |
| logger.Debugf("not filtering invalid IP: %q", addr.Value) | |
| } else { | |
| for _, ipNetName := range ipNets { | |
| if ipNetName.ipnet.Contains(ip) { | |
| bridgeName = ipNetName.name | |
| break | |
| } | |
| } | |
| } | |
| if bridgeName == "" { | |
| logger.Debugf("including address %v for machine", addr) | |
| filtered = append(filtered, addr) | |
| } else { | |
| logger.Debugf("filtering %q address %s for machine", bridgeName, addr.String()) | |
| } | |
| } | |
| return filtered | |
| } | |
| // gatherLXCAddresses tries to discover the default lxc bridge name | |
| // and all of its addresses. See LP bug #1416928. | |
| func gatherLXCAddresses(toRemove map[string][]net.Addr) { | |
| file, err := os.Open(LXCNetDefaultConfig) | |
| if os.IsNotExist(err) { | |
| // No lxc-net config found, nothing to do. | |
| logger.Debugf("no lxc bridge addresses to filter for machine") | |
| return | |
| } else if err != nil { | |
| // Just log it, as it's not fatal. | |
| logger.Errorf("cannot open %q: %v", LXCNetDefaultConfig, err) | |
| return | |
| } | |
| defer file.Close() | |
| scanner := bufio.NewScanner(file) | |
| for scanner.Scan() { | |
| line := strings.TrimSpace(scanner.Text()) | |
| switch { | |
| case strings.HasPrefix(line, "#"): | |
| // Skip comments. | |
| case strings.HasPrefix(line, "LXC_BRIDGE"): | |
| // Extract <name> from LXC_BRIDGE="<name>". | |
| parts := strings.Split(line, `"`) | |
| if len(parts) < 2 { | |
| logger.Debugf("ignoring invalid line '%s' in %q", line, LXCNetDefaultConfig) | |
| continue | |
| } | |
| bridgeName := strings.TrimSpace(parts[1]) | |
| gatherBridgeAddresses(bridgeName, toRemove) | |
| return | |
| } | |
| } | |
| if err := scanner.Err(); err != nil { | |
| logger.Debugf("failed to read %q: %v (ignoring)", LXCNetDefaultConfig, err) | |
| } | |
| return | |
| } | |
| func gatherBridgeAddresses(bridgeName string, toRemove map[string][]net.Addr) { | |
| addrs, err := InterfaceByNameAddrs(bridgeName) | |
| if err != nil { | |
| logger.Debugf("cannot get %q addresses: %v (ignoring)", bridgeName, err) | |
| return | |
| } | |
| logger.Debugf("%q has addresses %v", bridgeName, addrs) | |
| toRemove[bridgeName] = addrs | |
| return | |
| } | |
| // FilterBridgeAddresses removes addresses seen as a Bridge address (the IP | |
| // address used only to connect to local containers), rather than a remote | |
| // accessible address. | |
| func FilterBridgeAddresses(addresses []Address) []Address { | |
| addressesToRemove := make(map[string][]net.Addr) | |
| gatherLXCAddresses(addressesToRemove) | |
| gatherBridgeAddresses(DefaultLXDBridge, addressesToRemove) | |
| gatherBridgeAddresses(DefaultKVMBridge, addressesToRemove) | |
| filtered := filterAddrs(addresses, addressesToRemove) | |
| logger.Debugf("addresses after filtering: %v", filtered) | |
| return filtered | |
| } | |
| // QuoteSpaces takes a slice of space names, and returns a nicely formatted | |
| // form so they show up legible in log messages, etc. | |
| func QuoteSpaces(vals []string) string { | |
| out := []string{} | |
| if len(vals) == 0 { | |
| return "<none>" | |
| } | |
| for _, space := range vals { | |
| out = append(out, fmt.Sprintf("%q", space)) | |
| } | |
| return strings.Join(out, ", ") | |
| } | |
| // QuoteSpaceSet is the same as QuoteSpaces, but ensures that a set.Strings | |
| // gets sorted values output. | |
| func QuoteSpaceSet(vals set.Strings) string { | |
| return QuoteSpaces(vals.SortedValues()) | |
| } |