From 873ea8a2244dce9d71af7aff5d5e4f3fe096b50e Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Fri, 12 Jun 2015 12:43:18 -0700 Subject: [PATCH] Add libnetwork ipam implementation Signed-off-by: Alessandro Boch --- libnetwork/ipam/allocator.go | 441 ++++++++++++++++++++++++ libnetwork/ipam/allocator_test.go | 543 ++++++++++++++++++++++++++++++ 2 files changed, 984 insertions(+) create mode 100644 libnetwork/ipam/allocator.go create mode 100644 libnetwork/ipam/allocator_test.go diff --git a/libnetwork/ipam/allocator.go b/libnetwork/ipam/allocator.go new file mode 100644 index 0000000000..3c52853b8a --- /dev/null +++ b/libnetwork/ipam/allocator.go @@ -0,0 +1,441 @@ +package ipam + +import ( + "fmt" + "net" + + "github.com/docker/libnetwork/bitseq" +) + +const ( + // The biggest configurable host subnets + minNetSize = 8 + minNetSizeV6 = 64 + // The effective network size for v6 + minNetSizeV6Eff = 96 + // The size of the host subnet used internally, it's the most granular sequence addresses + defaultInternalHostSize = 16 +) + +// Allocator provides per address space ipv4/ipv6 book keeping +type Allocator struct { + // The internal subnets host size + internalHostSize int + // Static subnet information + subnetsInfo map[subnetKey]*subnetData + // Allocated addresses in each address space's internal subnet + addresses map[isKey]*bitmask +} + +// NewAllocator returns an instance of libnetwork ipam +func NewAllocator() *Allocator { + a := &Allocator{} + a.subnetsInfo = make(map[subnetKey]*subnetData) + a.addresses = make(map[isKey]*bitmask) + a.internalHostSize = defaultInternalHostSize + return a +} + +// Pointer to the configured subnets in each address space +type subnetKey struct { + addressSpace AddressSpace + subnet string +} + +// Pointer to the internal subnets in each address space +type isKey subnetKey + +// The structs contains the configured subnet information +// along with the pointers to the respective internal subnets +type subnetData struct { + info *SubnetInfo // Configured subnet + intSubKeyes []*isKey // Pointers to child internal subnets +} + +// The structs containing the address allocation bitmask for the internal subnet. +// The bitmask is stored a run-length encoded seq.Sequence of 4 bytes blcoks. +type bitmask struct { + subnet *net.IPNet + addressMask *bitseq.Sequence + freeAddresses int +} + +type ipVersion int + +const ( + v4 = 4 + v6 = 6 +) + +/******************* + * IPAMConf Contract + ********************/ + +// AddSubnet adds a subnet for the specified address space +func (a *Allocator) AddSubnet(addrSpace AddressSpace, subnetInfo *SubnetInfo) error { + // Sanity check + if addrSpace == "" { + return ErrInvalidAddressSpace + } + if subnetInfo == nil || subnetInfo.Subnet == nil { + return ErrInvalidSubnet + } + if a.contains(addrSpace, subnetInfo) { + return ErrOverlapSubnet + } + + // Sanity check and size adjustment for v6 + subnetToSplit, err := adjustAndCheckSubnetSize(subnetInfo.Subnet) + if err != nil { + return err + } + + // Convert to smaller internal subnets (if needed) + subnetList, err := getInternalSubnets(subnetToSplit, a.internalHostSize) + if err != nil { + return err + } + + // Store the configured subnet information + subnetKey := subnetKey{addrSpace, subnetInfo.Subnet.String()} + info := &subnetData{info: subnetInfo, intSubKeyes: make([]*isKey, len(subnetList))} + a.subnetsInfo[subnetKey] = info + + // Create and insert the internal subnet(s) addresses masks into the address database + for i, sub := range subnetList { + ones, bits := sub.Mask.Size() + numAddresses := 1 << uint(bits-ones) + + // Create and store internal subnet key into parent subnet handle + smallKey := &isKey{addrSpace, sub.String()} + info.intSubKeyes[i] = smallKey + + // Add the new address masks + a.addresses[*smallKey] = &bitmask{ + subnet: sub, + addressMask: bitseq.New(uint32(numAddresses)), + freeAddresses: numAddresses, + } + } + + return nil +} + +// Check subnets size. In case configured subnet is v6 and host size is +// greater than 32 bits, adjust subnet to /96. +func adjustAndCheckSubnetSize(subnet *net.IPNet) (*net.IPNet, error) { + ones, bits := subnet.Mask.Size() + if v6 == getAddressVersion(subnet.IP) { + if ones < minNetSizeV6 { + return nil, ErrInvalidSubnet + } + if ones < minNetSizeV6Eff { + newMask := net.CIDRMask(minNetSizeV6Eff, bits) + return &net.IPNet{IP: subnet.IP, Mask: newMask}, nil + } + } else { + if ones < minNetSize { + return nil, ErrInvalidSubnet + } + } + return subnet, nil +} + +// Checks whether the passed subnet is a superset or subset of any of the subset in the db +func (a *Allocator) contains(space AddressSpace, subInfo *SubnetInfo) bool { + for k, v := range a.subnetsInfo { + if space == k.addressSpace { + if subInfo.Subnet.Contains(v.info.Subnet.IP) || + v.info.Subnet.Contains(subInfo.Subnet.IP) { + return true + } + } + } + return false +} + +// Splits the passed subnet into N internal subnets with host size equal to internalHostSize. +// If the subnet's host size is equal to or smaller than internalHostSize, there won't be any +// split and the return list will contain only the passed subnet. +func getInternalSubnets(subnet *net.IPNet, internalHostSize int) ([]*net.IPNet, error) { + var subnetList []*net.IPNet + + // Get network/host subnet information + netBits, bits := subnet.Mask.Size() + hostBits := bits - netBits + + extraBits := hostBits - internalHostSize + if extraBits <= 0 { + subnetList = make([]*net.IPNet, 1) + subnetList[0] = subnet + } else { + // Split in smaller internal subnets + numIntSubs := 1 << uint(extraBits) + subnetList = make([]*net.IPNet, numIntSubs) + + // Construct one copy of the internal subnets's mask + intNetBits := bits - internalHostSize + intMask := net.CIDRMask(intNetBits, bits) + + // Construct the prefix portion for each internal subnet + for i := 0; i < numIntSubs; i++ { + intIP := make([]byte, len(subnet.IP)) + copy(intIP, subnet.IP) // IPv6 is too big, just work on the extra portion + addIntToIP(intIP, i< 192.168.53 +func addIntToIP(array []byte, ordinal int) { + for i := len(array) - 1; i >= 0; i-- { + array[i] |= (byte)(ordinal & 0xff) + ordinal >>= 8 + } +} + +// Convert an ordinal to the respective IP address +func ipToInt(ip []byte) int { + value := 0 + for i := 0; i < len(ip); i++ { + j := len(ip) - 1 - i + value += int(ip[i]) << uint(j*8) + } + return value +} + +// Given an address and subnet, returns the host portion address +func getHostPortionIP(address net.IP, subnet *net.IPNet) net.IP { + hostPortion := make([]byte, len(address)) + for i := 0; i < len(subnet.Mask); i++ { + hostPortion[i] = address[i] &^ subnet.Mask[i] + } + return hostPortion +} + +func printLine(head *bitseq.Sequence) { + fmt.Println() + for head != nil { + fmt.Printf("-") + head = head.Next + } +} diff --git a/libnetwork/ipam/allocator_test.go b/libnetwork/ipam/allocator_test.go new file mode 100644 index 0000000000..e72342015e --- /dev/null +++ b/libnetwork/ipam/allocator_test.go @@ -0,0 +1,543 @@ +package ipam + +import ( + "fmt" + "net" + "testing" + "time" + + "github.com/docker/libnetwork/bitseq" +) + +func getAllocator(subnet *net.IPNet) *Allocator { + a := NewAllocator() + a.AddSubnet("default", &SubnetInfo{Subnet: subnet}) + return a +} + +func TestInt2IP2IntConversion(t *testing.T) { + for i := 0; i < 256*256*256; i++ { + var array [4]byte // new array at each cycle + addIntToIP(array[:], i) + j := ipToInt(array[:]) + if j != i { + t.Fatalf("Failed to convert ordinal %d to IP % x and back to ordinal. Got %d", i, array, j) + } + } +} + +func TestIsValid(t *testing.T) { + list := []int{0, 255, 256, 511, 512, 767, 768} + for _, i := range list { + if isValidIP(i) { + t.Fatalf("Failed to detect invalid IPv4 ordinal: %d", i) + } + } + + list = []int{1, 254, 257, 258, 510, 513, 769, 770} + for _, i := range list { + if !isValidIP(i) { + t.Fatalf("Marked valid ipv4 as invalid: %d", i) + } + } +} + +func TestGetAddressVersion(t *testing.T) { + if v4 != getAddressVersion(net.ParseIP("172.28.30.112")) { + t.Fatalf("Failed to detect IPv4 version") + } + if v4 != getAddressVersion(net.ParseIP("0.0.0.1")) { + t.Fatalf("Failed to detect IPv4 version") + } + if v6 != getAddressVersion(net.ParseIP("ff01::1")) { + t.Fatalf("Failed to detect IPv6 version") + } + if v6 != getAddressVersion(net.ParseIP("2001:56::76:51")) { + t.Fatalf("Failed to detect IPv6 version") + } +} + +func TestAddSubnets(t *testing.T) { + a := NewAllocator() + + _, sub0, _ := net.ParseCIDR("10.0.0.0/8") + err := a.AddSubnet("default", &SubnetInfo{Subnet: sub0}) + if err != nil { + t.Fatalf("Unexpected failure in adding subent") + } + + err = a.AddSubnet("abc", &SubnetInfo{Subnet: sub0}) + if err != nil { + t.Fatalf("Unexpected failure in adding overlapping subents to different address spaces") + } + + err = a.AddSubnet("abc", &SubnetInfo{Subnet: sub0}) + if err == nil { + t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub0, sub0) + } + + _, sub1, _ := net.ParseCIDR("10.20.2.0/24") + err = a.AddSubnet("default", &SubnetInfo{Subnet: sub1}) + if err == nil { + t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub0, sub1) + } + + _, sub2, _ := net.ParseCIDR("10.128.0.0/9") + err = a.AddSubnet("default", &SubnetInfo{Subnet: sub2}) + if err == nil { + t.Fatalf("Failed to detect overlapping subnets: %s and %s", sub1, sub2) + } + + _, sub6, err := net.ParseCIDR("1003:1:2:3:4:5:6::/112") + if err != nil { + t.Fatalf("Wrong input, Can't proceed: %s", err.Error()) + } + err = a.AddSubnet("default", &SubnetInfo{Subnet: sub6}) + if err != nil { + t.Fatalf("Failed to add v6 subnet: %s", err.Error()) + } + + _, sub6, err = net.ParseCIDR("1003:1:2:3::/64") + if err != nil { + t.Fatalf("Wrong input, Can't proceed: %s", err.Error()) + } + err = a.AddSubnet("default", &SubnetInfo{Subnet: sub6}) + if err == nil { + t.Fatalf("Failed to detect overlapping v6 subnet") + } +} + +func TestAdjustAndCheckSubnet(t *testing.T) { + _, sub6, _ := net.ParseCIDR("1003:1:2:300::/63") + _, err := adjustAndCheckSubnetSize(sub6) + if err == nil { + t.Fatalf("Failed detect too big v6 subnet") + } + + _, sub, _ := net.ParseCIDR("192.0.0.0/7") + _, err = adjustAndCheckSubnetSize(sub) + if err == nil { + t.Fatalf("Failed detect too big v4 subnet") + } + + subnet := "1004:1:2:6::/64" + _, sub6, _ = net.ParseCIDR(subnet) + subnetToSplit, err := adjustAndCheckSubnetSize(sub6) + if err != nil { + t.Fatalf("Unexpected error returned by adjustAndCheckSubnetSize()") + } + ones, _ := subnetToSplit.Mask.Size() + if ones < minNetSizeV6Eff { + t.Fatalf("Wrong effective network size for %s. Expected: %d. Got: %d", subnet, minNetSizeV6Eff, ones) + } +} + +func TestRemoveSubnet(t *testing.T) { + a := NewAllocator() + + input := []struct { + addrSpace AddressSpace + subnet string + }{ + {"default", "192.168.0.0/16"}, + {"default", "172.17.0.0/16"}, + {"default", "10.0.0.0/8"}, + {"default", "2002:1:2:3:4:5:ffff::/112"}, + {"splane", "172.17.0.0/16"}, + {"splane", "10.0.0.0/8"}, + {"splane", "2002:1:2:3:4:5:6::/112"}, + {"splane", "2002:1:2:3:4:5:ffff::/112"}, + } + + for _, i := range input { + _, sub, err := net.ParseCIDR(i.subnet) + if err != nil { + t.Fatalf("Wrong input, Can't proceed: %s", err.Error()) + } + err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub}) + if err != nil { + t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error()) + } + } + + _, sub, _ := net.ParseCIDR("172.17.0.0/16") + a.RemoveSubnet("default", sub) + if len(a.subnetsInfo) != 7 { + t.Fatalf("Failed to remove subnet info") + } + list := a.getSubnetList("default", v4) + if len(list) != 257 { + t.Fatalf("Failed to effectively remove subnet address space") + } + + _, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:ffff::/112") + a.RemoveSubnet("default", sub) + if len(a.subnetsInfo) != 6 { + t.Fatalf("Failed to remove subnet info") + } + list = a.getSubnetList("default", v6) + if len(list) != 0 { + t.Fatalf("Failed to effectively remove subnet address space") + } + + _, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:6::/112") + a.RemoveSubnet("splane", sub) + if len(a.subnetsInfo) != 5 { + t.Fatalf("Failed to remove subnet info") + } + list = a.getSubnetList("splane", v6) + if len(list) != 1 { + t.Fatalf("Failed to effectively remove subnet address space") + } +} + +func TestGetInternalSubnets(t *testing.T) { + // This function tests the splitting of a parent subnet in small host subnets. + // The splitting is controlled by the max host size, which is the first parameter + // passed to the function. It basically says if the parent subnet host size is + // greater than the max host size, split the parent subnet into N internal small + // subnets with host size = max host size to cover the same address space. + + input := []struct { + internalHostSize int + parentSubnet string + firstIntSubnet string + lastIntSubnet string + }{ + // Test 8 bits prefix network + {24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"}, + {16, "10.0.0.0/8", "10.0.0.0/16", "10.255.0.0/16"}, + {8, "10.0.0.0/8", "10.0.0.0/24", "10.255.255.0/24"}, + // Test 16 bits prefix network + {16, "192.168.0.0/16", "192.168.0.0/16", "192.168.0.0/16"}, + {8, "192.168.0.0/16", "192.168.0.0/24", "192.168.255.0/24"}, + // Test 24 bits prefix network + {16, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"}, + {8, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"}, + // Test non byte multiple host size + {24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"}, + {20, "10.0.0.0/12", "10.0.0.0/12", "10.0.0.0/12"}, + {20, "10.128.0.0/12", "10.128.0.0/12", "10.128.0.0/12"}, + {12, "10.16.0.0/16", "10.16.0.0/20", "10.16.240.0/20"}, + {13, "10.0.0.0/8", "10.0.0.0/19", "10.255.224.0/19"}, + {15, "10.0.0.0/8", "10.0.0.0/17", "10.255.128.0/17"}, + // Test v6 network + {16, "2002:1:2:3:4:5:6000::/110", "2002:1:2:3:4:5:6000:0/112", "2002:1:2:3:4:5:6003:0/112"}, + {16, "2002:1:2:3:4:5:ff00::/104", "2002:1:2:3:4:5:ff00:0/112", "2002:1:2:3:4:5:ffff:0/112"}, + {12, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/116", "2002:1:2:3:4:5:ffff:f000/116"}, + {11, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/117", "2002:1:2:3:4:5:ffff:f800/117"}, + } + + for _, d := range input { + assertInternalSubnet(t, d.internalHostSize, d.parentSubnet, d.firstIntSubnet, d.lastIntSubnet) + } + +} + +func TestGetAddress(t *testing.T) { + input := []string{ + /*"10.0.0.0/8", "10.0.0.0/9", */ "10.0.0.0/10", "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14", + "10.0.0.0/15", "10.0.0.0/16", "10.0.0.0/17", "10.0.0.0/18", "10.0.0.0/19", "10.0.0.0/20", "10.0.0.0/21", + "10.0.0.0/22", "10.0.0.0/23", "10.0.0.0/24", "10.0.0.0/25", "10.0.0.0/26", "10.0.0.0/27", "10.0.0.0/28", + "10.0.0.0/29", "10.0.0.0/30", "10.0.0.0/31"} + + for _, subnet := range input { + assertGetAddress(t, subnet) + } +} + +func TestGetSubnetList(t *testing.T) { + a := NewAllocator() + input := []struct { + addrSpace AddressSpace + subnet string + }{ + {"default", "192.168.0.0/16"}, + {"default", "172.17.0.0/16"}, + {"default", "10.0.0.0/8"}, + {"default", "2002:1:2:3:4:5:6::/112"}, + {"default", "2002:1:2:3:4:5:ffff::/112"}, + {"splane", "172.17.0.0/16"}, + {"splane", "10.0.0.0/8"}, + {"splane", "2002:1:2:3:4:5:ff00::/104"}, + } + + for _, i := range input { + _, sub, err := net.ParseCIDR(i.subnet) + if err != nil { + t.Fatalf("Wrong input, Can't proceed: %s", err.Error()) + } + err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub}) + if err != nil { + t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error()) + } + } + + list := a.getSubnetList("default", v4) + if len(list) != 258 { + t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 258. Got %d.", len(list)) + } + list = a.getSubnetList("splane", v4) + if len(list) != 257 { + t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 257. Got %d.", len(list)) + } + + list = a.getSubnetList("default", v6) + if len(list) != 2 { + t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 2. Got %d.", len(list)) + } + list = a.getSubnetList("splane", v6) + if len(list) != 256 { + t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 256. Got %d.", len(list)) + } + +} + +func TestRequestSyntaxCheck(t *testing.T) { + var ( + a = NewAllocator() + subnet = "192.168.0.0/16" + addSpace = AddressSpace("green") + ) + + // Add subnet and create base request + _, sub, _ := net.ParseCIDR(subnet) + a.AddSubnet(addSpace, &SubnetInfo{Subnet: sub}) + req := &AddressRequest{Subnet: *sub} + + // Empty address space request + _, err := a.Request("", req) + if err == nil { + t.Fatalf("Failed to detect wrong request: empty address space") + } + + // Preferred address from different subnet in request + req.Address = net.ParseIP("172.17.0.23") + _, err = a.Request(addSpace, req) + if err == nil { + t.Fatalf("Failed to detect wrong request: preferred IP from different subnet") + } + + // Preferred address specified and nil subnet + req = &AddressRequest{Address: net.ParseIP("172.17.0.23")} + _, err = a.Request(addSpace, req) + if err == nil { + t.Fatalf("Failed to detect wrong request: subnet not specified but preferred address specified") + } +} + +func TestRequest(t *testing.T) { + // Request N addresses from different size subnets, verifying last request + // returns expected address. Internal subnet host size is Allocator's default, 16 + input := []struct { + subnet string + numReq int + lastIP string + }{ + {"192.168.59.0/24", 254, "192.168.59.254"}, + {"192.168.240.0/20", 254, "192.168.240.254"}, + {"192.168.0.0/16", 254, "192.168.0.254"}, + {"10.16.0.0/16", 254, "10.16.0.254"}, + {"10.128.0.0/12", 254, "10.128.0.254"}, + {"10.0.0.0/8", 254, "10.0.0.254"}, + {"192.168.0.0/16", 256, "192.168.1.2"}, + {"10.0.0.0/8", 256, "10.0.1.2"}, + + {"192.168.128.0/18", 4 * 254, "192.168.131.254"}, + {"192.168.240.0/20", 16 * 254, "192.168.255.254"}, + + {"192.168.0.0/16", 256 * 254, "192.168.255.254"}, + {"10.0.0.0/8", 256 * 254, "10.0.255.254"}, + {"10.0.0.0/8", 257 * 254, "10.1.0.254"}, + //{"10.0.0.0/8", 100 * 256 * 254, "10.99.255.254"}, + } + + for _, d := range input { + assertNRequests(t, d.subnet, d.numReq, d.lastIP) + } +} + +func TestRelease(t *testing.T) { + var ( + err error + req *AddressRequest + subnet = "192.168.0.0/16" + ) + + _, sub, _ := net.ParseCIDR(subnet) + a := getAllocator(sub) + req = &AddressRequest{Subnet: *sub} + bm := a.addresses[isKey{"default", subnet}] + + // Allocate all addresses + for err != ErrNoAvailableIPs { + _, err = a.Request("default", req) + } + + toRelease := []struct { + address string + }{ + {"192.168.0.1"}, + {"192.168.0.2"}, + {"192.168.0.3"}, + {"192.168.0.4"}, + {"192.168.0.5"}, + {"192.168.0.6"}, + {"192.168.0.7"}, + {"192.168.0.8"}, + {"192.168.0.9"}, + {"192.168.0.10"}, + {"192.168.0.30"}, + {"192.168.0.31"}, + {"192.168.1.32"}, + + {"192.168.0.254"}, + {"192.168.1.1"}, + {"192.168.1.2"}, + + {"192.168.1.3"}, + + {"192.168.255.253"}, + {"192.168.255.254"}, + } + + // One by one, relase the address and request again. We should get the same IP + req = &AddressRequest{Subnet: *sub} + for i, inp := range toRelease { + address := net.ParseIP(inp.address) + a.Release("default", address) + if bm.freeAddresses != 1 { + t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.freeAddresses) + } + + rsp, err := a.Request("default", req) + if err != nil { + t.Fatalf("Failed to obtain the address: %s", err.Error()) + } + if !address.Equal(rsp.Address) { + t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", address, rsp.Address) + } + } +} + +func assertInternalSubnet(t *testing.T, hostSize int, bigSubnet, firstSmall, lastSmall string) { + _, subnet, _ := net.ParseCIDR(bigSubnet) + list, _ := getInternalSubnets(subnet, hostSize) + count := 1 + ones, bits := subnet.Mask.Size() + diff := bits - ones - hostSize + if diff > 0 { + count <<= uint(diff) + } + + if len(list) != count { + t.Fatalf("Wrong small subnets number. Expected: %d, Got: %d", count, len(list)) + } + if firstSmall != list[0].String() { + t.Fatalf("Wrong first small subent. Expected: %v, Got: %v", firstSmall, list[0]) + } + if lastSmall != list[count-1].String() { + t.Fatalf("Wrong last small subent. Expected: %v, Got: %v", lastSmall, list[count-1]) + } +} + +func assertGetAddress(t *testing.T, subnet string) { + var ( + err error + printTime = false + a = &Allocator{} + ) + + _, sub, _ := net.ParseCIDR(subnet) + ones, bits := sub.Mask.Size() + zeroes := bits - ones + numAddresses := 1 << uint(zeroes) + + var expectedMax uint32 + if numAddresses >= 32 { + expectedMax = uint32(1<<32 - 1) + } else { + expectedMax = (1<