From ad16986cd8043e029a74e5a9da1ea1950bf97726 Mon Sep 17 00:00:00 2001 From: Robert Pothier Date: Tue, 20 Feb 2018 09:53:25 -0500 Subject: [PATCH] Remove subnet size restriction for IPv6 RangeSize was restricting IPv6 subnets to a /66 due to the logic using a uint64. This is not practical for IPv6. This change removes the /64 restriction, but also sets a limit on the range that can be allocated, so that the bitmap will not grow too large. --- .../core/service/ipallocator/allocator.go | 13 +- .../service/ipallocator/allocator_test.go | 189 ++++++++++-------- 2 files changed, 121 insertions(+), 81 deletions(-) diff --git a/pkg/registry/core/service/ipallocator/allocator.go b/pkg/registry/core/service/ipallocator/allocator.go index b480418c2f30..bb5c2984df5b 100644 --- a/pkg/registry/core/service/ipallocator/allocator.go +++ b/pkg/registry/core/service/ipallocator/allocator.go @@ -262,11 +262,18 @@ func calculateIPOffset(base *big.Int, ip net.IP) int { // RangeSize returns the size of a range in valid addresses. func RangeSize(subnet *net.IPNet) int64 { ones, bits := subnet.Mask.Size() - if bits == 32 && (bits-ones) >= 31 || bits == 128 && (bits-ones) >= 63 { + if bits == 32 && (bits-ones) >= 31 || bits == 128 && (bits-ones) >= 127 { return 0 } - max := int64(1) << uint(bits-ones) - return max + // For IPv6, the max size will be limited to 65536 + // This is due to the allocator keeping track of all the + // allocated IP's in a bitmap. This will keep the size of + // the bitmap to 64k. + if bits == 128 && (bits-ones) >= 16 { + return int64(1) << uint(16) + } else { + return int64(1) << uint(bits-ones) + } } // GetIndexedIP returns a net.IP that is subnet.IP + index in the contiguous IP space. diff --git a/pkg/registry/core/service/ipallocator/allocator_test.go b/pkg/registry/core/service/ipallocator/allocator_test.go index fa29e4e17335..4a5e9f540f1c 100644 --- a/pkg/registry/core/service/ipallocator/allocator_test.go +++ b/pkg/registry/core/service/ipallocator/allocator_test.go @@ -25,88 +25,121 @@ import ( ) func TestAllocate(t *testing.T) { - _, cidr, err := net.ParseCIDR("192.168.1.0/24") - if err != nil { - t.Fatal(err) - } - r := NewCIDRRange(cidr) - t.Logf("base: %v", r.base.Bytes()) - if f := r.Free(); f != 254 { - t.Errorf("unexpected free %d", f) - } - if f := r.Used(); f != 0 { - t.Errorf("unexpected used %d", f) + testCases := []struct { + name string + cidr string + free int + released string + outOfRange1 string + outOfRange2 string + outOfRange3 string + alreadyAllocated string + }{ + { + name: "IPv4", + cidr: "192.168.1.0/24", + free: 254, + released: "192.168.1.5", + outOfRange1: "192.168.0.1", + outOfRange2: "192.168.1.0", + outOfRange3: "192.168.1.255", + alreadyAllocated: "192.168.1.1", + }, + { + name: "IPv6", + cidr: "2001:db8:1::/48", + free: 65534, + released: "2001:db8:1::5", + outOfRange1: "2001:db8::1", + outOfRange2: "2001:db8:1::", + outOfRange3: "2001:db8:1::ffff", + alreadyAllocated: "2001:db8:1::1", + }, } - found := sets.NewString() - count := 0 - for r.Free() > 0 { - ip, err := r.AllocateNext() + for _, tc := range testCases { + _, cidr, err := net.ParseCIDR(tc.cidr) if err != nil { - t.Fatalf("error @ %d: %v", count, err) + t.Fatal(err) } - count++ - if !cidr.Contains(ip) { - t.Fatalf("allocated %s which is outside of %s", ip, cidr) + r := NewCIDRRange(cidr) + t.Logf("base: %v", r.base.Bytes()) + if f := r.Free(); f != tc.free { + t.Errorf("Test %s unexpected free %d", tc.name, f) } - if found.Has(ip.String()) { - t.Fatalf("allocated %s twice @ %d", ip, count) + if f := r.Used(); f != 0 { + t.Errorf("Test %s unexpected used %d", tc.name, f) + } + found := sets.NewString() + count := 0 + for r.Free() > 0 { + ip, err := r.AllocateNext() + if err != nil { + t.Fatalf("Test %s error @ %d: %v", tc.name, count, err) + } + count++ + if !cidr.Contains(ip) { + t.Fatalf("Test %s allocated %s which is outside of %s", tc.name, ip, cidr) + } + if found.Has(ip.String()) { + t.Fatalf("Test %s allocated %s twice @ %d", tc.name, ip, count) + } + found.Insert(ip.String()) + } + if _, err := r.AllocateNext(); err != ErrFull { + t.Fatal(err) } - found.Insert(ip.String()) - } - if _, err := r.AllocateNext(); err != ErrFull { - t.Fatal(err) - } - released := net.ParseIP("192.168.1.5") - if err := r.Release(released); err != nil { - t.Fatal(err) - } - if f := r.Free(); f != 1 { - t.Errorf("unexpected free %d", f) - } - if f := r.Used(); f != 253 { - t.Errorf("unexpected free %d", f) - } - ip, err := r.AllocateNext() - if err != nil { - t.Fatal(err) - } - if !released.Equal(ip) { - t.Errorf("unexpected %s : %s", ip, released) - } + released := net.ParseIP(tc.released) + if err := r.Release(released); err != nil { + t.Fatal(err) + } + if f := r.Free(); f != 1 { + t.Errorf("Test %s unexpected free %d", tc.name, f) + } + if f := r.Used(); f != (tc.free - 1) { + t.Errorf("Test %s unexpected free %d", tc.name, f) + } + ip, err := r.AllocateNext() + if err != nil { + t.Fatal(err) + } + if !released.Equal(ip) { + t.Errorf("Test %s unexpected %s : %s", tc.name, ip, released) + } - if err := r.Release(released); err != nil { - t.Fatal(err) - } - err = r.Allocate(net.ParseIP("192.168.0.1")) - if _, ok := err.(*ErrNotInRange); !ok { - t.Fatal(err) - } - if err := r.Allocate(net.ParseIP("192.168.1.1")); err != ErrAllocated { - t.Fatal(err) - } - err = r.Allocate(net.ParseIP("192.168.1.0")) - if _, ok := err.(*ErrNotInRange); !ok { - t.Fatal(err) - } - err = r.Allocate(net.ParseIP("192.168.1.255")) - if _, ok := err.(*ErrNotInRange); !ok { - t.Fatal(err) - } - if f := r.Free(); f != 1 { - t.Errorf("unexpected free %d", f) - } - if f := r.Used(); f != 253 { - t.Errorf("unexpected free %d", f) - } - if err := r.Allocate(released); err != nil { - t.Fatal(err) - } - if f := r.Free(); f != 0 { - t.Errorf("unexpected free %d", f) - } - if f := r.Used(); f != 254 { - t.Errorf("unexpected free %d", f) + if err := r.Release(released); err != nil { + t.Fatal(err) + } + err = r.Allocate(net.ParseIP(tc.outOfRange1)) + if _, ok := err.(*ErrNotInRange); !ok { + t.Fatal(err) + } + if err := r.Allocate(net.ParseIP(tc.alreadyAllocated)); err != ErrAllocated { + t.Fatal(err) + } + err = r.Allocate(net.ParseIP(tc.outOfRange2)) + if _, ok := err.(*ErrNotInRange); !ok { + t.Fatal(err) + } + err = r.Allocate(net.ParseIP(tc.outOfRange3)) + if _, ok := err.(*ErrNotInRange); !ok { + t.Fatal(err) + } + if f := r.Free(); f != 1 { + t.Errorf("Test %s unexpected free %d", tc.name, f) + } + if f := r.Used(); f != (tc.free - 1) { + t.Errorf("Test %s unexpected free %d", tc.name, f) + } + if err := r.Allocate(released); err != nil { + t.Fatal(err) + } + if f := r.Free(); f != 0 { + t.Errorf("Test %s unexpected free %d", tc.name, f) + } + if f := r.Used(); f != tc.free { + t.Errorf("Test %s unexpected free %d", tc.name, f) + } } } @@ -183,12 +216,12 @@ func TestRangeSize(t *testing.T) { }, { name: "supported IPv6 cidr", - cidr: "2001:db8::/98", - addrs: 1073741824, + cidr: "2001:db8::/48", + addrs: 65536, }, { name: "unsupported IPv6 mask", - cidr: "2001:db8::/65", + cidr: "2001:db8::/1", addrs: 0, }, }