Browse Source

libnet/ipam: split v4/v6 address spaces

Address spaces are a continuum of addresses that can be used for a
specific purpose (ie. 'local' for unmanaged containers, 'global for
Swarm). As such, combining v4 and v6 prefixes / addresses into a
single address space doesn't make much sense.

Also, the upcoming rewrite of `addrSpace` will benefit from that
split.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
Albin Kerouanton 1 year ago
parent
commit
eb78246263

+ 2 - 11
libnetwork/ipam/address_space.go

@@ -3,7 +3,6 @@ package ipam
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"net"
 	"net/netip"
 	"net/netip"
 	"sync"
 	"sync"
 
 
@@ -25,18 +24,10 @@ type addrSpace struct {
 	mu sync.Mutex
 	mu sync.Mutex
 }
 }
 
 
-func newAddrSpace(predefined []*net.IPNet) (*addrSpace, error) {
-	pdf := make([]netip.Prefix, len(predefined))
-	for i, n := range predefined {
-		var ok bool
-		pdf[i], ok = netiputil.ToPrefix(n)
-		if !ok {
-			return nil, fmt.Errorf("network at index %d (%v) is not in canonical form", i, n)
-		}
-	}
+func newAddrSpace(predefined []netip.Prefix) (*addrSpace, error) {
 	return &addrSpace{
 	return &addrSpace{
 		subnets:    map[netip.Prefix]*PoolData{},
 		subnets:    map[netip.Prefix]*PoolData{},
-		predefined: pdf,
+		predefined: predefined,
 	}, nil
 	}, nil
 }
 }
 
 

+ 57 - 12
libnetwork/ipam/allocator.go

@@ -22,26 +22,65 @@ const (
 // Allocator provides per address space ipv4/ipv6 book keeping
 // Allocator provides per address space ipv4/ipv6 book keeping
 type Allocator struct {
 type Allocator struct {
 	// The address spaces
 	// The address spaces
-	local, global *addrSpace
+	local4, local6, global4, global6 *addrSpace
 }
 }
 
 
 // NewAllocator returns an instance of libnetwork ipam
 // NewAllocator returns an instance of libnetwork ipam
 func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
 func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
 	var (
 	var (
-		a   Allocator
-		err error
+		a                          Allocator
+		err                        error
+		lcAs4, lcAs6, glAs4, glAs6 []netip.Prefix
 	)
 	)
-	a.local, err = newAddrSpace(lcAs)
+
+	lcAs4, lcAs6, err = splitByIPFamily(lcAs)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("could not construct local address space: %w", err)
 		return nil, fmt.Errorf("could not construct local address space: %w", err)
 	}
 	}
-	a.global, err = newAddrSpace(glAs)
+
+	glAs4, glAs6, err = splitByIPFamily(glAs)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("could not construct global address space: %w", err)
 		return nil, fmt.Errorf("could not construct global address space: %w", err)
 	}
 	}
+
+	a.local4, err = newAddrSpace(lcAs4)
+	if err != nil {
+		return nil, fmt.Errorf("could not construct local v4 address space: %w", err)
+	}
+	a.local6, err = newAddrSpace(lcAs6)
+	if err != nil {
+		return nil, fmt.Errorf("could not construct local v6 address space: %w", err)
+	}
+	a.global4, err = newAddrSpace(glAs4)
+	if err != nil {
+		return nil, fmt.Errorf("could not construct global v4 address space: %w", err)
+	}
+	a.global6, err = newAddrSpace(glAs6)
+	if err != nil {
+		return nil, fmt.Errorf("could not construct global v6 address space: %w", err)
+	}
 	return &a, nil
 	return &a, nil
 }
 }
 
 
