Skip to content

Commit

Permalink
libnet/ipam: split v4/v6 address spaces
Browse files Browse the repository at this point in the history
Address spaces are a continuum of addresses that can be used for a
specific purpose (ie. 'local' for unmanaged containers, 'global for
Swarm). v4 and v6 addresses aren't of the same size -- hence
combining them into a single address space doesn't form a continuum.
Better set them apart into two different address spaces.

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

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
  • Loading branch information
akerouanton committed Apr 26, 2024
1 parent 199c72c commit 37a81cd
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 33 deletions.
13 changes: 2 additions & 11 deletions libnetwork/ipam/address_space.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ipam
import (
"context"
"fmt"
"net"
"net/netip"
"sync"

Expand All @@ -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
}

Expand Down
69 changes: 57 additions & 12 deletions libnetwork/ipam/allocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
20 changes: 10 additions & 10 deletions libnetwork/ipam/allocator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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())
}
Expand Down
4 changes: 4 additions & 0 deletions libnetwork/ipam/structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 37a81cd

Please sign in to comment.