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>
This commit is contained in:
Albin Kerouanton 2024-04-14 18:59:38 +02:00
parent 8cb7558aaf
commit eb78246263
4 changed files with 73 additions and 33 deletions

View file

@ -3,7 +3,6 @@ package ipam
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
@ -25,18 +24,10 @@ type addrSpace struct {
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{
subnets: map[netip.Prefix]*PoolData{},
predefined: pdf,
predefined: predefined,
}, nil
}

View file

@ -22,26 +22,65 @@ const (
// Allocator provides per address space ipv4/ipv6 book keeping
type Allocator struct {
// The address spaces
local, global *addrSpace
local4, local6, global4, global6 *addrSpace
}
// NewAllocator returns an instance of libnetwork ipam
func NewAllocator(lcAs, glAs []*net.IPNet) (*Allocator, error) {
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 {
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 {
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
}
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
func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) {
return localAddressSpace, globalAddressSpace, nil
@ -62,7 +101,7 @@ func (a *Allocator) RequestPool(addressSpace, requestedPool, requestedSubPool st
if addressSpace == "" {
return "", nil, nil, parseErr(ipamapi.ErrInvalidAddressSpace)
}
aSpace, err := a.getAddrSpace(addressSpace)
aSpace, err := a.getAddrSpace(addressSpace, v6)
if err != nil {
return "", nil, nil, err
}
@ -115,7 +154,7 @@ func (a *Allocator) ReleasePool(poolID string) error {
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 {
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
// 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 {
case localAddressSpace:
return a.local, nil
if v6 {
return a.local6, nil
}
return a.local4, nil
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)
}
@ -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)
}
aSpace, err := a.getAddrSpace(k.AddressSpace)
aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
if err != nil {
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)
}
aSpace, err := a.getAddrSpace(k.AddressSpace)
aSpace, err := a.getAddrSpace(k.AddressSpace, k.Is6())
if err != nil {
return err
}

View file

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

View file

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