diff --git a/AUTHORS b/AUTHORS index c39c9f009cdb7..cc2a268af6f1a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Andreas Tiefenthaler Andrew Duckworth Andrew Macgregor Andrew Munsell +Andrew Williams Andrews Medina Andy Chambers andy diller diff --git a/container.go b/container.go index 9c5f468c5b873..13f42d7bf07eb 100644 --- a/container.go +++ b/container.go @@ -1,7 +1,6 @@ package docker import ( - "bytes" "encoding/json" "errors" "fmt" @@ -563,11 +562,14 @@ func populateCommand(c *Container) { if !c.Config.NetworkDisabled { network := c.NetworkSettings en = &execdriver.Network{ - Gateway: network.Gateway, - Bridge: network.Bridge, - IPAddress: network.IPAddress, - IPPrefixLen: network.IPPrefixLen, - Mtu: c.runtime.config.Mtu, + Gateway: network.Gateway, + Gateway6: network.Gateway6, + IPAddress: network.IPAddress, + IPAddress6: network.IPAddress6, + IPPrefixLen: network.IPPrefixLen, + IPPrefixLen6: network.IPPrefixLen6, + Bridge: network.Bridge, + Mtu: c.runtime.config.Mtu, } } @@ -640,6 +642,10 @@ func (container *Container) Start() (err error) { log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") } + if container.runtime.sysInfo.IPv6ForwardingDisabled { + log.Printf("WARNING: IPv6 forwarding is disabled. IPv6 networking will not work") + } + if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) container.VolumesRW = make(map[string]bool) @@ -1118,11 +1124,13 @@ func (container *Container) allocateNetwork() error { if container.runtime.config.DisableNetwork { env = &engine.Env{} } else { - currentIP := container.NetworkSettings.IPAddress + currentIP := container.NetworkSettings.IPAddress + currentIP6 := container.NetworkSettings.IPAddress6 job := eng.Job("allocate_interface", container.ID) if currentIP != "" { job.Setenv("RequestIP", currentIP) + job.Setenv("RequestIP6", currentIP6) } env, err = job.Stdout.AddEnv() @@ -1217,8 +1225,8 @@ func (container *Container) allocateNetwork() error { container.NetworkSettings.IPAddress = env.Get("IP") container.NetworkSettings.IPAddress6 = env.Get("IP6") container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen") - container.NetworkSettings.IPPrefixLen6 = env.GetInt("IPPrefixLen6") - container.NetworkSettings.Gateway = env.Get("Gateway") + container.NetworkSettings.IPPrefixLen6 = env.GetInt("IPPrefixLen6") + container.NetworkSettings.Gateway = env.Get("Gateway") container.NetworkSettings.Gateway6 = env.Get("Gateway6") return nil diff --git a/execdriver/driver.go b/execdriver/driver.go index 1ea086075d89d..281c9451c2a51 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -42,7 +42,9 @@ func GetInitFunc(name string) (InitFunc, error) { type InitArgs struct { User string Gateway string + Gateway6 string Ip string + Ip6 string WorkDir string Privileged bool Env []string @@ -68,11 +70,14 @@ type Driver interface { // Network settings of the container type Network struct { - Gateway string `json:"gateway"` - IPAddress string `json:"ip"` - Bridge string `json:"bridge"` - IPPrefixLen int `json:"ip_prefix_len"` - Mtu int `json:"mtu"` + Gateway string `json:"gateway"` + Gateway6 string `json:"gateway6"` + IPAddress string `json:"ip"` + IPAddress6 string `json:"ip6"` + IPPrefixLen int `json:"ip_prefix_len"` + IPPrefixLen6 int `json:"ip_prefix_len6"` + Bridge string `json:"bridge"` + Mtu int `json:"mtu"` } type Resources struct { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 4c3979e7185da..899cdb8760ec1 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -94,7 +94,9 @@ func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallba if c.Network != nil { params = append(params, "-g", c.Network.Gateway, + "-g6", c.Network.Gateway6, "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), + "-i6", fmt.Sprintf("%s/%d", c.Network.IPAddress6, c.Network.IPPrefixLen6), "-mtu", strconv.Itoa(c.Network.Mtu), ) } diff --git a/execdriver/lxc/init.go b/execdriver/lxc/init.go index 7c2b039c50fc6..f2856ad57801c 100644 --- a/execdriver/lxc/init.go +++ b/execdriver/lxc/init.go @@ -23,19 +23,35 @@ func setupHostname(args *execdriver.InitArgs) error { // Setup networking func setupNetworking(args *execdriver.InitArgs) error { - if args.Ip != "" { + if args.Ip != "" || args.Ip6 != "" { // eth0 iface, err := net.InterfaceByName("eth0") if err != nil { return fmt.Errorf("Unable to set up networking: %v", err) } - ip, ipNet, err := net.ParseCIDR(args.Ip) - if err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) + + // IPv4 + if args.Ip != "" { + ip, ipNet, err := net.ParseCIDR(args.Ip) + if err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + if err := netlink.NetworkLinkAddIp(iface, ip, ipNet); err != nil { + return fmt.Errorf("Unable to set up IPv4 networking: %v", err) + } } - if err := netlink.NetworkLinkAddIp(iface, ip, ipNet); err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) + + // IPv6 + if args.Ip6 != "" { + ip, ipNet, err := net.ParseCIDR(args.Ip6) + if err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + if err := netlink.NetworkLinkAddIp(iface, ip, ipNet); err != nil { + return fmt.Errorf("Unable to set up IPv6 networking: %v", err) + } } + if err := netlink.NetworkSetMTU(iface, args.Mtu); err != nil { return fmt.Errorf("Unable to set MTU: %v", err) } @@ -62,6 +78,16 @@ func setupNetworking(args *execdriver.InitArgs) error { return fmt.Errorf("Unable to set up networking: %v", err) } } + if args.Gateway6 != "" { + gw := net.ParseIP(args.Gateway6) + if gw == nil { + return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.Gateway) + } + + if err := netlink.AddDefaultGw(gw); err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + } return nil } diff --git a/network.go b/network.go deleted file mode 100644 index f02e2804624ff..0000000000000 --- a/network.go +++ /dev/null @@ -1,996 +0,0 @@ -package docker - -import ( - "encoding/binary" - "errors" - "fmt" - "github.com/dotcloud/docker/iptables" - "github.com/dotcloud/docker/netlink" - "github.com/dotcloud/docker/proxy" - "github.com/dotcloud/docker/utils" - "log" - "net" - "strconv" - "sync" -) - -const ( - DefaultNetworkBridge = "docker0" - DisableNetworkBridge = "none" - portRangeStart = 49153 - portRangeEnd = 65535 -) - -// Calculates the first and last IP addresses in an IPNet -func networkRange(network *net.IPNet) (net.IP, net.IP) { - netIP := network.IP - firstIP := netIP.Mask(network.Mask) - var lastIP net.IP - if ip4 := netIP.To4(); ip4 != nil { - lastIP = net.IPv4zero.To4() - } else { - lastIP = net.IP(make([]byte, 16)) - } - for i := 0; i < len(lastIP); i++ { - lastIP[i] = netIP[i] | ^network.Mask[i] - } - return firstIP, lastIP -} - -// Detects overlap between one IPNet and another -func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { - firstIP, _ := networkRange(netX) - if netY.Contains(firstIP) { - return true - } - firstIP, _ = networkRange(netY) - if netX.Contains(firstIP) { - return true - } - return false -} - -// Converts a 16 byte IP into a 32 bit integer -func ipToInt(ip net.IP) int32 { - return int32(binary.BigEndian.Uint32(ip.To4())) -} - -// Converts a 16 byte IP into two 64-bit integers -func ip6ToInt(ip net.IP) (uint64, uint64) { - b := make([]byte, 8) - b2 := make([]byte, 8) - for i := 0; i < len(b); i++ { - n := i + 8 - b[i] = ip[i] - b2[i] = ip[n] - } - return binary.BigEndian.Uint64(b), binary.BigEndian.Uint64(b2) -} - -// Converts a 32 bit integer into a 4 byte IP address -func intToIP(n int32) net.IP { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, uint32(n)) - return net.IP(b) -} - -// Converts 2 64 bit integers into a 16 byte IP address -func intToIP6(n1 uint64, n2 uint64) net.IP { - b := make([]byte, 8) - b2 := make([]byte, 8) - ip := net.IP(make([]byte, 16)) - - binary.BigEndian.PutUint64(b, n1) - binary.BigEndian.PutUint64(b2, n2) - - for i := 0; i < len(b); i++ { - n := i + 8 - ip[i] = b[i] - ip[n] = b2[i] - } - return net.IP(ip) -} - -// Given a netmask, calculates the number of available hosts -func networkSize(mask net.IPMask) int32 { - m := net.IPMask(net.IPv4zero.To4()) - for i := 0; i < net.IPv4len; i++ { - m[i] = ^mask[i] - } - return int32(binary.BigEndian.Uint32(m)) + 1 -} - -func networkSize6(mask net.IPMask) (uint64, uint64) { - m := net.IPMask([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) - - for i := 0; i < net.IPv6len; i++ { - m[i] = ^mask[i] - } - - m1 := make([]byte, 8) - m2 := make([]byte, 8) - for i := 0; i < len(m1); i++ { - n := i + 8 - m1[i] = m[i] - m2[i] = m[n] - } - return binary.BigEndian.Uint64(m1), binary.BigEndian.Uint64(m2) -} - -func checkRouteOverlaps(networks []*net.IPNet, dockerNetwork *net.IPNet) error { - for _, network := range networks { - if networkOverlaps(dockerNetwork, network) { - return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network) - } - } - return nil -} - -func checkNameserverOverlaps(nameservers []string, dockerNetwork *net.IPNet) error { - if len(nameservers) > 0 { - for _, ns := range nameservers { - _, nsNetwork, err := net.ParseCIDR(ns) - if err != nil { - return err - } - if networkOverlaps(dockerNetwork, nsNetwork) { - return fmt.Errorf("%s overlaps nameserver %s", dockerNetwork, nsNetwork) - } - } - } - return nil -} - -// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, -// and attempts to configure it with an address which doesn't conflict with any other interface on the host. -// If it can't find an address which doesn't conflict, it will return an error. -func CreateBridgeIface(config *DaemonConfig) error { - addrs4 := []string{ - // Here we don't follow the convention of using the 1st IP of the range for the gateway. - // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. - // In theory this shouldn't matter - in practice there's bound to be a few scripts relying - // on the internal addressing or other stupid things like that. - // The shouldn't, but hey, let's not break them unless we really have to. - "172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23 - "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive - "10.1.42.1/16", - "10.42.42.1/16", - "172.16.42.1/24", - "172.16.43.1/24", - "172.16.44.1/24", - "10.0.42.1/24", - "10.0.43.1/24", - "192.168.42.1/24", - "192.168.43.1/24", - "192.168.44.1/24", - } - - addrs6 := []string{ - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - utils.GenULA(), - } - - nameservers := []string{} - resolvConf, _ := utils.GetResolvConf() - // we don't check for an error here, because we don't really care - // if we can't read /etc/resolv.conf. So instead we skip the append - // if resolvConf is nil. It either doesn't exist, or we can't read it - // for some reason. - if resolvConf != nil { - nameservers = append(nameservers, utils.GetNameserversAsCIDR(resolvConf)...) - } - - var ifaceAddr string - var iface6Addr string - for _, addr := range addrs4 { - _, dockerNetwork, err := net.ParseCIDR(addr) - if err != nil { - return err - } - routes, err := netlink.NetworkGetRoutes() - if err != nil { - return err - } - if err := checkRouteOverlaps(routes, dockerNetwork); err == nil { - if err := checkNameserverOverlaps(nameservers, dockerNetwork); err == nil { - ifaceAddr = addr - break - } - } else { - utils.Debugf("%s: %s", addr, err) - } - } - for _, addr := range addrs6 { - _, dockerNetwork, err := net.ParseCIDR(addr) - if err != nil { - return err - } - routes, err := netlink.NetworkGetRoutes() - if err != nil { - return err - } - if err := checkRouteOverlaps(routes, dockerNetwork); err == nil { - if err := checkNameserverOverlaps(nameservers, dockerNetwork); err == nil { - iface6Addr = addr - break - } - } else { - utils.Debugf("%s: %s", addr, err) - } - } - - if ifaceAddr == "" || iface6Addr == "" { - return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface) - } - utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr) - - if err := netlink.NetworkLinkAdd(config.BridgeIface, "bridge"); err != nil { - return fmt.Errorf("Error creating bridge: %s", err) - } - iface, err := net.InterfaceByName(config.BridgeIface) - if err != nil { - return err - } - ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr) - if err != nil { - return err - } - ip6Addr, ip6Net, err := net.ParseCIDR(iface6Addr) - if err != nil { - return err - } - if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil { - return fmt.Errorf("Unable to add private network: %s", err) - } - if netlink.NetworkLinkAddIp(iface, ip6Addr, ip6Net); err != nil { - return fmt.Errorf("Unable to add private IPv6 network: %s", err) - } - if err := netlink.NetworkLinkUp(iface); err != nil { - return fmt.Errorf("Unable to start network bridge: %s", err) - } - - if config.EnableIptables { - // Enable NAT - if output, err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, - "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { - return fmt.Errorf("Unable to enable network bridge NAT: %s", err) - } else if len(output) != 0 { - return fmt.Errorf("Error iptables postrouting: %s", output) - } - - // Accept incoming packets for existing connections - if output, err := iptables.Raw("-I", "FORWARD", "-o", config.BridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"); err != nil { - return fmt.Errorf("Unable to allow incoming packets: %s", err) - } else if len(output) != 0 { - return fmt.Errorf("Error iptables allow incoming: %s", output) - } - - // Accept all non-intercontainer outgoing packets - if output, err := iptables.Raw("-I", "FORWARD", "-i", config.BridgeIface, "!", "-o", config.BridgeIface, "-j", "ACCEPT"); err != nil { - return fmt.Errorf("Unable to allow outgoing packets: %s", err) - } else if len(output) != 0 { - return fmt.Errorf("Error iptables allow outgoing: %s", output) - } - - } - return nil -} - -// Return the IPv4 address of a network interface -func getIfaceAddr(name string) (net.Addr, error) { - iface, err := net.InterfaceByName(name) - if err != nil { - return nil, err - } - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - var addrs4 []net.Addr - for _, addr := range addrs { - ip := (addr.(*net.IPNet)).IP - if ip4 := ip.To4(); len(ip4) == net.IPv4len { - addrs4 = append(addrs4, addr) - } - } - switch { - case len(addrs4) == 0: - return nil, fmt.Errorf("Interface %v has no IP addresses", name) - case len(addrs4) > 1: - fmt.Printf("Interface %v has more than 1 IP address. Defaulting to using %v\n", - name, (addrs4[0].(*net.IPNet)).IP) - } - return addrs4[0], nil -} - -// Return the IPv6 address of a network interface -func getIfaceAddr6(name string) (net.Addr, error) { - iface, err := net.InterfaceByName(name) - if err != nil { - return nil, err - } - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - var addrs6 []net.Addr - for _, addr := range addrs { - ip := (addr.(*net.IPNet)).IP - if ip4 := ip.To4(); ip4 == nil { - addrs6 = append(addrs6, addr) - } - } - switch { - case len(addrs6) == 0: - return nil, fmt.Errorf("Interface %v has no IPv6 addresses", name) - case len(addrs6) == 1: - return nil, fmt.Errorf("Interface %v only has a link-local IPv6 address", name) - case len(addrs6) > 2: - fmt.Printf("Interface %v has more than 2 IPv6 addresses. Defaulting to using %v\n", - name, (addrs6[0].(*net.IPNet)).IP) - } - return addrs6[0], nil -} - -// Port mapper takes care of mapping external ports to containers by setting -// up iptables rules. -// It keeps track of all mappings and is able to unmap at will -type PortMapper struct { - tcpMapping map[int]*net.TCPAddr - tcpProxies map[int]proxy.Proxy - udpMapping map[int]*net.UDPAddr - udpProxies map[int]proxy.Proxy - - iptables *iptables.Chain - defaultIp net.IP -} - -func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error { - if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { - backendPort := backendAddr.(*net.TCPAddr).Port - backendIP := backendAddr.(*net.TCPAddr).IP - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil { - return err - } - } - mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) - proxy, err := proxy.NewProxy(&net.TCPAddr{IP: ip, Port: port}, backendAddr) - if err != nil { - mapper.Unmap(ip, port, "tcp") - return err - } - mapper.tcpProxies[port] = proxy - go proxy.Run() - } else { - backendPort := backendAddr.(*net.UDPAddr).Port - backendIP := backendAddr.(*net.UDPAddr).IP - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil { - return err - } - } - mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) - proxy, err := proxy.NewProxy(&net.UDPAddr{IP: ip, Port: port}, backendAddr) - if err != nil { - mapper.Unmap(ip, port, "udp") - return err - } - mapper.udpProxies[port] = proxy - go proxy.Run() - } - return nil -} - -func (mapper *PortMapper) Unmap(ip net.IP, port int, proto string) error { - if proto == "tcp" { - backendAddr, ok := mapper.tcpMapping[port] - if !ok { - return fmt.Errorf("Port tcp/%v is not mapped", port) - } - if proxy, exists := mapper.tcpProxies[port]; exists { - proxy.Close() - delete(mapper.tcpProxies, port) - } - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err - } - } - delete(mapper.tcpMapping, port) - } else { - backendAddr, ok := mapper.udpMapping[port] - if !ok { - return fmt.Errorf("Port udp/%v is not mapped", port) - } - if proxy, exists := mapper.udpProxies[port]; exists { - proxy.Close() - delete(mapper.udpProxies, port) - } - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err - } - } - delete(mapper.udpMapping, port) - } - return nil -} - -func newPortMapper(config *DaemonConfig) (*PortMapper, error) { - // We can always try removing the iptables - if err := iptables.RemoveExistingChain("DOCKER"); err != nil { - return nil, err - } - var chain *iptables.Chain - if config.EnableIptables { - var err error - chain, err = iptables.NewChain("DOCKER", config.BridgeIface) - if err != nil { - return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err) - } - } - - mapper := &PortMapper{ - tcpMapping: make(map[int]*net.TCPAddr), - tcpProxies: make(map[int]proxy.Proxy), - udpMapping: make(map[int]*net.UDPAddr), - udpProxies: make(map[int]proxy.Proxy), - iptables: chain, - defaultIp: config.DefaultIp, - } - return mapper, nil -} - -// Port allocator: Automatically allocate and release networking ports -type PortAllocator struct { - sync.Mutex - inUse map[int]struct{} - fountain chan int - quit chan bool -} - -func (alloc *PortAllocator) runFountain() { - for { - for port := portRangeStart; port < portRangeEnd; port++ { - select { - case alloc.fountain <- port: - case quit := <-alloc.quit: - if quit { - return - } - } - } - } -} - -// FIXME: Release can no longer fail, change its prototype to reflect that. -func (alloc *PortAllocator) Release(port int) error { - utils.Debugf("Releasing %d", port) - alloc.Lock() - delete(alloc.inUse, port) - alloc.Unlock() - return nil -} - -func (alloc *PortAllocator) Acquire(port int) (int, error) { - utils.Debugf("Acquiring %d", port) - if port == 0 { - // Allocate a port from the fountain - for port := range alloc.fountain { - if _, err := alloc.Acquire(port); err == nil { - return port, nil - } - } - return -1, fmt.Errorf("Port generator ended unexpectedly") - } - alloc.Lock() - defer alloc.Unlock() - if _, inUse := alloc.inUse[port]; inUse { - return -1, fmt.Errorf("Port already in use: %d", port) - } - alloc.inUse[port] = struct{}{} - return port, nil -} - -func (alloc *PortAllocator) Close() error { - alloc.quit <- true - close(alloc.quit) - close(alloc.fountain) - return nil -} - -func newPortAllocator() (*PortAllocator, error) { - allocator := &PortAllocator{ - inUse: make(map[int]struct{}), - fountain: make(chan int), - quit: make(chan bool), - } - go allocator.runFountain() - return allocator, nil -} - -// IP allocator: Automatically allocate and release networking ports -type IPAllocator struct { - network *net.IPNet - queueAlloc chan allocatedIP - queueReleased chan net.IP - inUse map[string]struct{} - quit chan bool -} - -type allocatedIP struct { - ip net.IP - err error -} - -func (alloc *IPAllocator) run() { - firstIP, _ := networkRange(alloc.network) - ip4 := firstIP.To4() - - if ip4 != nil { - ipNum := ipToInt(firstIP) - ownIP := ipToInt(alloc.network.IP) - size := networkSize(alloc.network.Mask) - pos := int32(1) - max := size - 2 // -1 for the broadcast address, -1 for the gateway address - - for { - var ( - newNum int32 - inUse bool - ) - - // Find first unused IP, give up after one whole round - for attempt := int32(0); attempt < max; attempt++ { - newNum = ipNum + pos - - pos = pos%max + 1 - - // The network's IP is never okay to use - if newNum == ownIP { - continue - } - - if _, inUse = alloc.inUse[string(intToIP(newNum))]; !inUse { - // We found an unused IP - break - } - } - - ip := allocatedIP{ip: intToIP(newNum)} - if inUse { - ip.err = errors.New("No unallocated IP available") - } - - select { - case quit := <-alloc.quit: - if quit { - return - } - case alloc.queueAlloc <- ip: - alloc.inUse[string(intToIP(newNum))] = struct{}{} - case released := <-alloc.queueReleased: - r := ipToInt(released) - delete(alloc.inUse, string(intToIP(r))) - - if inUse { - // If we couldn't allocate a new IP, the released one - // will be the only free one now, so instantly use it - // next time - pos = r - ipNum - } else { - // Use same IP as last time - if pos == 1 { - pos = max - } else { - pos-- - } - } - } - } - } else { - ipNum6, ipNum6a := ip6ToInt(firstIP) - _, ownIP6a := ip6ToInt(alloc.network.IP) - _, size2 := networkSize6(alloc.network.Mask) - pos := uint64(1) - max := size2 - - for { - var ( - newNum6 uint64 - newNum6a uint64 - inUse bool - ) - - // Find first unused IP, give up after one whole round - for attempt := uint64(0); attempt < max; attempt++ { - newNum6 = ipNum6 - newNum6a = ipNum6a + pos - - pos = pos%max + 1 - - // The network's IP is never okay to use - if newNum6a == ownIP6a { - continue - } - - ip := intToIP6(newNum6, newNum6a) - if _, inUse = alloc.inUse[ip.String()]; !inUse { - // We found an unused IP - break - } - } - - ip := allocatedIP{ip: intToIP6(newNum6, newNum6a)} - if inUse { - ip.err = errors.New("No unallocated IPv6 available") - } - - select { - case quit := <-alloc.quit: - if quit { - return - } - case alloc.queueAlloc <- ip: - ip6 := intToIP6(newNum6, newNum6a) - alloc.inUse[ip6.String()] = struct{}{} - case released := <-alloc.queueReleased: - _, r1 := ip6ToInt(released) - delete(alloc.inUse, released.String()) - - if inUse { - // If we couldn't allocate a new IP, the released one - // will be the only free one now, so instantly use it - // next time - pos = r1 - ipNum6a - } else { - // Use same IP as last time - if pos == 1 { - pos = max - } else { - pos-- - } - } - } - } - } -} - -func (alloc *IPAllocator) Acquire() (net.IP, error) { - ip := <-alloc.queueAlloc - return ip.ip, ip.err -} - -func (alloc *IPAllocator) Release(ip net.IP) { - alloc.queueReleased <- ip -} - -func (alloc *IPAllocator) Close() error { - alloc.quit <- true - close(alloc.quit) - close(alloc.queueAlloc) - close(alloc.queueReleased) - return nil -} - -func newIPAllocator(network *net.IPNet) *IPAllocator { - alloc := &IPAllocator{ - network: network, - queueAlloc: make(chan allocatedIP), - queueReleased: make(chan net.IP), - inUse: make(map[string]struct{}), - quit: make(chan bool), - } - - go alloc.run() - - return alloc -} - -// Network interface represents the networking stack of a container -type NetworkInterface struct { - IPNet net.IPNet - IPNet6 net.IPNet - Gateway net.IP - Gateway6 net.IP - - manager *NetworkManager - extPorts []*Nat - disabled bool -} - -// Allocate an external port and map it to the interface -func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) { - - if iface.disabled { - return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME - } - - ip := iface.manager.portMapper.defaultIp - - if binding.HostIp != "" { - ip = net.ParseIP(binding.HostIp) - } else { - binding.HostIp = ip.String() - } - - nat := &Nat{ - Port: port, - Binding: binding, - } - - containerPort, err := parsePort(port.Port()) - if err != nil { - return nil, err - } - - hostPort, _ := parsePort(nat.Binding.HostPort) - - if nat.Port.Proto() == "tcp" { - extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort) - if err != nil { - return nil, err - } - - backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort} - if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { - iface.manager.tcpPortAllocator.Release(extPort) - return nil, err - } - nat.Binding.HostPort = strconv.Itoa(extPort) - } else { - extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort) - if err != nil { - return nil, err - } - backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} - if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { - iface.manager.udpPortAllocator.Release(extPort) - return nil, err - } - nat.Binding.HostPort = strconv.Itoa(extPort) - } - iface.extPorts = append(iface.extPorts, nat) - - return nat, nil -} - -type Nat struct { - Port Port - Binding PortBinding -} - -func (n *Nat) String() string { - return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto()) -} - -// Release: Network cleanup - release all resources -func (iface *NetworkInterface) Release() { - if iface.disabled { - return - } - - for _, nat := range iface.extPorts { - hostPort, err := parsePort(nat.Binding.HostPort) - if err != nil { - log.Printf("Unable to get host port: %s", err) - continue - } - ip := net.ParseIP(nat.Binding.HostIp) - utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort) - if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil { - log.Printf("Unable to unmap port %s: %s", nat, err) - } - if nat.Port.Proto() == "tcp" { - if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil { - log.Printf("Unable to release port %s", nat) - } - } else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil { - log.Printf("Unable to release port %s: %s", nat, err) - } - } - - iface.manager.ipAllocator.Release(iface.IPNet.IP) - iface.manager.ipAllocator6.Release(iface.IPNet6.IP) -} - -// Network Manager manages a set of network interfaces -// Only *one* manager per host machine should be used -type NetworkManager struct { - bridgeIface string - bridgeNetwork *net.IPNet - bridgeNetwork6 *net.IPNet - - ipAllocator *IPAllocator - ipAllocator6 *IPAllocator - tcpPortAllocator *PortAllocator - udpPortAllocator *PortAllocator - portMapper *PortMapper - - disabled bool -} - -// Allocate a network interface -func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { - - if manager.disabled { - return &NetworkInterface{disabled: true}, nil - } - - var ip net.IP - var ip6 net.IP - var err error - - ip, err = manager.ipAllocator.Acquire() - if err != nil { - return nil, err - } - ip6, err = manager.ipAllocator6.Acquire() - if err != nil { - return nil, err - } - - // avoid duplicate IP - dup := 0 - dup6 := 0 - - ipNum := ipToInt(ip) - firstIP := manager.ipAllocator.network.IP.To4().Mask(manager.ipAllocator.network.Mask) - firstIPNum := ipToInt(firstIP) + 1 - if firstIPNum == ipNum { - dup = 1 - } - - ipNum6, ipNum6a := ip6ToInt(ip6) - firstIP6 := manager.ipAllocator6.network.IP.Mask(manager.ipAllocator6.network.Mask) - firstIP6Num, firstIP6Num1 := ip6ToInt(firstIP6) - if firstIP6Num == ipNum6 && firstIP6Num1 == ipNum6a { - dup6 = 1 - } - - if dup == 1 { - ip, err = manager.ipAllocator.Acquire() - if err != nil { - return nil, err - } - } - if dup6 == 1 { - ip6, err = manager.ipAllocator6.Acquire() - if err != nil { - return nil, err - } - } - - iface := &NetworkInterface{ - IPNet: net.IPNet{IP: ip, Mask: manager.bridgeNetwork.Mask}, - IPNet6: net.IPNet{IP: ip6, Mask: manager.bridgeNetwork6.Mask}, - Gateway: manager.bridgeNetwork.IP, - Gateway6: manager.bridgeNetwork6.IP, - manager: manager, - } - return iface, nil -} - -func (manager *NetworkManager) Close() error { - if manager.disabled { - return nil - } - err1 := manager.tcpPortAllocator.Close() - err2 := manager.udpPortAllocator.Close() - err3 := manager.ipAllocator.Close() - err4 := manager.ipAllocator6.Close() - if err1 != nil { - return err1 - } - if err2 != nil { - return err2 - } - if err3 != nil { - return err3 - } - return err4 -} - -func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { - if config.BridgeIface == DisableNetworkBridge { - manager := &NetworkManager{ - disabled: true, - } - return manager, nil - } - - addr, err := getIfaceAddr(config.BridgeIface) - if err != nil { - // If the iface is not found, try to create it - if err := CreateBridgeIface(config); err != nil { - return nil, err - } - addr, err = getIfaceAddr(config.BridgeIface) - if err != nil { - return nil, err - } - } - network := addr.(*net.IPNet) - - addr6, err := getIfaceAddr6(config.BridgeIface) - if err != nil { - if err := CreateBridgeIface(config); err != nil { - return nil, err - } - addr6, err = getIfaceAddr6(config.BridgeIface) - if err != nil { - return nil, err - } - } - network6 := addr6.(*net.IPNet) - - // Configure iptables for link support - if config.EnableIptables { - args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j"} - acceptArgs := append(args, "ACCEPT") - dropArgs := append(args, "DROP") - - if !config.InterContainerCommunication { - iptables.Raw(append([]string{"-D"}, acceptArgs...)...) - if !iptables.Exists(dropArgs...) { - utils.Debugf("Disable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error disabling intercontainer communication: %s", output) - } - } - } else { - iptables.Raw(append([]string{"-D"}, dropArgs...)...) - if !iptables.Exists(acceptArgs...) { - utils.Debugf("Enable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to allow intercontainer communication: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error enabling intercontainer communication: %s", output) - } - } - } - } - - ipAllocator := newIPAllocator(network) - ipAllocator6 := newIPAllocator(network6) - - tcpPortAllocator, err := newPortAllocator() - if err != nil { - return nil, err - } - udpPortAllocator, err := newPortAllocator() - if err != nil { - return nil, err - } - - portMapper, err := newPortMapper(config) - if err != nil { - return nil, err - } - - manager := &NetworkManager{ - bridgeIface: config.BridgeIface, - bridgeNetwork: network, - bridgeNetwork6: network6, - ipAllocator: ipAllocator, - ipAllocator6: ipAllocator6, - tcpPortAllocator: tcpPortAllocator, - udpPortAllocator: udpPortAllocator, - portMapper: portMapper, - } - - return manager, nil -} diff --git a/network_test.go b/network_test.go deleted file mode 100644 index a173a3ea9d322..0000000000000 --- a/network_test.go +++ /dev/null @@ -1,448 +0,0 @@ -package docker - -import ( - "net" - "testing" -) - -func TestPortAllocation(t *testing.T) { - allocator, err := newPortAllocator() - if err != nil { - t.Fatal(err) - } - if port, err := allocator.Acquire(80); err != nil { - t.Fatal(err) - } else if port != 80 { - t.Fatalf("Acquire(80) should return 80, not %d", port) - } - port, err := allocator.Acquire(0) - if err != nil { - t.Fatal(err) - } - if port <= 0 { - t.Fatalf("Acquire(0) should return a non-zero port") - } - if _, err := allocator.Acquire(port); err == nil { - t.Fatalf("Acquiring a port already in use should return an error") - } - if newPort, err := allocator.Acquire(0); err != nil { - t.Fatal(err) - } else if newPort == port { - t.Fatalf("Acquire(0) allocated the same port twice: %d", port) - } - if _, err := allocator.Acquire(80); err == nil { - t.Fatalf("Acquiring a port already in use should return an error") - } - if err := allocator.Release(80); err != nil { - t.Fatal(err) - } - if _, err := allocator.Acquire(80); err != nil { - t.Fatal(err) - } -} - -func TestNetworkRange(t *testing.T) { - // Simple class C test - _, network, _ := net.ParseCIDR("192.168.0.1/24") - first, last := networkRange(network) - if !first.Equal(net.ParseIP("192.168.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("192.168.0.255")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 256 { - t.Error(size) - } - - // Class A test - _, network, _ = net.ParseCIDR("10.0.0.1/8") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.0.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.255.255.255")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 16777216 { - t.Error(size) - } - - // Class A, random IP address - _, network, _ = net.ParseCIDR("10.1.2.3/8") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.0.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.255.255.255")) { - t.Error(last.String()) - } - - // Simple IPv6 test - _, network, _ = net.ParseCIDR("fd13:514e:9236:6127::/64") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("fd13:514e:9236:6127::")) { - t.Error(first.String()) - } - if size, size1 := networkSize6(network.Mask); size != 0 || size1 != 18446744073709551615 { - t.Error(size, size1) - } - - // 32bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/32") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.3")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.3")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 1 { - t.Error(size) - } - - // 31bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/31") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.2")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.3")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 2 { - t.Error(size) - } - - // 26bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/26") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.63")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 64 { - t.Error(size) - } -} - -func TestConversion(t *testing.T) { - ip := net.ParseIP("127.0.0.1") - i := ipToInt(ip) - if i == 0 { - t.Fatal("converted to zero") - } - conv := intToIP(i) - if !ip.Equal(conv) { - t.Error(conv.String()) - } -} - -func TestConversion6(t *testing.T) { - ip := net.ParseIP("::1") - i, i2 := ip6ToInt(ip) - if i != 0 || i2 == 0 { - t.Fatal("converted to zero") - } - conv := intToIP6(i, i2) - if !ip.Equal(conv) { - t.Error(conv.String()) - } -} - -func TestIPAllocator(t *testing.T) { - expectedIPs := []net.IP{ - 0: net.IPv4(127, 0, 0, 2), - 1: net.IPv4(127, 0, 0, 3), - 2: net.IPv4(127, 0, 0, 4), - 3: net.IPv4(127, 0, 0, 5), - 4: net.IPv4(127, 0, 0, 6), - } - - gwIP, n, _ := net.ParseCIDR("127.0.0.1/29") - alloc := newIPAllocator(&net.IPNet{IP: gwIP, Mask: n.Mask}) - // Pool after initialisation (f = free, u = used) - // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - // ↑ - - // Check that we get 5 IPs, from 127.0.0.2–127.0.0.6, in that - // order. - for i := 0; i < 5; i++ { - ip, err := alloc.Acquire() - if err != nil { - t.Fatal(err) - } - - assertIPEquals(t, expectedIPs[i], ip) - } - // Before loop begin - // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 0 - // 2(u) - 3(f) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 1 - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 2 - // 2(u) - 3(u) - 4(u) - 5(f) - 6(f) - // ↑ - - // After i = 3 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) - // ↑ - - // After i = 4 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - // ↑ - - // Check that there are no more IPs - _, err := alloc.Acquire() - if err == nil { - t.Fatal("There shouldn't be any IP addresses at this point") - } - - // Release some IPs in non-sequential order - alloc.Release(expectedIPs[3]) - // 2(u) - 3(u) - 4(u) - 5(f) - 6(u) - // ↑ - - alloc.Release(expectedIPs[2]) - // 2(u) - 3(u) - 4(f) - 5(f) - 6(u) - // ↑ - - alloc.Release(expectedIPs[4]) - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - // ↑ - - // Make sure that IPs are reused in sequential order, starting - // with the first released IP - newIPs := make([]net.IP, 3) - for i := 0; i < 3; i++ { - ip, err := alloc.Acquire() - if err != nil { - t.Fatal(err) - } - - newIPs[i] = ip - } - // Before loop begin - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 0 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(f) - // ↑ - - // After i = 1 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(u) - // ↑ - - // After i = 2 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - // ↑ - - assertIPEquals(t, expectedIPs[3], newIPs[0]) - assertIPEquals(t, expectedIPs[4], newIPs[1]) - assertIPEquals(t, expectedIPs[2], newIPs[2]) - - _, err = alloc.Acquire() - if err == nil { - t.Fatal("There shouldn't be any IP addresses at this point") - } -} - -func TestIPAllocator6(t *testing.T) { - expectedIPs := []net.IP{ - 0: net.IP([]byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}), - 1: net.IP([]byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}), - 2: net.IP([]byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}), - 3: net.IP([]byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5}), - 4: net.IP([]byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6}), - 5: net.IP([]byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7}), - } - - gwIP, n, _ := net.ParseCIDR("2001:db8::1/125") - alloc := newIPAllocator(&net.IPNet{IP: gwIP, Mask: n.Mask}) - // Pool after initialisation (f = free, u = used) - // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - 7(f) - // ↑ - - // Check that we get 6 IPs, from 2001:db8::2-2001:db8::7, in that - // order. - for i := 0; i < 6; i++ { - ip, err := alloc.Acquire() - if err != nil { - t.Fatal(err) - } - assertIPEquals(t, expectedIPs[i], ip) - } - // Before loop begin - // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - 7(f) - // ↑ - - // After i = 0 - // 2(u) - 3(f) - 4(f) - 5(f) - 6(f) - 7(f) - // ↑ - - // After i = 1 - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - 7(f) - // ↑ - - // After i = 2 - // 2(u) - 3(u) - 4(u) - 5(f) - 6(f) - 7(f) - // ↑ - - // After i = 3 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) - 7(f) - // ↑ - - // After i = 4 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) - 7(f) - // ↑ - - // After i = 5 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - 7(u) - // ↑ - - // Check that there are no more IPs - _, err := alloc.Acquire() - if err == nil { - t.Fatal("There shouldn't be any IP addresses at this point") - } - - // Release some IPs in non-sequential order - alloc.Release(expectedIPs[3]) - // 2(u) - 3(u) - 4(u) - 5(f) - 6(u) - 7(u) - // ↑ - - alloc.Release(expectedIPs[2]) - // 2(u) - 3(u) - 4(f) - 5(f) - 6(u) - 7(u) - // ↑ - - alloc.Release(expectedIPs[4]) - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - 7(u) - // ↑ - - // Make sure that IPs are reused in sequential order, starting - // with the first released IP - newIPs := make([]net.IP, 3) - for i := 0; i < 3; i++ { - ip, err := alloc.Acquire() - if err != nil { - t.Fatal(err) - } - - newIPs[i] = ip - } - // Before loop begin - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - 7(u) - // ↑ - - // After i = 0 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(f) - 7(u) - // ↑ - - // After i = 1 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(u) - 7(u) - // ↑ - - // After i = 2 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - 7(u) - // ↑ - - assertIPEquals(t, expectedIPs[3], newIPs[0]) - assertIPEquals(t, expectedIPs[4], newIPs[1]) - assertIPEquals(t, expectedIPs[2], newIPs[2]) - - _, err = alloc.Acquire() - if err == nil { - t.Fatal("There shouldn't be any IP addresses at this point") - } -} - -func assertIPEquals(t *testing.T, ip1, ip2 net.IP) { - if !ip1.Equal(ip2) { - t.Fatalf("Expected IP %s, got %s", ip1, ip2) - } -} - -func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) { - _, netX, _ := net.ParseCIDR(CIDRx) - _, netY, _ := net.ParseCIDR(CIDRy) - if !networkOverlaps(netX, netY) { - t.Errorf("%v and %v should overlap", netX, netY) - } -} - -func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) { - _, netX, _ := net.ParseCIDR(CIDRx) - _, netY, _ := net.ParseCIDR(CIDRy) - if networkOverlaps(netX, netY) { - t.Errorf("%v and %v should not overlap", netX, netY) - } -} - -func TestNetworkOverlaps(t *testing.T) { - //netY starts at same IP and ends within netX - AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t) - AssertOverlap("2001:db8::1/64", "2001:db8::1/65", t) - //netY starts within netX and ends at same IP - AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t) - AssertOverlap("2001:db8::1/64", "2001:db8:0:0:8000::1/65", t) - //netY starts and ends within netX - AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t) - AssertOverlap("2001:db8::1/64", "2001:db8:0:0:4000::1/65", t) - //netY starts at same IP and ends outside of netX - AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t) - AssertOverlap("2001:db8::1/64", "2001:db8::1/63", t) - //netY starts before and ends at same IP of netX - AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) - //netY starts before and ends outside of netX - AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) - //netY starts and ends before netX - AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t) - //netX starts and ends before netY - AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t) -} - -func TestCheckRouteOverlaps(t *testing.T) { - routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} - - routes := []*net.IPNet{} - for _, addr := range routesData { - _, netX, _ := net.ParseCIDR(addr) - routes = append(routes, netX) - } - - _, netX, _ := net.ParseCIDR("172.16.0.1/24") - if err := checkRouteOverlaps(routes, netX); err != nil { - t.Fatal(err) - } - - _, netX, _ = net.ParseCIDR("10.0.2.0/24") - if err := checkRouteOverlaps(routes, netX); err == nil { - t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't") - } -} - -func TestCheckNameserverOverlaps(t *testing.T) { - nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"} - - _, netX, _ := net.ParseCIDR("10.0.2.3/32") - - if err := checkNameserverOverlaps(nameservers, netX); err == nil { - t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX) - } - - _, netX, _ = net.ParseCIDR("192.168.102.2/32") - - if err := checkNameserverOverlaps(nameservers, netX); err != nil { - t.Fatalf("%s should not overlap %v but it does", netX, nameservers) - } -} diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index 1c5a7b4cc27e6..cb7ec954c969b 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -33,11 +33,19 @@ func RequestIP(address *net.IPNet, ip *net.IP) (*net.IP, error) { checkAddress(address) if ip == nil { - next, err := getNextIp(address) - if err != nil { - return nil, err + if !networkdriver.IsIPv6(&address.IP) { + next, err := getNextIp(address) + if err != nil { + return nil, err + } + return next, nil + } else { + next, err := getNextIp6(address) + if err != nil { + return nil, err + } + return next, nil } - return next, nil } if err := registerIP(address, ip); err != nil { @@ -57,11 +65,16 @@ func ReleaseIP(address *net.IPNet, ip *net.IP) error { var ( existing = allocatedIPs[address.String()] available = availableIPS[address.String()] - pos = getPosition(address, ip) + pos uint64 ) - existing.Remove(int(pos)) - available.Push(int(pos)) + if !networkdriver.IsIPv6(ip) { + pos = uint64(getPosition(address, ip)) + } else { + _, pos = getPosition6(address, ip) + } + existing.Remove(pos) + available.Push(pos) return nil } @@ -77,6 +90,15 @@ func getPosition(address *net.IPNet, ip *net.IP) int32 { return i - base } +func getPosition6(address *net.IPNet, ip *net.IP) (uint64, uint64) { + var ( + first, _ = networkdriver.NetworkRange(address) + base, base2 = ip6ToInt(&first) + i, i2 = ip6ToInt(ip) + ) + return i - base, i2 - base2 +} + // return an available ip if one is currently available. If not, // return the next available ip for the nextwork func getNextIp(address *net.IPNet) (*net.IP, error) { @@ -87,14 +109,15 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { first, _ = networkdriver.NetworkRange(address) base = ipToInt(&first) size = int(networkdriver.NetworkSize(address.Mask)) - max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address - pos = int32(available.Pop()) + max = uint64(size - 2) // size -1 for the broadcast address, -1 for the gateway address + pos = available.Pop() ) + // We pop and push the position not the ip if pos != 0 { - ip := intToIP(int32(base + pos)) - allocated.Push(int(pos)) + ip := intToIP(base + int32(pos)) + allocated.Push(pos) return ip, nil } @@ -104,18 +127,63 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { firstAsInt = ipToInt(&firstNetIP) + 1 ) - pos = int32(allocated.PullBack()) - for i := int32(0); i < max; i++ { + pos = allocated.PullBack() + for i := uint64(0); i < max; i++ { pos = pos%max + 1 - next := int32(base + pos) + next := base + int32(pos) if next == ownIP || next == firstAsInt { continue } - if !allocated.Exists(int(pos)) { + if !allocated.Exists(pos) { ip := intToIP(next) - allocated.Push(int(pos)) + allocated.Push(pos) + return ip, nil + } + } + return nil, ErrNoAvailableIPs +} + +// return an available ip if one is currently available. If not, +// return the next available ip for the nextwork +func getNextIp6(address *net.IPNet) (*net.IP, error) { + var ( + _, ownIP = ip6ToInt(&address.IP) + available = availableIPS[address.String()] + allocated = allocatedIPs[address.String()] + first, _ = networkdriver.NetworkRange(address) + baseTop, base = ip6ToInt(&first) + _, max = networkdriver.NetworkSize6(address.Mask) + pos = available.Pop() + ) + + // We pop and push the position not the ip + if pos != 0 { + ip := intToIP6(baseTop, base + pos) + allocated.Push(pos) + + return ip, nil + } + + var ( + firstNetIP = address.IP.To16().Mask(address.Mask) + _, firstAsInt = ip6ToInt(&firstNetIP) + ) + firstAsInt = firstAsInt + 1 + + pos = allocated.PullBack() + for i := uint64(0); i < max; i++ { + pos = pos%max + 1 + next := base + pos + + if next == ownIP || next == firstAsInt { + continue + } + + if !allocated.Exists(pos) { + ip := intToIP6(baseTop, next) + allocated.Push(pos) return ip, nil } } @@ -126,13 +194,19 @@ func registerIP(address *net.IPNet, ip *net.IP) error { var ( existing = allocatedIPs[address.String()] available = availableIPS[address.String()] - pos = getPosition(address, ip) + pos uint64 ) - if existing.Exists(int(pos)) { + if !networkdriver.IsIPv6(ip) { + pos = uint64(getPosition(address, ip)) + } else { + _, pos = getPosition6(address, ip) + } + + if existing.Exists(pos) { return ErrIPAlreadyAllocated } - available.Remove(int(pos)) + available.Remove(pos) return nil } @@ -142,6 +216,20 @@ func ipToInt(ip *net.IP) int32 { return int32(binary.BigEndian.Uint32(ip.To4())) } +// Converts a 16 byte IP into two 64-bit integers +func ip6ToInt(ip *net.IP) (uint64, uint64) { + b := make([]byte, 8) + b2 := make([]byte, 8) + ip2 := ip.To16() + + for i := 0; i < len(b); i++ { + n := i + 8 + b[i] = ip2[i] + b2[i] = ip2[n] + } + return binary.BigEndian.Uint64(b), binary.BigEndian.Uint64(b2) +} + // Converts 32 bit integer into a 4 bytes IP address func intToIP(n int32) *net.IP { b := make([]byte, 4) @@ -150,6 +238,25 @@ func intToIP(n int32) *net.IP { return &ip } +// Converts 2 64 bit integers into a 16 byte IP address +func intToIP6(n1 uint64, n2 uint64) *net.IP { + b := make([]byte, 8) + b2 := make([]byte, 8) + final := make([]byte, 16) + + binary.BigEndian.PutUint64(b, n1) + binary.BigEndian.PutUint64(b2, n2) + + for i := 0; i < len(b); i++ { + n := i + 8 + final[i] = b[i] + final[n] = b2[i] + } + + ip := net.IP(final) + return &ip +} + func checkAddress(address *net.IPNet) { key := address.String() if _, exists := allocatedIPs[key]; !exists { diff --git a/networkdriver/ipallocator/allocator_test.go b/networkdriver/ipallocator/allocator_test.go index 5e9fcfc9836d6..f5e1cfa2e313e 100644 --- a/networkdriver/ipallocator/allocator_test.go +++ b/networkdriver/ipallocator/allocator_test.go @@ -30,6 +30,42 @@ func TestRequestNewIps(t *testing.T) { } } +func TestGetPosition(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 0, 0}, + } + ip := net.IPv4(192, 168, 0, 10) + ip2 := net.IPv4(192, 168, 1, 100) + + if result := getPosition(network, &ip); result != 10 { + t.Fatalf("Expected 10, got %d", result) + } + if result := getPosition(network, &ip2); result != 356 { + t.Fatalf("Expected 356, got %d", result) + } +} + +func TestGetPosition6(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}, + Mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + } + ip := net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xfa, 0xb8} + ip2 := net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0x10} + + result1, result2 := getPosition6(network, &ip) + if result1 != 0 && result2 == 64184 { + t.Fatalf("Expected 0 and 64184, got %d and %d", result1, result2) + } + result3, result4 := getPosition6(network, &ip2) + if result3 != 256 && result4 == 16 { + t.Fatalf("Expected 1 and 16, got %d and %d", result3, result4) + } +} + func TestReleaseIp(t *testing.T) { defer reset() network := &net.IPNet{ @@ -100,6 +136,18 @@ func TestConversion(t *testing.T) { } } +func TestConversion6(t *testing.T) { + ip := net.ParseIP("::1") + i, i2 := ip6ToInt(&ip) + if i != 0 || i2 == 0 { + t.Fatal("converted to zero") + } + conv := intToIP6(i, i2) + if !ip.Equal(*conv) { + t.Error(conv.String()) + } +} + func TestIPAllocator(t *testing.T) { expectedIPs := []net.IP{ 0: net.IPv4(127, 0, 0, 2), @@ -213,6 +261,123 @@ func TestIPAllocator(t *testing.T) { } } +func TestIPAllocator6(t *testing.T) { + expectedIPs := []net.IP{ + 0: net.IP([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}), + 1: net.IP([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03}), + 2: net.IP([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04}), + 3: net.IP([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x05}), + 4: net.IP([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x06}), + 5: net.IP([]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x07}), + } + + gwIP, n, _ := net.ParseCIDR("2001:db8::1/125") + network := &net.IPNet{IP: gwIP, Mask: n.Mask} + // Pool after initialisation (f = free, u = used) + // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - 7(f) + // ↑ + + // Check that we get 6 IPs, from 2001:db8::2-2001:db8::7, in that + // order. + for i := 0; i < 6; i++ { + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + assertIPEquals(t, &expectedIPs[i], ip) + } + // Before loop begin + // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - 7(f) + // ↑ + + // After i = 0 + // 2(u) - 3(f) - 4(f) - 5(f) - 6(f) - 7(f) + // ↑ + + // After i = 1 + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - 7(f) + // ↑ + + // After i = 2 + // 2(u) - 3(u) - 4(u) - 5(f) - 6(f) - 7(f) + // ↑ + + // After i = 3 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) - 7(f) + // ↑ + + // After i = 4 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) - 7(f) + // ↑ + + // After i = 5 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - 7(u) + // ↑ + + // Check that there are no more IPs + ip, err := RequestIP(network, nil) + if err == nil { + t.Fatalf("There shouldn't be any IP addresses at this point, got %s\n", ip) + } + + // Release some IPs in non-sequential order + if err := ReleaseIP(network, &expectedIPs[3]); err != nil { + t.Fatal(err) + } + // 2(u) - 3(u) - 4(u) - 5(f) - 6(u) + // ↑ + + if err := ReleaseIP(network, &expectedIPs[2]); err != nil { + t.Fatal(err) + } + // 2(u) - 3(u) - 4(f) - 5(f) - 6(u) + // ↑ + + if err := ReleaseIP(network, &expectedIPs[4]); err != nil { + t.Fatal(err) + } + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) + // ↑ + + // Make sure that IPs are reused in sequential order, starting + // with the first released IP + newIPs := make([]*net.IP, 3) + for i := 0; i < 3; i++ { + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + newIPs[i] = ip + } + // Before loop begin + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - 7(u) + // ↑ + + // After i = 0 + // 2(u) - 3(u) - 4(f) - 5(u) - 6(f) - 7(u) + // ↑ + + // After i = 1 + // 2(u) - 3(u) - 4(f) - 5(u) - 6(u) - 7(u) + // ↑ + + // After i = 2 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - 7(u) + // ↑ + + // Reordered these because the new set will always return the + // lowest ips first and not in the order that they were released + assertIPEquals(t, &expectedIPs[2], newIPs[0]) + assertIPEquals(t, &expectedIPs[3], newIPs[1]) + assertIPEquals(t, &expectedIPs[4], newIPs[2]) + + _, err = RequestIP(network, nil) + if err == nil { + t.Fatal("There shouldn't be any IP addresses at this point") + } +} + func TestAllocateFirstIP(t *testing.T) { defer reset() network := &net.IPNet{ @@ -234,6 +399,28 @@ func TestAllocateFirstIP(t *testing.T) { } } +func TestAllocateFirstIP6(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0}, + } + + firstIP := network.IP.To16().Mask(network.Mask) + _, first := ip6ToInt(&firstIP) + first = first + 1 + + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + _, allocated := ip6ToInt(ip) + + if allocated == first { + t.Fatalf("allocated ip should not equal first ip: %d == %d", first, allocated) + } +} + func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { if !ip1.Equal(*ip2) { t.Fatalf("Expected IP %s, got %s", ip1, ip2) diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index c767fd2208e00..5e885499be2d1 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -26,11 +26,12 @@ const ( // Network interface represents the networking stack of a container type networkInterface struct { IP net.IP + IP6 net.IP PortMappings []net.Addr // there are mappings to the host interfaces } var ( - addrs = []string{ + addrs4 = []string{ // Here we don't follow the convention of using the 1st IP of the range for the gateway. // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. // In theory this shouldn't matter - in practice there's bound to be a few scripts relying @@ -50,10 +51,19 @@ var ( "192.168.44.1/24", } - bridgeIface string - bridgeNetwork *net.IPNet + addrs6 = []string{ + networkdriver.GenULA(), + networkdriver.GenULA(), + networkdriver.GenULA(), + networkdriver.GenULA(), + } + + bridgeIface string + bridgeNetwork *net.IPNet + bridgeNetwork6 *net.IPNet defaultBindingIP = net.ParseIP("0.0.0.0") + defaultBindingIP6 = net.ParseIP("::") currentInterfaces = make(map[string]*networkInterface) ) @@ -66,15 +76,20 @@ func init() { func InitDriver(job *engine.Job) engine.Status { var ( network *net.IPNet + network6 *net.IPNet enableIPTables = job.GetenvBool("EnableIptables") icc = job.GetenvBool("InterContainerCommunication") ipForward = job.GetenvBool("EnableIpForward") bridgeIP = job.Getenv("BridgeIP") + bridgeIP6 = job.Getenv("BridgeIP6") ) if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" { defaultBindingIP = net.ParseIP(defaultIP) } + if defaultIP6 := job.Getenv("DefaultBindingIP6"); defaultIP6 != "" { + defaultBindingIP6 = net.ParseIP(defaultIP6) + } bridgeIface = job.Getenv("BridgeIface") if bridgeIface == "" { @@ -85,7 +100,7 @@ func InitDriver(job *engine.Job) engine.Status { if err != nil { // If the iface is not found, try to create it job.Logf("creating new bridge for %s", bridgeIface) - if err := createBridge(bridgeIP); err != nil { + if err := createBridge(bridgeIP, bridgeIP6); err != nil { job.Error(err) return engine.StatusErr } @@ -101,6 +116,16 @@ func InitDriver(job *engine.Job) engine.Status { network = addr.(*net.IPNet) } + addr6, err := networkdriver.GetIfaceAddr6(bridgeIface) + if err != nil { + // At this point we should have a bridge because of + // IPv4. Throw an error + job.Error(err) + return engine.StatusErr + } else { + network6 = addr6.(*net.IPNet) + } + // Configure iptables for link support if enableIPTables { if err := setupIPTables(addr, icc); err != nil { @@ -114,6 +139,11 @@ func InitDriver(job *engine.Job) engine.Status { if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { job.Logf("WARNING: unable to enable IPv4 forwarding: %s\n", err) } + + // Enable IPv6 forwarding + if err := ioutil.WriteFile("/proc/sys/net/ipv6/ip_forward", []byte{'1', '\n'}, 0644); err != nil { + job.Logf("WARNING: unable to enable IPv6 forwarding: %s\n", err) + } } // We can always try removing the iptables @@ -131,7 +161,8 @@ func InitDriver(job *engine.Job) engine.Status { portmapper.SetIptablesChain(chain) } - bridgeNetwork = network + bridgeNetwork = network + bridgeNetwork6 = network6 // https://github.com/dotcloud/docker/issues/2768 job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP) @@ -216,32 +247,42 @@ func setupIPTables(addr net.Addr, icc bool) error { return nil } -// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, -// and attempts to configure it with an address which doesn't conflict with any other interface on the host. -// If it can't find an address which doesn't conflict, it will return an error. -func createBridge(bridgeIP string) error { +func findBridgeNetwork(preferredIp string) (string, error) { + var address_pool *[]string + nameservers := []string{} + ip := net.ParseIP(preferredIp) resolvConf, _ := utils.GetResolvConf() // we don't check for an error here, because we don't really care // if we can't read /etc/resolv.conf. So instead we skip the append // if resolvConf is nil. It either doesn't exist, or we can't read it // for some reason. if resolvConf != nil { - nameservers = append(nameservers, utils.GetNameserversAsCIDR(resolvConf)...) + if !networkdriver.IsIPv6(&ip) { + nameservers = append(nameservers, utils.GetIPv4NameserversAsCIDR(resolvConf)...) + } else { + nameservers = append(nameservers, utils.GetIPv6NameserversAsCIDR(resolvConf)...) + } + } + + if networkdriver.IsIPv6(&ip) { + address_pool = &addrs6 + } else { + address_pool = &addrs4 } var ifaceAddr string - if len(bridgeIP) != 0 { - _, _, err := net.ParseCIDR(bridgeIP) + if len(preferredIp) != 0 { + _, _, err := net.ParseCIDR(preferredIp) if err != nil { - return err + return "", err } - ifaceAddr = bridgeIP + return preferredIp, nil } else { - for _, addr := range addrs { + for _, addr := range *address_pool { _, dockerNetwork, err := net.ParseCIDR(addr) if err != nil { - return err + return "", err } if err := networkdriver.CheckNameserverOverlaps(nameservers, dockerNetwork); err == nil { if err := networkdriver.CheckRouteOverlaps(dockerNetwork); err == nil { @@ -255,10 +296,28 @@ func createBridge(bridgeIP string) error { } if ifaceAddr == "" { - return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", bridgeIface, bridgeIface) + return "", fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", bridgeIface, bridgeIface) } - utils.Debugf("Creating bridge %s with network %s", bridgeIface, ifaceAddr) + return ifaceAddr, nil +} + +// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, +// and attempts to configure it with an address which doesn't conflict with any other interface on the host. +// If it can't find an address which doesn't conflict, it will return an error. +func createBridge(bridgeIP, bridgeIP6 string) error { + var inet, inet6 string + inet, err := findBridgeNetwork(bridgeIP) + if err != nil { + return err + } + inet6, err = findBridgeNetwork(bridgeIP6) + if err != nil { + return err + } + + + utils.Debugf("Creating bridge %s with networks %s, %s", bridgeIface, inet, inet6) if err := createBridgeIface(bridgeIface); err != nil { return err } @@ -268,14 +327,25 @@ func createBridge(bridgeIP string) error { return err } - ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr) + ipAddr, ipNet, err := net.ParseCIDR(inet) if err != nil { return err } if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil { - return fmt.Errorf("Unable to add private network: %s", err) + return fmt.Errorf("Unable to add private IPv4 network: %s", err) + } + + ipAddr6, ipNet6, err := net.ParseCIDR(inet6) + if err != nil { + return err } + + if netlink.NetworkLinkAddIp(iface, ipAddr6, ipNet6); err != nil { + return fmt.Errorf("Unable to add private IPv6 network: %s", err) + } + + if err := netlink.NetworkLinkUp(iface); err != nil { return fmt.Errorf("Unable to start network bridge: %s", err) } @@ -309,12 +379,15 @@ func createBridgeIface(name string) error { // Allocate a network interface func Allocate(job *engine.Job) engine.Status { var ( - ip *net.IP - err error - id = job.Args[0] - requestedIP = net.ParseIP(job.Getenv("RequestedIP")) + ip *net.IP + ip6 *net.IP + err error + id = job.Args[0] + requestedIP = net.ParseIP(job.Getenv("RequestedIP")) + requestedIP6 = net.ParseIP(job.Getenv("RequestedIP6")) ) + // IPv4 if requestedIP != nil { ip, err = ipallocator.RequestIP(bridgeNetwork, &requestedIP) } else { @@ -324,18 +397,36 @@ func Allocate(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + // IPv6 + if requestedIP6 != nil { + ip6, err = ipallocator.RequestIP(bridgeNetwork6, &requestedIP) + } else { + ip6, err = ipallocator.RequestIP(bridgeNetwork6, nil) + } + if err != nil { + job.Error(err) + return engine.StatusErr + } out := engine.Env{} out.Set("IP", ip.String()) + out.Set("IP6", ip6.String()) out.Set("Mask", bridgeNetwork.Mask.String()) + out.Set("Mask6", bridgeNetwork6.Mask.String()) out.Set("Gateway", bridgeNetwork.IP.String()) + out.Set("Gateway6", bridgeNetwork6.IP.String()) out.Set("Bridge", bridgeIface) + // IPv4 size, _ := bridgeNetwork.Mask.Size() out.SetInt("IPPrefixLen", size) + // IPv6 + size6, _ := bridgeNetwork6.Mask.Size() + out.SetInt("IPPrefixLen6", size6) currentInterfaces[id] = &networkInterface{ - IP: *ip, + IP: *ip, + IP6: *ip6, } out.WriteTo(job.Stdout) diff --git a/networkdriver/network_test.go b/networkdriver/network_test.go index c15f8b1cf5a36..6ef19b1a5e6f1 100644 --- a/networkdriver/network_test.go +++ b/networkdriver/network_test.go @@ -96,20 +96,28 @@ func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) { func TestNetworkOverlaps(t *testing.T) { //netY starts at same IP and ends within netX AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t) + AssertOverlap("2001:db8::1/64", "2001:db8::1/65", t) //netY starts within netX and ends at same IP AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t) + AssertOverlap("2001:db8::1/64", "2001:db8::1/65", t) //netY starts and ends within netX AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t) + AssertOverlap("2001:db8::1/64", "2001:db8::64/65", t) //netY starts at same IP and ends outside of netX AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t) + AssertOverlap("2001:db8::1/64", "2001:db8::1/48", t) //netY starts before and ends at same IP of netX AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) + AssertOverlap("2001:db8:0:1::1/64", "2001:db8::1/63", t) //netY starts before and ends outside of netX AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) + AssertOverlap("2001:db8::1/64", "2001:db8::1/48", t) //netY starts and ends before netX AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t) + AssertNoOverlap("2001:db8:2::1/64", "2001:db8::1/64", t) //netX starts and ends before netY AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t) + AssertNoOverlap("2001:db8::1/64", "2001:db8:2::1/64", t) } func TestNetworkRange(t *testing.T) { @@ -187,4 +195,93 @@ func TestNetworkRange(t *testing.T) { if size := NetworkSize(network.Mask); size != 64 { t.Error(size) } + + + // IPv6 + // 48bit mask + _, network, _ = net.ParseCIDR("2001:db8::1/48") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("2001:db8::")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("2001:db8::ffff:ffff:ffff:ffff:ffff")) { + t.Error(last.String()) + } + if size, size2 := NetworkSize6(network.Mask); size != uint64(65535) || size2 != uint64(18446744073709551615) { + t.Error(size) + } + + // 64bit mask + _, network, _ = net.ParseCIDR("2001:db8::1/64") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("2001:db8::")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("2001:db8::ffff:ffff:ffff:ffff")) { + t.Error(last.String()) + } + if size, size2 := NetworkSize6(network.Mask); size != 0 || size2 != uint64(18446744073709551615) { + t.Error(size) + } + + // TODO We should handle the special /127 and /128 networks + // differently from others because they ignore the "network address" + // in a subnet. Arbitrarily adding one to the number hosts can cause an overflow + + // 127bit mask + _, network, _ = net.ParseCIDR("2001:db8::1/127") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("2001:db8::")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("2001:db8::1")) { + t.Error(last.String()) + } + if size, size2 := NetworkSize6(network.Mask); size != 0 || size2 != uint64(1) { + t.Error(size) + } + + // 128bit mask + _, network, _ = net.ParseCIDR("2001:db8::1/128") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("2001:db8::1")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("2001:db8::1")) { + t.Error(last.String()) + } + if size, size2 := NetworkSize6(network.Mask); size != 0 || size2 != 0 { + t.Error(size) + } +} + +func TestIsIPv6(t *testing.T) { + ip, _,_ := net.ParseCIDR("202.12.27.33/32") + ip2,_,_ := net.ParseCIDR("10.1.2.1/8") + ip3,_,_ := net.ParseCIDR("224.0.0.1/4") + + ip4,_,_ := net.ParseCIDR("2001:db8::1/48") + ip5,_,_ := net.ParseCIDR("fe80::1/64") + ip6,_,_ := net.ParseCIDR("ffx8::/16") + + + if result := IsIPv6(&ip); result != false { + t.Fatalf("Expected false for %s, got %t", ip.String(), result) + } + if result := IsIPv6(&ip2); result != false { + t.Fatalf("Expected false for %s, got %t", ip2.String(), result) + } + if result := IsIPv6(&ip3); result != false { + t.Fatalf("Expected false for %s, got %t", ip3.String(), result) + } + + if result := IsIPv6(&ip4); result != true { + t.Fatalf("Expected true for %s, got %t", ip4.String(), result) + } + if result := IsIPv6(&ip5); result != true { + t.Fatalf("Expected true for %s, got %t", ip5.String(), result) + } + if result := IsIPv6(&ip6); result != true { + t.Fatalf("Expected true for %s, got %t", ip6.String(), result) + } } diff --git a/networkdriver/portallocator/portallocator.go b/networkdriver/portallocator/portallocator.go index 71cac82703b97..5fb95adceb486 100644 --- a/networkdriver/portallocator/portallocator.go +++ b/networkdriver/portallocator/portallocator.go @@ -71,14 +71,14 @@ func ReleasePort(ip net.IP, proto string, port int) error { } allocated := defaultAllocatedPorts[proto] - allocated.Remove(port) + allocated.Remove(uint64(port)) if !equalsDefault(ip) { registerIP(ip) // Remove the port for the specific ip address allocated = otherAllocatedPorts[ip.String()][proto] - allocated.Remove(port) + allocated.Remove(uint64(port)) } return nil } @@ -111,16 +111,16 @@ func registerDynamicPort(ip net.IP, proto string) (int, error) { registerIP(ip) ipAllocated := otherAllocatedPorts[ip.String()][proto] - ipAllocated.Push(port) + ipAllocated.Push(uint64(port)) } else { - allocated.Push(port) + allocated.Push(uint64(port)) } return port, nil } func registerSetPort(ip net.IP, proto string, port int) error { allocated := defaultAllocatedPorts[proto] - if allocated.Exists(port) { + if allocated.Exists(uint64(port)) { return ErrPortAlreadyAllocated } @@ -128,12 +128,12 @@ func registerSetPort(ip net.IP, proto string, port int) error { registerIP(ip) ipAllocated := otherAllocatedPorts[ip.String()][proto] - if ipAllocated.Exists(port) { + if ipAllocated.Exists(uint64(port)) { return ErrPortAlreadyAllocated } - ipAllocated.Push(port) + ipAllocated.Push(uint64(port)) } else { - allocated.Push(port) + allocated.Push(uint64(port)) } return nil } diff --git a/networkdriver/utils.go b/networkdriver/utils.go index 0a4ef70c95a70..77ece9bc0954b 100644 --- a/networkdriver/utils.go +++ b/networkdriver/utils.go @@ -1,10 +1,12 @@ package networkdriver import ( + "crypto/sha1" "encoding/binary" "errors" "fmt" "net" + "time" "github.com/dotcloud/docker/pkg/netlink" ) @@ -57,11 +59,19 @@ func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { // Calculates the first and last IP addresses in an IPNet func NetworkRange(network *net.IPNet) (net.IP, net.IP) { var ( - netIP = network.IP.To4() - firstIP = netIP.Mask(network.Mask) - lastIP = net.IPv4(0, 0, 0, 0).To4() + netIP = network.IP + firstIP, lastIP net.IP ) + firstIP = netIP.Mask(network.Mask) + if tempIP := netIP.To4(); tempIP == nil { + // Looks weird, but net.IPv4 still returns a 16 byte slice + lastIP = net.IPv4(0, 0, 0, 0) + } else { + netIP = tempIP + lastIP = net.IPv4(0, 0, 0, 0).To4() + } + for i := 0; i < len(lastIP); i++ { lastIP[i] = netIP[i] | ^network.Mask[i] } @@ -77,6 +87,24 @@ func NetworkSize(mask net.IPMask) int32 { return int32(binary.BigEndian.Uint32(m)) + 1 } +// Given a netmask, calculates the number of available hosts +func NetworkSize6(mask net.IPMask) (uint64, uint64) { + m := net.IPMask([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + + for i := 0; i < net.IPv6len; i++ { + m[i] = ^mask[i] + } + + m1 := make([]byte, 8) + m2 := make([]byte, 8) + for i := 0; i < len(m1); i++ { + n := i + 8 + m1[i] = m[i] + m2[i] = m[n] + } + return binary.BigEndian.Uint64(m1), binary.BigEndian.Uint64(m2) +} + // Return the IPv4 address of a network interface func GetIfaceAddr(name string) (net.Addr, error) { iface, err := net.InterfaceByName(name) @@ -104,6 +132,35 @@ func GetIfaceAddr(name string) (net.Addr, error) { return addrs4[0], nil } +// Return the IPv6 address of a network interface +func GetIfaceAddr6(name string) (net.Addr, error) { + iface, err := net.InterfaceByName(name) + if err != nil { + return nil, err + } + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + var addrs6 []net.Addr + for _, addr := range addrs { + ip := (addr.(*net.IPNet)).IP + if ip4 := ip.To4(); ip4 == nil { + addrs6 = append(addrs6, addr) + } + } + switch { + case len(addrs6) == 0: + return nil, fmt.Errorf("Interface %v has no IPv6 addresses", name) + case len(addrs6) == 1: + return nil, fmt.Errorf("Interface %v only has a link-local IPv6 address", name) + case len(addrs6) > 2: + fmt.Printf("Interface %v has more than 2 IPv6 addresses. Defaulting to using %v\n", + name, (addrs6[0].(*net.IPNet)).IP) + } + return addrs6[0], nil +} + func GetDefaultRouteIface() (*net.Interface, error) { rs, err := networkGetRoutesFct() if err != nil { @@ -116,3 +173,90 @@ func GetDefaultRouteIface() (*net.Interface, error) { } return nil, ErrNoDefaultRoute } + +// timeNTP() retrieves the current time from NTP's official server, +// returning uint64-encoded 'seconds' and 'fractions' See RFC 5905 +func timeNTP() (uint64, uint64, error) { + ntps, err := net.ResolveUDPAddr("udp", "0.pool.ntp.org:123") + + data := make([]byte, 48) + data[0] = 3<<3 | 3 + + con, err := net.DialUDP("udp", nil, ntps) + defer con.Close() + + _, err = con.Write(data) + + con.SetDeadline(time.Now().Add(5 * time.Second)) + + _, err = con.Read(data) + if err != nil { + return 0, 0, err + } + + var sec, frac uint64 + + sec = uint64(data[43]) | uint64(data[42])<<8 | uint64(data[41])<<16 | uint64(data[40])<<24 + frac = uint64(data[47]) | uint64(data[46])<<8 | uint64(data[45])<<16 | uint64(data[44])<<24 + return sec, frac, nil +} + +// Uint48([]byte) encodes a 48-bit (6 byte) []byte such as an interface MAC address into a uint64. +func Uint48(b []byte) uint64 { + return uint64(b[5]) | uint64(b[4])<<8 | uint64(b[3])<<16 | uint64(b[2])<<24 | uint64(b[1])<<32 | uint64(b[0])<<40 +} + +// findMAC() discovers the 'best' interface to use for IPv6 ULA +// generation; it loops through each available interface, looking for a +// non-zero, non-one MAC address. +// +// If none are found, it returns 0. +func findMAC() uint64 { + interfaces, _ := net.Interfaces() + for i := range interfaces { + mac := interfaces[i].HardwareAddr + if mac != nil { + macint := Uint48(mac) + if macint > 1 { + return macint + } + } + } + return 0 +} + +// GenULA() generates Unique Local Addresses for IPv6, implementing the +// algorithm suggested in RFC 4193 +func GenULA() string { + ntpsec, ntpfrac, _ := timeNTP() + mac := findMAC() + if mac == 0 { + mac = uint64(123456789123) // non-standard-compliant placeholder in case of error + } + key := ntpsec + ntpfrac + uint64(mac) + keyb := make([]byte, 8) + binary.BigEndian.PutUint64(keyb, key) + sha := sha1.New() + shakey := sha.Sum(keyb) + ip := net.IP(make([]byte, 16)) + pre := []byte{252} + + for i := 0; i < len(pre); i++ { + ip[i] = pre[i] + } + + for i := 0; i < 7; i++ { + n := i + 1 + ip[n] = shakey[i] + } + + return ip.String() + "/64" +} + +func IsIPv6(ip *net.IP) bool { + if ip.To4() == nil { + return true + } + + return false +} diff --git a/pkg/collections/orderedintset.go b/pkg/collections/orderedintset.go index 23abab04d3f58..968bb414e4119 100644 --- a/pkg/collections/orderedintset.go +++ b/pkg/collections/orderedintset.go @@ -7,7 +7,7 @@ import ( // OrderedIntSet is a thread-safe sorted set and a stack. type OrderedIntSet struct { sync.RWMutex - set []int + set []uint64 } // NewOrderedSet returns an initialized OrderedSet @@ -16,7 +16,7 @@ func NewOrderedIntSet() *OrderedIntSet { } // Push takes a string and adds it to the set. If the elem aready exists, it has no effect. -func (s *OrderedIntSet) Push(elem int) { +func (s *OrderedIntSet) Push(elem uint64) { s.RLock() for _, e := range s.set { if e == elem { @@ -31,7 +31,7 @@ func (s *OrderedIntSet) Push(elem int) { // Make sure the list is always sorted for i, e := range s.set { if elem < e { - s.set = append(s.set[:i], append([]int{elem}, s.set[i:]...)...) + s.set = append(s.set[:i], append([]uint64{elem}, s.set[i:]...)...) s.Unlock() return } @@ -42,13 +42,13 @@ func (s *OrderedIntSet) Push(elem int) { } // Pop is an alias to PopFront() -func (s *OrderedIntSet) Pop() int { +func (s *OrderedIntSet) Pop() uint64 { return s.PopFront() } // Pop returns the first elemen from the list and removes it. // If the list is empty, it returns 0 -func (s *OrderedIntSet) PopFront() int { +func (s *OrderedIntSet) PopFront() uint64 { s.RLock() for i, e := range s.set { @@ -67,7 +67,7 @@ func (s *OrderedIntSet) PopFront() int { // PullBack retrieve the last element of the list. // The element is not removed. // If the list is empty, an empty element is returned. -func (s *OrderedIntSet) PullBack() int { +func (s *OrderedIntSet) PullBack() uint64 { if len(s.set) == 0 { return 0 } @@ -75,7 +75,7 @@ func (s *OrderedIntSet) PullBack() int { } // Exists checks if the given element present in the list. -func (s *OrderedIntSet) Exists(elem int) bool { +func (s *OrderedIntSet) Exists(elem uint64) bool { for _, e := range s.set { if e == elem { return true @@ -86,7 +86,7 @@ func (s *OrderedIntSet) Exists(elem int) bool { // Remove removes an element from the list. // If the element is not found, it has no effect. -func (s *OrderedIntSet) Remove(elem int) { +func (s *OrderedIntSet) Remove(elem uint64) { for i, e := range s.set { if e == elem { s.set = append(s.set[:i], s.set[i+1:]...) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 27af37bb89849..6e0be79996592 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -12,6 +12,7 @@ type SysInfo struct { MemoryLimit bool SwapLimit bool IPv4ForwardingDisabled bool + IPv6ForwardingDisabled bool AppArmor bool } diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index dcf0eddf56eec..a3a825be91cab 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -53,8 +53,10 @@ func SysInit() { var ( // Get cmdline arguments user = flag.String("u", "", "username or uid") - gateway = flag.String("g", "", "gateway address") - ip = flag.String("i", "", "ip address") + gateway = flag.String("g", "", "IPv4 gateway address") + gateway6 = flag.String("g6", "", "IPv6 gateway address") + ip = flag.String("i", "", "IPv4 address") + ip6 = flag.String("i6", "", "IPv6 address") workDir = flag.String("w", "", "workdir") privileged = flag.Bool("privileged", false, "privileged mode") mtu = flag.Int("mtu", 1500, "interface mtu") @@ -78,7 +80,9 @@ func SysInit() { args := &execdriver.InitArgs{ User: *user, Gateway: *gateway, + Gateway6: *gateway6, Ip: *ip, + Ip6: *ip6, WorkDir: *workDir, Privileged: *privileged, Env: env, diff --git a/utils/ipv6.go b/utils/ipv6.go deleted file mode 100644 index 3abd73f07f07f..0000000000000 --- a/utils/ipv6.go +++ /dev/null @@ -1,83 +0,0 @@ -package utils - -import ( - "crypto/sha1" - "encoding/binary" - "net" - "time" -) - -// timeNTP() retrieves the current time from NTP's official server, returning uint64-encoded 'seconds' and 'fractions' -// See RFC 5905 -func timeNTP() (uint64, uint64, error) { - ntps, err := net.ResolveUDPAddr("udp", "0.pool.ntp.org:123") - - data := make([]byte, 48) - data[0] = 3<<3 | 3 - - con, err := net.DialUDP("udp", nil, ntps) - defer con.Close() - - _, err = con.Write(data) - - con.SetDeadline(time.Now().Add(5 * time.Second)) - - _, err = con.Read(data) - if err != nil { - return 0, 0, err - } - - var sec, frac uint64 - - sec = uint64(data[43]) | uint64(data[42])<<8 | uint64(data[41])<<16 | uint64(data[40])<<24 - frac = uint64(data[47]) | uint64(data[46])<<8 | uint64(data[45])<<16 | uint64(data[44])<<24 - return sec, frac, nil -} - -// Uint48([]byte) encodes a 48-bit (6 byte) []byte such as an interface MAC address into a uint64. -func Uint48(b []byte) uint64 { - return uint64(b[5]) | uint64(b[4])<<8 | uint64(b[3])<<16 | uint64(b[2])<<24 | uint64(b[1])<<32 | uint64(b[0])<<40 -} - -// findMAC() discovers the 'best' interface to use for IPv6 ULA generation; it loops through each available interface, looking for a non-zero, non-one MAC address. -// If none are found, it returns 0. -func findMAC() uint64 { - interfaces, _ := net.Interfaces() - for i := range interfaces { - mac := interfaces[i].HardwareAddr - if mac != nil { - macint := Uint48(mac) - if macint > 1 { - return macint - } - } - } - return 0 -} - -// GenULA() generates Unique Local Addresses for IPv6, implementing the algorithm suggested in RFC 4193 -func GenULA() string { - ntpsec, ntpfrac, _ := timeNTP() - mac := findMAC() - if mac == 0 { - mac = uint64(123456789123) // non-standard-compliant placeholder in case of error - } - key := ntpsec + ntpfrac + uint64(mac) - keyb := make([]byte, 8) - binary.BigEndian.PutUint64(keyb, key) - sha := sha1.New() - shakey := sha.Sum(keyb) - ip := net.IP(make([]byte, 16)) - pre := []byte{252} - - for i := 0; i < len(pre); i++ { - ip[i] = pre[i] - } - - for i := 0; i < 7; i++ { - n := i + 1 - ip[n] = shakey[i] - } - - return ip.String() + "/64" -} diff --git a/utils/utils.go b/utils/utils.go index 542ab497025fc..c2d866714e162 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -21,6 +21,7 @@ import ( "strings" "sync" "time" + "net" ) var ( @@ -732,10 +733,10 @@ func StripComments(input []byte, commentMarker []byte) []byte { return output } -// GetNameserversAsCIDR returns nameservers (if any) listed in +// GetIPv4NameserversAsCIDR returns IPv4 nameservers (if any) listed in // /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") // This function's output is intended for net.ParseCIDR -func GetNameserversAsCIDR(resolvConf []byte) []string { +func GetIPv4NameserversAsCIDR(resolvConf []byte) []string { var parsedResolvConf = StripComments(resolvConf, []byte("#")) nameservers := []string{} re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`) @@ -749,6 +750,31 @@ func GetNameserversAsCIDR(resolvConf []byte) []string { return nameservers } +// GetIPv6NameserversAsCIDR returns IPv6 nameservers (if any) listed in +// /etc/resolv.conf as CIDR blocks (e.g., "2001:503:ba3e::2:30/128") +// This function's output is intended for net.ParseCIDR +func GetIPv6NameserversAsCIDR(resolvConf []byte) []string { + var parsedResolvConf = StripComments(resolvConf, []byte("#")) + nameservers := []string{} + // The regex to catch an IPv6 address is pretty scary. Lets just let Go + // figure out it once we have something could be + re := regexp.MustCompile(`^\s*nameserver\s*\[?([a-fA-F0-9:]+)\]?\s*$`) + for _, line := range bytes.Split(parsedResolvConf, []byte("\n")) { + var ns = re.FindSubmatch(line) + if len(ns) > 0 { + // Add the /128 CIDR notation to force parsing as IPv6 + _,_,err := net.ParseCIDR(string(ns[1])+"/128") + if err != nil { + fmt.Println(err) + } + + nameservers = append(nameservers, string(ns[1])+"/128") + } + } + + return nameservers +} + // FIXME: Change this not to receive default value as parameter func ParseHost(defaultHost string, defaultPort int, defaultUnix, addr string) (string, error) { var ( diff --git a/utils/utils_test.go b/utils/utils_test.go index b0a5acb170e9a..375b43468dc17 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -440,7 +440,7 @@ func TestParsePortMapping(t *testing.T) { } } -func TestGetNameserversAsCIDR(t *testing.T) { +func TestGetIPv4NameserversAsCIDR(t *testing.T) { for resolv, result := range map[string][]string{` nameserver 1.2.3.4 nameserver 40.3.200.10 @@ -456,8 +456,41 @@ nameserver 1.2.3.4 #nameserver 4.3.2.1`: {"1.2.3.4/32"}, `search example.com nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"}, + `search example.com +nameserver fe80::1 +nameserver 1.2.3.4`: {"1.2.3.4/32"}, + } { + test := GetIPv4NameserversAsCIDR([]byte(resolv)) + if !StrSlicesEqual(test, result) { + t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv) + } + } +} + +func TestGetIPv6NameserversAsCIDR(t *testing.T) { + for resolv, result := range map[string][]string{` +nameserver fe80::1 +nameserver fe80:1::1 +search example.com`: {"fe80::1/128", "fe80:1::1/128"}, + `search example.com`: {}, + `nameserver 2001:503:ba3e::2:30 +search example.com +nameserver fe80::1`: {"2001:503:ba3e::2:30/128", "fe80::1/128"}, + ``: {}, + ` nameserver 2001:503:ba3e::2:30 `: {"2001:503:ba3e::2:30/128"}, + `search example.com +nameserver 2001:503:ba3e::2:30 +#nameserver fe80::1`: {"2001:503:ba3e::2:30/128"}, + `search example.com +nameserver [2001:503:ba3e::2:30] +#nameserver fe80::1`: {"2001:503:ba3e::2:30/128"}, + `search example.com +nameserver fe80::1 # comments hooray`: {"fe80::1/128"}, + `search example.com +nameserver fe80::1 +nameserver 1.2.3.4`: {"fe80::1/128"}, } { - test := GetNameserversAsCIDR([]byte(resolv)) + test := GetIPv6NameserversAsCIDR([]byte(resolv)) if !StrSlicesEqual(test, result) { t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv) }