Skip to content

Commit

Permalink
Merge pull request #47768 from akerouanton/libnet-ipam-linear-allocator
Browse files Browse the repository at this point in the history
libnet/ipams/default: introduce a linear allocator
  • Loading branch information
akerouanton committed May 23, 2024
2 parents 5f183b9 + 500eff0 commit d16a425
Show file tree
Hide file tree
Showing 29 changed files with 1,197 additions and 696 deletions.
7 changes: 4 additions & 3 deletions daemon/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config // import "github.com/docker/docker/daemon/config"

import (
"encoding/json"
"net/netip"
"os"
"path/filepath"
"reflect"
Expand Down Expand Up @@ -157,7 +158,7 @@ func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) {
emptyConfigFile := makeConfigFile(t, `{}`)
configFile := makeConfigFile(t, `{"default-address-pools":[{"base": "10.123.0.0/16", "size": 24 }]}`)

expected := []*ipamutils.NetworkToSplit{{Base: "10.123.0.0/16", Size: 24}}
expected := []*ipamutils.NetworkToSplit{{Base: netip.MustParsePrefix("10.123.0.0/16"), Size: 24}}

t.Run("empty config file", func(t *testing.T) {
conf := Config{}
Expand All @@ -167,7 +168,7 @@ func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) {

config, err := MergeDaemonConfigurations(&conf, flags, emptyConfigFile)
assert.NilError(t, err)
assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected)
assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected, cmpopts.EquateComparable(netip.Prefix{}))
})

t.Run("config file", func(t *testing.T) {
Expand All @@ -177,7 +178,7 @@ func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) {

config, err := MergeDaemonConfigurations(&conf, flags, configFile)
assert.NilError(t, err)
assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected)
assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected, cmpopts.EquateComparable(netip.Prefix{}))
})