+func splitByIPFamily(s []*net.IPNet) ([]netip.Prefix, []netip.Prefix, error) {
+	var v4, v6 []netip.Prefix
+
+	for i, n := range s {
+		p, ok := netiputil.ToPrefix(n)
+		if !ok {
+			return []netip.Prefix{}, []netip.Prefix{}, fmt.Errorf("network at index %d (%v) is not in canonical form", i, n)
+		}
+
+		if p.Addr().Is4() {
+			v4 = append(v4, p)
+		} else {
+			v6 = append(v6, p)
+		}
+	}
+
+	return v4, v6, nil
+}
+
 // GetDefaultAddressSpaces returns the local and global default address spaces
 // GetDefaultAddressSpaces returns the local and global default address spaces
 func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) {
 func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) {
 	return localAddressSpace, globalAddressSpace, nil
 	return localAddressSpace, globalAddressSpace, nil
@@ -62,7 +101,7 @@ func (a *Allocator) RequestPool(addressSpace, requestedPool, requestedSubPool st
 	if addressSpace == "" {
 	if addressSpace == "" {
 		return "", nil, nil, parseErr(ipamapi.ErrInvalidAddressSpace)
 		return "", nil, nil, parseErr(ipamapi.ErrInvalidAddressSpace)
 	}
 	}
-	aSpace, err := a.getAddrSpace(addressSpace)
+	aSpace, err := a.getAddrSpace(addressSpace, v6)
 	if err != nil {
 	if err != nil {
 		return "", nil, nil, err
 		return "", nil, nil, err
 	}
 	}
@@ -115,7 +154,7 @@ func (a *Allocator) ReleasePool(poolID string) error {
 		return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
 		return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
 	}
 	}
 
 
-	aSpace, err := a.getAddrSpace(k.AddressSpace)
+	aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -125,12 +164,18 @@ func (a *Allocator) ReleasePool(poolID string) error {
 
 
 // Given the address space, returns the local or global PoolConfig based on whether the
 // Given the address space, returns the local or global PoolConfig based on whether the
 // address space is local or global. AddressSpace locality is registered with IPAM out of band.
 // address space is local or global. AddressSpace locality is registered with IPAM out of band.
-func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) {
+func (a *Allocator) getAddrSpace(as string, v6 bool) (*addrSpace, error) {
 	switch as {
 	switch as {
 	case localAddressSpace:
 	case localAddressSpace:
-		return a.local, nil
+		if v6 {
+			return a.local6, nil
+		}
+		return a.local4, nil
 	case globalAddressSpace:
 	case globalAddressSpace:
-		return a.global, nil
+		if v6 {
+			return a.global6, nil
+		}
+		return a.global4, nil
 	}
 	}
 	return nil, types.InvalidParameterErrorf("cannot find address space %s", as)
 	return nil, types.InvalidParameterErrorf("cannot find address space %s", as)
 }
 }
@@ -170,7 +215,7 @@ func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[s
 		return nil, nil, types.InvalidParameterErrorf("invalid pool id: %s", poolID)
 		return nil, nil, types.InvalidParameterErrorf("invalid pool id: %s", poolID)
 	}
 	}
 
 
-	aSpace, err := a.getAddrSpace(k.AddressSpace)
+	aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
@@ -200,7 +245,7 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
 		return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
 		return types.InvalidParameterErrorf("invalid pool id: %s", poolID)
 	}
 	}
 
 
-	aSpace, err := a.getAddrSpace(k.AddressSpace)
+	aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 10 - 10
libnetwork/ipam/allocator_test.go

@@ -132,7 +132,7 @@ func TestAddReleasePoolID(t *testing.T) {
 	a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
 	a, err := NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), ipamutils.GetGlobalScopeDefaultNetworks())
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
-	_, err = a.getAddrSpace(localAddressSpace)
+	_, err = a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -146,7 +146,7 @@ func TestAddReleasePoolID(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	aSpace, err := a.getAddrSpace(localAddressSpace)
+	aSpace, err := a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -168,7 +168,7 @@ func TestAddReleasePoolID(t *testing.T) {
 		t.Fatalf("Incorrect poolIDs returned %s, %s", pid0, pid1)
 		t.Fatalf("Incorrect poolIDs returned %s, %s", pid0, pid1)
 	}
 	}
 
 
