From 4ee2bc45951281aa92788309fbe3d94f14011f34 Mon Sep 17 00:00:00 2001 From: cvlc Date: Sat, 30 Nov 2013 09:10:16 +0000 Subject: [PATCH] Add IPv6 support and tests with automatic ULA generation. --- AUTHORS | 1 + container.go | 42 ++++-- lxc_template.go | 1 + network.go | 373 +++++++++++++++++++++++++++++++++++++++--------- network_test.go | 135 ++++++++++++++++++ utils/ipv6.go | 83 +++++++++++ 6 files changed, 554 insertions(+), 81 deletions(-) create mode 100644 utils/ipv6.go diff --git a/AUTHORS b/AUTHORS index 60533c1cc2c41..7b6f6582b6aff 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,6 +32,7 @@ Briehan Lombaard Bruno Bigras Caleb Spare Calen Pennington +Calum Lacroix Charles Hooper Christopher Currie Colin Dunklau diff --git a/container.go b/container.go index 49cb33b536ee0..080ca22e2a660 100644 --- a/container.go +++ b/container.go @@ -162,12 +162,15 @@ func NewPort(proto, port string) Port { type PortMapping map[string]string // Deprecated type NetworkSettings struct { - IPAddress string - IPPrefixLen int - Gateway string - Bridge string - PortMapping map[string]PortMapping // Deprecated - Ports map[Port][]PortBinding + IPAddress string + IPAddress6 string + IPPrefixLen int + IPPrefixLen6 int + Gateway string + Gateway6 string + Bridge string + PortMapping map[string]PortMapping // Deprecated + Ports map[Port][]PortBinding } func (settings *NetworkSettings) PortMappingAPI() []APIPort { @@ -514,12 +517,12 @@ func (container *Container) Start() (err error) { } if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true - container.buildHostnameAndHostsFiles("127.0.1.1") + container.buildHostnameAndHostsFiles("127.0.1.1", "::2") } else { if err := container.allocateNetwork(); err != nil { return err } - container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) + container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress, container.NetworkSettings.IPAddress6) } // Make sure the config is compatible with the current kernel @@ -923,7 +926,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { return utils.NewBufReader(reader), nil } -func (container *Container) buildHostnameAndHostsFiles(IP string) { +func (container *Container) buildHostnameAndHostsFiles(IP string, IP6 string) { container.HostnamePath = path.Join(container.root, "hostname") ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) @@ -940,8 +943,10 @@ ff02::2 ip6-allrouters if container.Config.Domainname != "" { hostsContent = append([]byte(fmt.Sprintf("%s\t%s.%s %s\n", IP, container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("%s\t%s.%s %s\n", IP6, container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) } else { hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP6, container.Config.Hostname)), hostsContent...) } ioutil.WriteFile(container.HostsPath, hostsContent, 0644) @@ -961,13 +966,17 @@ func (container *Container) allocateNetwork() error { iface = &NetworkInterface{disabled: true} } else { iface = &NetworkInterface{ - IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, - Gateway: manager.bridgeNetwork.IP, - manager: manager, + IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, + IPNet6: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress6), Mask: manager.bridgeNetwork6.Mask}, + Gateway: manager.bridgeNetwork.IP, + Gateway6: manager.bridgeNetwork6.IP, + manager: manager, } - if iface != nil && iface.IPNet.IP != nil { - ipNum := ipToInt(iface.IPNet.IP) - manager.ipAllocator.inUse[ipNum] = struct{}{} + if iface != nil && iface.IPNet6.IP != nil && iface.IPNet.IP != nil { + ip6 := iface.IPNet6.IP + manager.ipAllocator6.inUse[ip6.String()] = struct{}{} + ip := iface.IPNet.IP + manager.ipAllocator.inUse[ip.String()] = struct{}{} } else { iface, err = container.runtime.networkManager.Allocate() if err != nil { @@ -1040,8 +1049,11 @@ func (container *Container) allocateNetwork() error { container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface container.NetworkSettings.IPAddress = iface.IPNet.IP.String() + container.NetworkSettings.IPAddress6 = iface.IPNet6.IP.String() container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size() + container.NetworkSettings.IPPrefixLen6, _ = iface.IPNet6.Mask.Size() container.NetworkSettings.Gateway = iface.Gateway.String() + container.NetworkSettings.Gateway6 = iface.Gateway6.String() return nil } diff --git a/lxc_template.go b/lxc_template.go index 2ba286742847e..179e8b3d2b9e7 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -23,6 +23,7 @@ lxc.network.link = {{.NetworkSettings.Bridge}} lxc.network.name = eth0 lxc.network.mtu = 1500 lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}} +lxc.network.ipv6 = {{.NetworkSettings.IPAddress6}}/{{.NetworkSettings.IPPrefixLen6}} {{end}} # root filesystem diff --git a/network.go b/network.go index 1397de0557598..f02e2804624ff 100644 --- a/network.go +++ b/network.go @@ -23,9 +23,14 @@ const ( // Calculates the first and last IP addresses in an IPNet func networkRange(network *net.IPNet) (net.IP, net.IP) { - netIP := network.IP.To4() + netIP := network.IP firstIP := netIP.Mask(network.Mask) - lastIP := net.IPv4(0, 0, 0, 0).To4() + 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] } @@ -45,28 +50,73 @@ func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { return false } -// Converts a 4 bytes IP into a 32 bit integer +// Converts a 16 byte IP into a 32 bit integer func ipToInt(ip net.IP) int32 { return int32(binary.BigEndian.Uint32(ip.To4())) } -// Converts 32 bit integer into a 4 bytes IP address +// 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.IPv4Mask(0, 0, 0, 0) + 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) { @@ -95,7 +145,7 @@ func checkNameserverOverlaps(nameservers []string, dockerNetwork *net.IPNet) err // 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 { - 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 @@ -115,6 +165,21 @@ func CreateBridgeIface(config *DaemonConfig) error { "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 @@ -126,7 +191,8 @@ func CreateBridgeIface(config *DaemonConfig) error { } var ifaceAddr string - for _, addr := range addrs { + var iface6Addr string + for _, addr := range addrs4 { _, dockerNetwork, err := net.ParseCIDR(addr) if err != nil { return err @@ -144,7 +210,26 @@ func CreateBridgeIface(config *DaemonConfig) error { utils.Debugf("%s: %s", addr, err) } } - if ifaceAddr == "" { + 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) @@ -160,9 +245,16 @@ func CreateBridgeIface(config *DaemonConfig) error { 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) } @@ -215,12 +307,41 @@ func getIfaceAddr(name string) (net.Addr, error) { 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 IPv4 address. Defaulting to using %v\n", + 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 @@ -404,7 +525,7 @@ type IPAllocator struct { network *net.IPNet queueAlloc chan allocatedIP queueReleased chan net.IP - inUse map[int32]struct{} + inUse map[string]struct{} quit chan bool } @@ -415,62 +536,131 @@ type allocatedIP struct { func (alloc *IPAllocator) run() { firstIP, _ := networkRange(alloc.network) - 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 + 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 + } - // 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 + } } - if _, inUse = alloc.inUse[newNum]; !inUse { - // We found an unused IP - break + ip := allocatedIP{ip: intToIP(newNum)} + if inUse { + ip.err = errors.New("No unallocated IP available") } - } - 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 + } - select { - case quit := <-alloc.quit: - if quit { - return + ip := intToIP6(newNum6, newNum6a) + if _, inUse = alloc.inUse[ip.String()]; !inUse { + // We found an unused IP + break + } } - case alloc.queueAlloc <- ip: - alloc.inUse[newNum] = struct{}{} - case released := <-alloc.queueReleased: - r := ipToInt(released) - delete(alloc.inUse, r) + ip := allocatedIP{ip: intToIP6(newNum6, newNum6a)} 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 + 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 { - pos-- + // Use same IP as last time + if pos == 1 { + pos = max + } else { + pos-- + } } } } @@ -499,7 +689,7 @@ func newIPAllocator(network *net.IPNet) *IPAllocator { network: network, queueAlloc: make(chan allocatedIP), queueReleased: make(chan net.IP), - inUse: make(map[int32]struct{}), + inUse: make(map[string]struct{}), quit: make(chan bool), } @@ -510,8 +700,10 @@ func newIPAllocator(network *net.IPNet) *IPAllocator { // Network interface represents the networking stack of a container type NetworkInterface struct { - IPNet net.IPNet - Gateway net.IP + IPNet net.IPNet + IPNet6 net.IPNet + Gateway net.IP + Gateway6 net.IP manager *NetworkManager extPorts []*Nat @@ -610,15 +802,18 @@ func (iface *NetworkInterface) Release() { } 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 + bridgeIface string + bridgeNetwork *net.IPNet + bridgeNetwork6 *net.IPNet ipAllocator *IPAllocator + ipAllocator6 *IPAllocator tcpPortAllocator *PortAllocator udpPortAllocator *PortAllocator portMapper *PortMapper @@ -634,28 +829,55 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { } 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}, - Gateway: manager.bridgeNetwork.IP, - manager: manager, + 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 } @@ -667,13 +889,17 @@ func (manager *NetworkManager) Close() error { 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 } - return err3 + if err3 != nil { + return err3 + } + return err4 } func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { @@ -697,6 +923,18 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } 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"} @@ -727,6 +965,7 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } ipAllocator := newIPAllocator(network) + ipAllocator6 := newIPAllocator(network6) tcpPortAllocator, err := newPortAllocator() if err != nil { @@ -745,7 +984,9 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { manager := &NetworkManager{ bridgeIface: config.BridgeIface, bridgeNetwork: network, + bridgeNetwork6: network6, ipAllocator: ipAllocator, + ipAllocator6: ipAllocator6, tcpPortAllocator: tcpPortAllocator, udpPortAllocator: udpPortAllocator, portMapper: portMapper, diff --git a/network_test.go b/network_test.go index e2631ddcb7226..a173a3ea9d322 100644 --- a/network_test.go +++ b/network_test.go @@ -78,6 +78,16 @@ func TestNetworkRange(t *testing.T) { 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) @@ -130,6 +140,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), @@ -235,6 +257,115 @@ func TestIPAllocator(t *testing.T) { } } +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) @@ -260,12 +391,16 @@ 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: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 diff --git a/utils/ipv6.go b/utils/ipv6.go new file mode 100644 index 0000000000000..3abd73f07f07f --- /dev/null +++ b/utils/ipv6.go @@ -0,0 +1,83 @@ +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" +}