t.Run("with conflicting options", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion daemon/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func (daemon *Daemon) fillDefaultAddressPools(ctx context.Context, v *system.Inf
defer span.End()
for _, pool := range cfg.DefaultAddressPools.Value() {
v.DefaultAddressPools = append(v.DefaultAddressPools, system.NetworkAddressPool{
Base: pool.Base,
Base: pool.Base.String(),
Size: pool.Size,
})
}
Expand Down
8 changes: 7 additions & 1 deletion libnetwork/cnmallocator/drivers_ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cnmallocator

import (
"context"
"fmt"
"net/netip"
"strconv"
"strings"

Expand All @@ -22,8 +24,12 @@ func initIPAMDrivers(r ipamapi.Registerer, netConfig *networkallocator.Config) e
// happens with default address pool option
if netConfig != nil {
for _, p := range netConfig.DefaultAddrPool {
base, err := netip.ParsePrefix(p)
if err != nil {
return fmt.Errorf("invalid prefix %q: %w", p, err)
}
addressPool = append(addressPool, &ipamutils.NetworkToSplit{
Base: p,
Base: base,
Size: int(netConfig.SubnetSize),
})
str.WriteString(p + ",")
Expand Down
26 changes: 16 additions & 10 deletions libnetwork/drivers/bridge/bridge_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (

"github.com/docker/docker/internal/testutils/netnsutils"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/internal/netiputil"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/ipams/defaultipam"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/libnetwork/iptables"
"github.com/docker/docker/libnetwork/netlabel"
Expand Down Expand Up @@ -195,16 +198,19 @@ func compareBindings(a, b []types.PortBinding) bool {
}

func getIPv4Data(t *testing.T) []driverapi.IPAMData {
ipd := driverapi.IPAMData{AddressSpace: "full"}
nw, err := netutils.FindAvailableNetwork(ipamutils.GetLocalScopeDefaultNetworks())
if err != nil {
t.Fatal(err)
}
ipd.Pool = nw
// Set network gateway to X.X.X.1
ipd.Gateway = types.GetIPNetCopy(nw)
ipd.Gateway.IP[len(ipd.Gateway.IP)-1] = 1
return []driverapi.IPAMData{ipd}
t.Helper()

a, _ := defaultipam.NewAllocator(ipamutils.GetLocalScopeDefaultNetworks(), nil)
alloc, err := a.RequestPool(ipamapi.PoolRequest{
AddressSpace: "LocalDefault",
Exclude: netutils.InferReservedNetworks(false),
})
assert.NilError(t, err)

gw, _, err := a.RequestAddress(alloc.PoolID, nil, nil)
assert.NilError(t, err)

return []driverapi.IPAMData{{AddressSpace: "LocalDefault", Pool: netiputil.ToIPNet(alloc.Pool), Gateway: gw}}
}

func getIPv6Data(t *testing.T) []driverapi.IPAMData {
Expand Down
29 changes: 29 additions & 0 deletions libnetwork/internal/netiputil/netiputil.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,32 @@ func AddrPortFromNet(addr net.Addr) netip.AddrPort {
}
return netip.AddrPort{}
}

// LastAddr returns the last address of prefix 'p'.
func LastAddr(p netip.Prefix) netip.Addr {
return ipbits.Add(p.Addr().Prev(), 1, uint(p.Addr().BitLen()-p.Bits()))
}

// PrefixCompare two prefixes and return a negative, 0, or a positive integer as
// required by [slices.SortFunc]. When two prefixes with the same address is
// provided, the shortest one will be sorted first.
func PrefixCompare(a, b netip.Prefix) int {
cmp := a.Addr().Compare(b.Addr())
if cmp != 0 {
return cmp
}
return a.Bits() - b.Bits()
}

// PrefixAfter returns the prefix of size 'sz' right after 'prev'.
func PrefixAfter(prev netip.Prefix, sz int) netip.Prefix {
s := sz
if prev.Bits() < sz {
s = prev.Bits()
}
addr := ipbits.Add(prev.Addr(), 1, uint(prev.Addr().BitLen()-s))
if addr.IsUnspecified() {
return netip.Prefix{}
}
return netip.PrefixFrom(addr, sz).Masked()
}
46 changes: 46 additions & 0 deletions libnetwork/internal/netiputil/netiputil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package netiputil

import (
"net/netip"
"testing"

"gotest.tools/v3/assert"
)

func TestLastAddr(t *testing.T) {
testcases := []struct {
p netip.Prefix
want netip.Addr
}{
{netip.MustParsePrefix("10.0.0.0/24"), netip.MustParseAddr("10.0.0.255")},
{netip.MustParsePrefix("10.0.0.0/8"), netip.MustParseAddr("10.255.255.255")},
{netip.MustParsePrefix("fd00::/64"), netip.MustParseAddr("fd00::ffff:ffff:ffff:ffff")},
{netip.MustParsePrefix("fd00::/16"), netip.MustParseAddr("fd00:ffff:ffff:ffff:ffff:ffff:ffff:ffff")},
{netip.MustParsePrefix("ffff::/16"), netip.MustParseAddr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")},
}

for _, tc := range testcases {
last := LastAddr(tc.p)
assert.Check(t, last == tc.want, "LastAddr(%q) = %s; want: %s", tc.p, last, tc.want)
}
}

func TestPrefixAfter(t *testing.T) {
testcases := []struct {
prev netip.Prefix
sz int
want netip.Prefix
}{
{netip.MustParsePrefix("10.0.10.0/24"), 24, netip.MustParsePrefix("10.0.11.0/24")},
{netip.MustParsePrefix("10.0.10.0/24"), 16, netip.MustParsePrefix("10.1.0.0/16")},
{netip.MustParsePrefix("10.10.0.0/16"), 24, netip.MustParsePrefix("10.11.0.0/24")},
{netip.MustParsePrefix("2001:db8:feed:cafe:b000:dead::/96"), 16, netip.MustParsePrefix("2002::/16")},
{netip.MustParsePrefix("ffff::/16"), 16, netip.Prefix{}},
{netip.MustParsePrefix("2001:db8:1::/48"), 64, netip.MustParsePrefix("2001:db8:2::/64")},
}

for _, tc := range testcases {
next := PrefixAfter(tc.prev, tc.sz)
assert.Check(t, next == tc.want, "PrefixAfter(%q, %d) = %s; want: %s", tc.prev, tc.sz, next, tc.want)
}
}
5 changes: 5 additions & 0 deletions libnetwork/ipamapi/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
ErrIPOutOfRange = types.InvalidParameterErrorf("requested address is out of range")
ErrPoolOverlap = types.ForbiddenErrorf("Pool overlaps with other one on this address space")
ErrBadPool = types.InvalidParameterErrorf("address space does not contain specified address pool")
ErrNoMoreSubnets = types.InvalidParameterErrorf("all predefined address pools have been fully subnetted")
)

// Ipam represents the interface the IPAM service plugins must implement
Expand Down Expand Up @@ -73,6 +74,10 @@ type PoolRequest struct {
// Options is a map of opaque k/v passed to the driver. It's non-mandatory.
// Drivers are free to ignore it.
Options map[string]string
// Exclude is a list of prefixes the requester wish to not be dynamically
// allocated (ie. when Pool isn't specified). It's up to the IPAM driver to
// take it into account, or totally ignore it. It's required to be sorted.
Exclude []netip.Prefix
// V6 indicates which address family should be used to dynamically allocate
// a prefix (ie. when Pool isn't specified).
V6 bool
Expand Down
Loading

0 comments on commit d16a425

Please sign in to comment.