-	aSpace, err = a.getAddrSpace(localAddressSpace)
+	aSpace, err = a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -182,7 +182,7 @@ func TestAddReleasePoolID(t *testing.T) {
 		t.Fatalf("Expected failure in adding sub pool: %v", err)
 		t.Fatalf("Expected failure in adding sub pool: %v", err)
 	}
 	}
 
 
-	aSpace, err = a.getAddrSpace(localAddressSpace)
+	aSpace, err = a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -195,7 +195,7 @@ func TestAddReleasePoolID(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	aSpace, err = a.getAddrSpace(localAddressSpace)
+	aSpace, err = a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -219,7 +219,7 @@ func TestAddReleasePoolID(t *testing.T) {
 		t.Errorf("main pool should still exist. Got poolID %q, want %q", pid00, pid0)
 		t.Errorf("main pool should still exist. Got poolID %q, want %q", pid00, pid0)
 	}
 	}
 
 
-	aSpace, err = a.getAddrSpace(localAddressSpace)
+	aSpace, err = a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -232,7 +232,7 @@ func TestAddReleasePoolID(t *testing.T) {
 		t.Error(err)
 		t.Error(err)
 	}
 	}
 
 
-	aSpace, err = a.getAddrSpace(localAddressSpace)
+	aSpace, err = a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -246,7 +246,7 @@ func TestAddReleasePoolID(t *testing.T) {
 		t.Errorf("Unexpected failure in adding pool: %v", err)
 		t.Errorf("Unexpected failure in adding pool: %v", err)
 	}
 	}
 
 
-	aSpace, err = a.getAddrSpace(localAddressSpace)
+	aSpace, err = a.getAddrSpace(localAddressSpace, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -295,7 +295,7 @@ func TestRemoveSubnet(t *testing.T) {
 		{localAddressSpace, "192.168.0.0/16", false},
 		{localAddressSpace, "192.168.0.0/16", false},
 		{localAddressSpace, "172.17.0.0/16", false},
 		{localAddressSpace, "172.17.0.0/16", false},
 		{localAddressSpace, "10.0.0.0/8", false},
 		{localAddressSpace, "10.0.0.0/8", false},
-		{localAddressSpace, "2001:db8:1:2:3:4:ffff::/112", false},
+		{localAddressSpace, "2001:db8:1:2:3:4:ffff::/112", true},
 		{globalAddressSpace, "172.17.0.0/16", false},
 		{globalAddressSpace, "172.17.0.0/16", false},
 		{globalAddressSpace, "10.0.0.0/8", false},
 		{globalAddressSpace, "10.0.0.0/8", false},
 		{globalAddressSpace, "2001:db8:1:2:3:4:5::/112", true},
 		{globalAddressSpace, "2001:db8:1:2:3:4:5::/112", true},
@@ -929,7 +929,7 @@ func TestRelease(t *testing.T) {
 	for i, inp := range toRelease {
 	for i, inp := range toRelease {
 		ip0 := net.ParseIP(inp.address)
 		ip0 := net.ParseIP(inp.address)
 		a.ReleaseAddress(pid, ip0)
 		a.ReleaseAddress(pid, ip0)
-		bm := a.local.subnets[netip.MustParsePrefix(subnet)].addrs
+		bm := a.local4.subnets[netip.MustParsePrefix(subnet)].addrs
 		if bm.Unselected() != 1 {
 		if bm.Unselected() != 1 {
 			t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
 			t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected())
 		}
 		}

+ 4 - 0
libnetwork/ipam/structures.go

@@ -29,6 +29,10 @@ type SubnetKey struct {
 	Subnet, ChildSubnet netip.Prefix
 	Subnet, ChildSubnet netip.Prefix
 }
 }
 
 
+func (k SubnetKey) Is6() bool {
+	return k.Subnet.Addr().Is6()
+}
+
 // PoolIDFromString creates a new PoolID and populates the SubnetKey object
 // PoolIDFromString creates a new PoolID and populates the SubnetKey object
 // reading it from the given string.
 // reading it from the given string.
 func PoolIDFromString(str string) (pID PoolID, err error) {
 func PoolIDFromString(str string) (pID PoolID, err error) {