| package cluster // import "github.com/docker/docker/daemon/cluster" |
| |
| import ( |
| "fmt" |
| "net" |
| ) |
| |
| 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 "", "", fmt.Errorf("could not parse listen address %s", 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 |
| } |
| |
| 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)) |
| } |