Skip to content

Commit

Permalink
Merge pull request moby#47853 from akerouanton/libnet-ipam-default-ula
Browse files Browse the repository at this point in the history
libnet/i/defaultipam: use ULA prefix by default
  • Loading branch information
akerouanton committed Jun 3, 2024
2 parents 58aac77 + d18b88f commit cd38046
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 15 deletions.
39 changes: 37 additions & 2 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ package daemon // import "github.com/docker/docker/daemon"

import (
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"net"
"net/netip"
"os"
"path"
"path/filepath"
"runtime"
"slices"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -60,6 +64,8 @@ import (
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/libnetwork/cluster"
nwconfig "github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/libnetwork/ipbits"
"github.com/docker/docker/pkg/authorization"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/idtools"
Expand Down Expand Up @@ -1461,7 +1467,7 @@ func isBridgeNetworkDisabled(conf *config.Config) bool {
return conf.BridgeConfig.Iface == config.DisableNetworkBridge
}

func (daemon *Daemon) networkOptions(conf *config.Config, pg plugingetter.PluginGetter, activeSandboxes map[string]interface{}) ([]nwconfig.Option, error) {
func (daemon *Daemon) networkOptions(conf *config.Config, pg plugingetter.PluginGetter, hostID string, activeSandboxes map[string]interface{}) ([]nwconfig.Option, error) {
dd := runconfig.DefaultDaemonNetworkMode()

options := []nwconfig.Option{
Expand All @@ -1474,9 +1480,21 @@ func (daemon *Daemon) networkOptions(conf *config.Config, pg plugingetter.Plugin
driverOptions(conf),
}

defaultAddressPools := ipamutils.GetLocalScopeDefaultNetworks()
if len(conf.NetworkConfig.DefaultAddressPools.Value()) > 0 {
options = append(options, nwconfig.OptionDefaultAddressPoolConfig(conf.NetworkConfig.DefaultAddressPools.Value()))
defaultAddressPools = conf.NetworkConfig.DefaultAddressPools.Value()
}
// If the Engine admin don't configure default-address-pools or if they
// don't provide any IPv6 prefix, we derive a ULA prefix from the daemon's
// hostID and add it to the pools. This makes dynamic IPv6 subnet
// allocation possible out-of-the-box.
if !slices.ContainsFunc(defaultAddressPools, func(nw *ipamutils.NetworkToSplit) bool {
return nw.Base.Addr().Is6() && !nw.Base.Addr().Is4In6()
}) {
defaultAddressPools = append(defaultAddressPools, deriveULABaseNetwork(hostID))
}
options = append(options, nwconfig.OptionDefaultAddressPoolConfig(defaultAddressPools))

if conf.LiveRestoreEnabled && len(activeSandboxes) != 0 {
options = append(options, nwconfig.OptionActiveSandboxes(activeSandboxes))
}
Expand All @@ -1487,6 +1505,23 @@ func (daemon *Daemon) networkOptions(conf *config.Config, pg plugingetter.Plugin
return options, nil
}

// deriveULABaseNetwork derives a Global ID from the provided hostID and
// appends it to the ULA prefix (with L bit set) to generate a ULA prefix
// unique to this host. The returned ipamutils.NetworkToSplit is stable over
// time if hostID doesn't change.
//
// This is loosely based on the algorithm described in https://datatracker.ietf.org/doc/html/rfc4193#section-3.2.2.
func deriveULABaseNetwork(hostID string) *ipamutils.NetworkToSplit {
sha := sha256.Sum256([]byte(hostID))
gid := binary.BigEndian.Uint64(sha[:]) & (1<<40 - 1) // Keep the 40 least significant bits.
addr := ipbits.Add(netip.MustParseAddr("fd00::"), gid, 80)

return &ipamutils.NetworkToSplit{
Base: netip.PrefixFrom(addr, 48),
Size: 64,
}
}

// GetCluster returns the cluster
func (daemon *Daemon) GetCluster() Cluster {
return daemon.cluster
Expand Down
28 changes: 28 additions & 0 deletions daemon/daemon_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package daemon // import "github.com/docker/docker/daemon"

import (
"net/netip"
"os"
"path/filepath"
"runtime"
Expand Down Expand Up @@ -313,3 +314,30 @@ func TestFindNetworkErrorType(t *testing.T) {
t.Error("The FindNetwork method MUST always return an error that implements the NotFound interface and is ErrNoSuchNetwork")
}
}

// TestDeriveULABaseNetwork checks that for a given hostID, the derived prefix is stable over time.
func TestDeriveULABaseNetwork(t *testing.T) {
testcases := []struct {
name string
hostID string
expPrefix netip.Prefix
}{
{
name: "Empty hostID",
expPrefix: netip.MustParsePrefix("fd42:98fc:1c14::/48"),
},
{
name: "499d4bc0-b0b3-416f-b1ee-cf6486315593",
hostID: "499d4bc0-b0b3-416f-b1ee-cf6486315593",
expPrefix: netip.MustParsePrefix("fd62:fb69:18af::/48"),
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
nw := deriveULABaseNetwork(tc.hostID)
assert.Equal(t, nw.Base, tc.expPrefix)
assert.Equal(t, nw.Size, 64)
})
}
}
2 changes: 1 addition & 1 deletion daemon/daemon_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ func configureKernelSecuritySupport(config *config.Config, driverName string) er
// network settings. If there's active sandboxes, configuration changes will not
// take effect.
func (daemon *Daemon) initNetworkController(cfg *config.Config, activeSandboxes map[string]interface{}) error {
netOptions, err := daemon.networkOptions(cfg, daemon.PluginStore, activeSandboxes)
netOptions, err := daemon.networkOptions(cfg, daemon.PluginStore, daemon.id, activeSandboxes)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/daemon_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func configureMaxThreads(config *config.Config) error {
}

func (daemon *Daemon) initNetworkController(daemonCfg *config.Config, activeSandboxes map[string]interface{}) error {
netOptions, err := daemon.networkOptions(daemonCfg, nil, nil)
netOptions, err := daemon.networkOptions(daemonCfg, nil, daemon.id, nil)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/reload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ func TestDaemonReloadNetworkDiagnosticPort(t *testing.T) {
},
}

netOptions, err := daemon.networkOptions(&config.Config{CommonConfig: config.CommonConfig{Root: t.TempDir()}}, nil, nil)
netOptions, err := daemon.networkOptions(&config.Config{CommonConfig: config.CommonConfig{Root: t.TempDir()}}, nil, "", nil)
if err != nil {
t.Fatal(err)
}
Expand Down
26 changes: 26 additions & 0 deletions integration/network/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package network

import (
"context"
"net/netip"
"strings"
"testing"
"time"

"github.com/docker/docker/api/types"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions"
ctr "github.com/docker/docker/integration/internal/container"
Expand Down Expand Up @@ -43,3 +45,27 @@ func TestCreateWithMultiNetworks(t *testing.T) {
ifacesWithAddress := strings.Count(res.Stdout.String(), "\n")
assert.Equal(t, ifacesWithAddress, 3)
}

func TestCreateWithIPv6DefaultsToULAPrefix(t *testing.T) {
// On Windows, network creation fails with this error message: Error response from daemon: this request is not supported by the 'windows' ipam driver
skip.If(t, testEnv.DaemonInfo.OSType == "windows")

ctx := setupTest(t)
apiClient := testEnv.APIClient()

const nwName = "testnetula"
network.CreateNoError(ctx, t, apiClient, nwName, network.WithIPv6())
defer network.RemoveNoError(ctx, t, apiClient, nwName)

nw, err := apiClient.NetworkInspect(ctx, "testnetula", types.NetworkInspectOptions{})
assert.NilError(t, err)

for _, ipam := range nw.IPAM.Config {
ipr := netip.MustParsePrefix(ipam.Subnet)
if netip.MustParsePrefix("fd00::/8").Overlaps(ipr) {
return
}
}

t.Fatalf("Network %s has no ULA prefix, expected one.", nwName)
}
4 changes: 0 additions & 4 deletions libnetwork/ipams/defaultipam/allocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ const (
// two optional address pools respectively containing the list of user-defined
// address pools for 'local' and 'global' address spaces.
func Register(ic ipamapi.Registerer, lAddrPools, gAddrPools []*ipamutils.NetworkToSplit) error {
if len(lAddrPools) == 0 {
lAddrPools = ipamutils.GetLocalScopeDefaultNetworks()
}

if len(gAddrPools) == 0 {
gAddrPools = ipamutils.GetGlobalScopeDefaultNetworks()
}
Expand Down
10 changes: 7 additions & 3 deletions libnetwork/libnetwork_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"time"

"github.com/docker/docker/internal/testutils/netnsutils"
"github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/ipams/defaultipam"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/netutils"
"github.com/docker/docker/libnetwork/scope"
Expand Down Expand Up @@ -353,13 +355,14 @@ func TestSRVServiceQuery(t *testing.T) {

defer netnsutils.SetupTestOSContext(t)()

c, err := New(OptionBoltdbWithRandomDBFile(t))
c, err := New(OptionBoltdbWithRandomDBFile(t),
config.OptionDefaultAddressPoolConfig(ipamutils.GetLocalScopeDefaultNetworks()))
if err != nil {
t.Fatal(err)
}
defer c.Stop()

n, err := c.NewNetwork("bridge", "net1", "", nil)
n, err := c.NewNetwork("bridge", "net1", "")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -451,7 +454,8 @@ func TestServiceVIPReuse(t *testing.T) {

defer netnsutils.SetupTestOSContext(t)()

c, err := New(OptionBoltdbWithRandomDBFile(t))
c, err := New(OptionBoltdbWithRandomDBFile(t),
config.OptionDefaultAddressPoolConfig(ipamutils.GetLocalScopeDefaultNetworks()))
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 2 additions & 0 deletions libnetwork/libnetwork_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/ipams/defaultipam"
"github.com/docker/docker/libnetwork/ipams/null"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/osl"
Expand Down Expand Up @@ -58,6 +59,7 @@ func newController(t *testing.T) *libnetwork.Controller {
"EnableIPForwarding": true,
},
}),
config.OptionDefaultAddressPoolConfig(ipamutils.GetLocalScopeDefaultNetworks()),
)
if err != nil {
t.Fatal(err)
Expand Down
8 changes: 6 additions & 2 deletions libnetwork/resolver_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import (
"testing"

"github.com/docker/docker/internal/testutils/netnsutils"
"github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/miekg/dns"
)

// test only works on linux
func TestDNSIPQuery(t *testing.T) {
defer netnsutils.SetupTestOSContext(t)()
c, err := New(OptionBoltdbWithRandomDBFile(t))
c, err := New(OptionBoltdbWithRandomDBFile(t),
config.OptionDefaultAddressPoolConfig(ipamutils.GetLocalScopeDefaultNetworks()))
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -110,7 +113,8 @@ func TestDNSProxyServFail(t *testing.T) {
osctx := netnsutils.SetupTestOSContextEx(t)
defer osctx.Cleanup(t)

c, err := New(OptionBoltdbWithRandomDBFile(t))
c, err := New(OptionBoltdbWithRandomDBFile(t),
config.OptionDefaultAddressPoolConfig(ipamutils.GetLocalScopeDefaultNetworks()))
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 2 additions & 0 deletions libnetwork/sandbox_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/docker/docker/internal/testutils/netnsutils"
"github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/ipams/defaultipam"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/osl"
Expand All @@ -24,6 +25,7 @@ func getTestEnv(t *testing.T, opts ...[]NetworkOption) (*Controller, []*Network)
config.OptionDriverConfig(netType, map[string]any{
netlabel.GenericData: options.Generic{"EnableIPForwarding": true},
}),
config.OptionDefaultAddressPoolConfig(ipamutils.GetLocalScopeDefaultNetworks()),
)
if err != nil {
t.Fatal(err)
Expand Down
5 changes: 4 additions & 1 deletion libnetwork/service_common_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import (
"testing"

"github.com/docker/docker/internal/testutils/netnsutils"
"github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/ipamutils"
"gotest.tools/v3/assert"
)

func TestCleanupServiceDiscovery(t *testing.T) {
defer netnsutils.SetupTestOSContext(t)()
c, err := New(OptionBoltdbWithRandomDBFile(t))
c, err := New(OptionBoltdbWithRandomDBFile(t),
config.OptionDefaultAddressPoolConfig(ipamutils.GetLocalScopeDefaultNetworks()))
assert.NilError(t, err)
defer c.Stop()

Expand Down

0 comments on commit cd38046

Please sign in to comment.