docker/docker

View on GitHub
daemon/cluster/listen_addr.go

Summary

Maintainability
C
1 day
Test Coverage
package cluster // import "github.com/docker/docker/daemon/cluster"

import (
    "fmt"
    "net"
    "strings"
)

const (
    errNoSuchInterface         configError = "no such interface"
    errNoIP                    configError = "could not find the system's IP address"
    errMustSpecifyListenAddr   configError = "must specify a listening address because the address to advertise is not recognized as a system address, and a system's IP address to use could not be uniquely identified"
    errBadNetworkIdentifier    configError = "must specify a valid IP address or interface name"
    errBadListenAddr           configError = "listen address must be an IP address or network interface (with optional port number)"
    errBadAdvertiseAddr        configError = "advertise address must be a non-zero IP address or network interface (with optional port number)"
    errBadDataPathAddr         configError = "data path address must be a non-zero IP address or network interface (without a port number)"
    errBadDefaultAdvertiseAddr configError = "default advertise address must be a non-zero IP address or network interface (without a port number)"
)

func resolveListenAddr(specifiedAddr string) (string, string, error) {
    specifiedHost, specifiedPort, err := net.SplitHostPort(specifiedAddr)
    if err != nil {
        return "", "", configError("could not parse listen address " + specifiedAddr)
    }
    // Does the host component match any of the interface names on the
    // system? If so, use the address from that interface.
    specifiedIP, err := resolveInputIPAddr(specifiedHost, true)
    if err != nil {
        if err == errBadNetworkIdentifier {
            err = errBadListenAddr
        }
        return "", "", err
    }

    return specifiedIP.String(), specifiedPort, nil
}

func (c *Cluster) resolveAdvertiseAddr(advertiseAddr, listenAddrPort string) (string, string, error) {
    // Approach:
    // - If an advertise address is specified, use that. Resolve the
    //   interface's address if an interface was specified in
    //   advertiseAddr. Fill in the port from listenAddrPort if necessary.
    // - If DefaultAdvertiseAddr is not empty, use that with the port from
    //   listenAddrPort. Resolve the interface's address from
    //   if an interface name was specified in DefaultAdvertiseAddr.
    // - Otherwise, try to autodetect the system's address. Use the port in
    //   listenAddrPort with this address if autodetection succeeds.

    if advertiseAddr != "" {
        advertiseHost, advertisePort, err := net.SplitHostPort(advertiseAddr)
        if err != nil {
            // Not a host:port specification
            advertiseHost = advertiseAddr
            advertisePort = listenAddrPort
        }
        // Does the host component match any of the interface names on the
        // system? If so, use the address from that interface.
        advertiseIP, err := resolveInputIPAddr(advertiseHost, false)
        if err != nil {
            if err == errBadNetworkIdentifier {
                err = errBadAdvertiseAddr
            }
            return "", "", err
        }

        return advertiseIP.String(), advertisePort, nil
    }

    if c.config.DefaultAdvertiseAddr != "" {
        // Does the default advertise address component match any of the
        // interface names on the system? If so, use the address from
        // that interface.
        defaultAdvertiseIP, err := resolveInputIPAddr(c.config.DefaultAdvertiseAddr, false)
        if err != nil {
            if err == errBadNetworkIdentifier {
                err = errBadDefaultAdvertiseAddr
            }
            return "", "", err
        }

        return defaultAdvertiseIP.String(), listenAddrPort, nil
    }

    systemAddr, err := c.resolveSystemAddr()
    if err != nil {
        return "", "", err
    }
    return systemAddr.String(), listenAddrPort, nil
}

// validateDefaultAddrPool validates default address pool
// it also strips white space from the string before validation
func validateDefaultAddrPool(defaultAddrPool []string, size uint32) error {
    if defaultAddrPool == nil {
        // defaultAddrPool is not defined
        return nil
    }
    // if size is not set, then we use default value 24
    if size == 0 {
        size = 24
    }
    // We allow max value as 29. We can have 8 IP addresses for max value 29
    // If we allow 30, then we will get only 4 IP addresses. But with latest
    // libnetwork LB scale implementation, we use total of 4 IP addresses for internal use.
    // Hence keeping 29 as max value, we will have 8 IP addresses. This will be
    // smallest subnet that can be used in overlay network.
    if size > 29 {
        return fmt.Errorf("subnet size is out of range: %d", size)
    }
    for i := range defaultAddrPool {
        // trim leading and trailing white spaces
        defaultAddrPool[i] = strings.TrimSpace(defaultAddrPool[i])
        _, b, err := net.ParseCIDR(defaultAddrPool[i])
        if err != nil {
            return fmt.Errorf("invalid base pool %s: %v", defaultAddrPool[i], err)
        }
        ones, _ := b.Mask.Size()
        if size < uint32(ones) {
            return fmt.Errorf("invalid CIDR: %q. Subnet size is too small for pool: %d", defaultAddrPool[i], size)
        }
    }

    return nil
}

// getDataPathPort validates vxlan udp port (data path port) number.
// if no port is set, the default (4789) is returned
// valid port numbers are between 1024 and 49151
func getDataPathPort(portNum uint32) (uint32, error) {
    // if the value comes as 0 by any reason we set it to default value 4789
    if portNum == 0 {
        portNum = 4789
        return portNum, nil
    }
    // IANA procedures for each range in detail
    // The Well Known Ports, aka the System Ports, from 0-1023
    // The Registered Ports, aka the User Ports, from 1024-49151
    // The Dynamic Ports, aka the Private Ports, from 49152-65535
    // So we can allow range between 1024 to 49151
    if portNum < 1024 || portNum > 49151 {
        return 0, fmt.Errorf("Datapath port number is not in valid range (1024-49151) : %d", portNum)
    }
    return portNum, nil
}

func resolveDataPathAddr(dataPathAddr string) (string, error) {
    if dataPathAddr == "" {
        // dataPathAddr is not defined
        return "", nil
    }
    // If a data path flag is specified try to resolve the IP address.
    dataPathIP, err := resolveInputIPAddr(dataPathAddr, false)
    if err != nil {
        if err == errBadNetworkIdentifier {
            err = errBadDataPathAddr
        }
        return "", err
    }
    return dataPathIP.String(), nil
}

func resolveInterfaceAddr(specifiedInterface string) (net.IP, error) {
    // Use a specific interface's IP address.
    intf, err := net.InterfaceByName(specifiedInterface)
    if err != nil {
        return nil, errNoSuchInterface
    }

    addrs, err := intf.Addrs()
    if err != nil {
        return nil, err
    }

    var interfaceAddr4, interfaceAddr6 net.IP

    for _, addr := range addrs {
        ipAddr, ok := addr.(*net.IPNet)

        if ok {
            if ipAddr.IP.To4() != nil {
                // IPv4
                if interfaceAddr4 != nil {
                    return nil, configError(fmt.Sprintf("interface %s has more than one IPv4 address (%s and %s)", specifiedInterface, interfaceAddr4, ipAddr.IP))
                }
                interfaceAddr4 = ipAddr.IP
            } else {
                // IPv6
                if interfaceAddr6 != nil {
                    return nil, configError(fmt.Sprintf("interface %s has more than one IPv6 address (%s and %s)", specifiedInterface, interfaceAddr6, ipAddr.IP))
                }
                interfaceAddr6 = ipAddr.IP
            }
        }
    }

    if interfaceAddr4 == nil && interfaceAddr6 == nil {
        return nil, configError(fmt.Sprintf("interface %s has no usable IPv4 or IPv6 address", specifiedInterface))
    }

    // In the case that there's exactly one IPv4 address
    // and exactly one IPv6 address, favor IPv4 over IPv6.
    if interfaceAddr4 != nil {
        return interfaceAddr4, nil
    }
    return interfaceAddr6, nil
}

// resolveInputIPAddr tries to resolve the IP address from the string passed as input
//   - tries to match the string as an interface name, if so returns the IP address associated with it
//   - on failure of previous step tries to parse the string as an IP address itself
//     if succeeds returns the IP address
func resolveInputIPAddr(input string, isUnspecifiedValid bool) (net.IP, error) {
    // Try to see if it is an interface name
    interfaceAddr, err := resolveInterfaceAddr(input)
    if err == nil {
        return interfaceAddr, nil
    }
    // String matched interface but there is a potential ambiguity to be resolved
    if err != errNoSuchInterface {
        return nil, err
    }

    // String is not an interface check if it is a valid IP
    if ip := net.ParseIP(input); ip != nil && (isUnspecifiedValid || !ip.IsUnspecified()) {
        return ip, nil
    }

    // Not valid IP found
    return nil, errBadNetworkIdentifier
}

func (c *Cluster) resolveSystemAddrViaSubnetCheck() (net.IP, error) {
    // Use the system's only IP address, or fail if there are
    // multiple addresses to choose from. Skip interfaces which
    // are managed by docker via subnet check.
    interfaces, err := net.Interfaces()
    if err != nil {
        return nil, err
    }

    var systemAddr net.IP
    var systemInterface string

    // List Docker-managed subnets
    v4Subnets, v6Subnets := c.config.NetworkSubnetsProvider.Subnets()

ifaceLoop:
    for _, intf := range interfaces {
        // Skip inactive interfaces and loopback interfaces
        if (intf.Flags&net.FlagUp == 0) || (intf.Flags&net.FlagLoopback) != 0 {
            continue
        }

        addrs, err := intf.Addrs()
        if err != nil {
            continue
        }

        var interfaceAddr4, interfaceAddr6 net.IP

        for _, addr := range addrs {
            ipAddr, ok := addr.(*net.IPNet)

            // Skip loopback and link-local addresses
            if !ok || !ipAddr.IP.IsGlobalUnicast() {
                continue
            }

            if ipAddr.IP.To4() != nil {
                // IPv4

                // Ignore addresses in subnets that are managed by Docker.
                for _, subnet := range v4Subnets {
                    if subnet.Contains(ipAddr.IP) {
                        continue ifaceLoop
                    }
                }

                if interfaceAddr4 != nil {
                    return nil, errMultipleIPs(intf.Name, intf.Name, interfaceAddr4, ipAddr.IP)
                }

                interfaceAddr4 = ipAddr.IP
            } else {
                // IPv6

                // Ignore addresses in subnets that are managed by Docker.
                for _, subnet := range v6Subnets {
                    if subnet.Contains(ipAddr.IP) {
                        continue ifaceLoop
                    }
                }

                if interfaceAddr6 != nil {
                    return nil, errMultipleIPs(intf.Name, intf.Name, interfaceAddr6, ipAddr.IP)
                }

                interfaceAddr6 = ipAddr.IP
            }
        }

        // In the case that this interface has exactly one IPv4 address
        // and exactly one IPv6 address, favor IPv4 over IPv6.
        if interfaceAddr4 != nil {
            if systemAddr != nil {
                return nil, errMultipleIPs(systemInterface, intf.Name, systemAddr, interfaceAddr4)
            }
            systemAddr = interfaceAddr4
            systemInterface = intf.Name
        } else if interfaceAddr6 != nil {
            if systemAddr != nil {
                return nil, errMultipleIPs(systemInterface, intf.Name, systemAddr, interfaceAddr6)
            }
            systemAddr = interfaceAddr6
            systemInterface = intf.Name
        }
    }

    if systemAddr == nil {
        return nil, errNoIP
    }

    return systemAddr, nil
}

func listSystemIPs() []net.IP {
    interfaces, err := net.Interfaces()
    if err != nil {
        return nil
    }

    var systemAddrs []net.IP

    for _, intf := range interfaces {
        addrs, err := intf.Addrs()
        if err != nil {
            continue
        }

        for _, addr := range addrs {
            ipAddr, ok := addr.(*net.IPNet)

            if ok {
                systemAddrs = append(systemAddrs, ipAddr.IP)
            }
        }
    }

    return systemAddrs
}

func errMultipleIPs(interfaceA, interfaceB string, addrA, addrB net.IP) error {
    if interfaceA == interfaceB {
        return configError(fmt.Sprintf("could not choose an IP address to advertise since this system has multiple addresses on interface %s (%s and %s)", interfaceA, addrA, addrB))
    }
    return configError(fmt.Sprintf("could not choose an IP address to advertise since this system has multiple addresses on different interfaces (%s on %s and %s on %s)", addrA, interfaceA, addrB, interfaceB))
}