mirror of
https://github.com/moby/moby.git
synced 2026-01-11 10:41:43 +00:00
526 lines
20 KiB
Go
526 lines
20 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/netip"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/internal/nlwrap"
|
|
"github.com/docker/docker/internal/testutils/networking"
|
|
"github.com/docker/docker/testutil"
|
|
"github.com/docker/docker/testutil/daemon"
|
|
"github.com/moby/moby/api/types/network"
|
|
swarmtypes "github.com/moby/moby/api/types/swarm"
|
|
"github.com/vishvananda/netlink"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
"gotest.tools/v3/poll"
|
|
"gotest.tools/v3/skip"
|
|
)
|
|
|
|
// Check that the daemon will start with fixed-cidr set, but no bip.
|
|
// Regression test for https://github.com/moby/moby/issues/45356
|
|
func TestDaemonDefaultBridgeWithFixedCidrButNoBip(t *testing.T) {
|
|
skip.If(t, testEnv.IsRootless, "can't create L3 segment in rootless namespace")
|
|
ctx := testutil.StartSpan(baseContext, t)
|
|
|
|
host, cleanup := newHostInL3Seg(t, "fcnobip", "192.168.130.1", "fd69:d2cd:f7df::1")
|
|
defer cleanup()
|
|
|
|
host.Do(t, func() {
|
|
// Run without OTel because there's no routing from this netns for it - which
|
|
// means the daemon doesn't shut down cleanly, causing the test to fail.
|
|
d := daemon.New(t, daemon.WithEnvVars("OTEL_EXPORTER_OTLP_ENDPOINT="))
|
|
d.StartWithBusybox(ctx, t, "--fixed-cidr", "192.168.130.0/24")
|
|
defer func() {
|
|
d.Stop(t)
|
|
d.Cleanup(t)
|
|
}()
|
|
})
|
|
}
|
|
|
|
// Test fixed-cidr and bip options, with various addresses on the bridge
|
|
// before the daemon starts.
|
|
func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
|
|
skip.If(t, testEnv.IsRootless, "can't create L3 segment in rootless namespace")
|
|
ctx := testutil.StartSpan(baseContext, t)
|
|
|
|
testcases := []defaultBridgeIPAMTestCase{
|
|
{
|
|
name: "no config",
|
|
// No config for the bridge, but override default-address-pools to
|
|
// get a predictable result for IPv6 (rather than the daemon's ULA).
|
|
daemonArgs: []string{
|
|
"--default-address-pool", `base=192.168.176.0/20,size=24`,
|
|
"--default-address-pool", `base=fdd1:8161:2d2c::/56,size=64`,
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/24", Gateway: "192.168.176.1"},
|
|
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::1"},
|
|
},
|
|
},
|
|
{
|
|
name: "fixed-cidr only",
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"},
|
|
{Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64"},
|
|
},
|
|
},
|
|
{
|
|
name: "bip only",
|
|
daemonArgs: []string{
|
|
"--bip", "192.168.176.88/24",
|
|
"--bip6", "fdd1:8161:2d2c::8888/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
|
|
},
|
|
},
|
|
{
|
|
name: "existing bridge address only",
|
|
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
|
|
},
|
|
},
|
|
{
|
|
name: "fixed-cidr within old bridge subnet",
|
|
initialBridgeAddrs: []string{"192.168.176.88/20", "fdd1:8161:2d2c::8888/56"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64",
|
|
},
|
|
// There's no --bip to dictate the subnet, so it's derived from an
|
|
// existing bridge address. If fixed-cidr's subnet is made smaller
|
|
// following a daemon restart, a user might reasonably expect the
|
|
// default bridge network's subnet to shrink to match. However,
|
|
// that has not been the behaviour - instead, only the allocatable
|
|
// range is reduced (as would happen with a user-managed bridge).
|
|
// In this case, if the user wants a smaller subnet, their options
|
|
// are to delete docker0, or supply a --bip. A change in this subtle
|
|
// behaviour might be best. But, it's probably not causing problems,
|
|
// and it'd be a breaking change for anyone relying on the existing
|
|
// behaviour.
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fdd1:8161:2d2c::/56", IPRange: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
|
|
},
|
|
},
|
|
{
|
|
name: "link-local fixed-cidr-v6",
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fe80::/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"},
|
|
{Subnet: "fe80::/64", IPRange: "fe80::/64", Gateway: llGwPlaceholder},
|
|
},
|
|
},
|
|
{
|
|
name: "nonstandard link-local fixed-cidr-v6",
|
|
initialBridgeAddrs: []string{"192.168.176.88/20", "fe80:1234::88/56"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fe80:1234::/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fe80:1234::/56", IPRange: "fe80:1234::/64", Gateway: "fe80:1234::88"},
|
|
},
|
|
},
|
|
{
|
|
name: "fixed-cidr within old bridge subnet with new bip",
|
|
initialBridgeAddrs: []string{"192.168.176.88/20", "fdd1:8161:2d2c::/56"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24", "--bip", "192.168.176.99/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64", "--bip6", "fdd1:8161:2d2c::9999/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24", Gateway: "192.168.176.99"},
|
|
{Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::9999"},
|
|
},
|
|
},
|
|
{
|
|
name: "old bridge subnet within fixed-cidr",
|
|
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/20",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c::/56",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/20", Gateway: "192.168.176.88"},
|
|
{Subnet: "fdd1:8161:2d2c::/56", IPRange: "fdd1:8161:2d2c::/56", Gateway: "fdd1:8161:2d2c::8888"},
|
|
},
|
|
},
|
|
{
|
|
name: "old bridge subnet outside fixed-cidr",
|
|
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.177.0/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c:1::/64",
|
|
},
|
|
// The bridge's address/subnet should be ignored, this is a change
|
|
// of fixed-cidr.
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.177.0/24", IPRange: "192.168.177.0/24"},
|
|
{Subnet: "fdd1:8161:2d2c:1::/64", IPRange: "fdd1:8161:2d2c:1::/64"},
|
|
// No Gateway is configured, because the address could not be learnt from the
|
|
// bridge. An address will have been allocated but, because there's config (the
|
|
// fixed-cidr), inspect shows just the config. (Surprisingly, when there's no
|
|
// config at all, the inspect output still says its showing config but actually
|
|
// shows the running state.) When the daemon is restarted, after a gateway
|
|
// address has been assigned to the bridge, that address will become config - so
|
|
// a Gateway address will show up in the inspect output.
|
|
},
|
|
},
|
|
{
|
|
name: "old bridge subnet outside fixed-cidr with bip",
|
|
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.177.0/24", "--bip", "192.168.177.99/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c:1::/64", "--bip6", "fdd1:8161:2d2c:1::9999/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.177.0/24", IPRange: "192.168.177.0/24", Gateway: "192.168.177.99"},
|
|
{Subnet: "fdd1:8161:2d2c:1::/64", IPRange: "fdd1:8161:2d2c:1::/64", Gateway: "fdd1:8161:2d2c:1::9999"},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range testcases {
|
|
tc.bridgeName = "docker0"
|
|
testDefaultBridgeIPAM(ctx, t, tc)
|
|
}
|
|
}
|
|
|
|
// Like TestDaemonUserDefaultBridgeIPAMDocker0, but with a user-defined/supplied
|
|
// bridge, instead of docker0.
|
|
func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
|
|
skip.If(t, testEnv.IsRootless, "can't create L3 segment in rootless namespace")
|
|
ctx := testutil.StartSpan(baseContext, t)
|
|
|
|
testcases := []defaultBridgeIPAMTestCase{
|
|
{
|
|
name: "bridge only",
|
|
initialBridgeAddrs: []string{"192.168.176.88/20", "fdd1:8161:2d2c::8888/64"},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", Gateway: "192.168.176.88"},
|
|
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
|
|
},
|
|
},
|
|
{
|
|
name: "fixed-cidr only",
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"},
|
|
{Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64"},
|
|
},
|
|
},
|
|
{
|
|
name: "fcidr in bridge subnet and bridge ip in fcidr",
|
|
initialBridgeAddrs: []string{
|
|
"192.168.160.88/20", "192.168.176.88/20", "192.168.192.88/20",
|
|
"fdd1:8161:2d2c::8888/60", "fdd1:8161:2d2c:10::8888/60", "fdd1:8161:2d2c:20::8888/60",
|
|
},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c:10::/64",
|
|
},
|
|
// Selected bip should be the one within fixed-cidr
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fdd1:8161:2d2c:10::/60", IPRange: "fdd1:8161:2d2c:10::/64", Gateway: "fdd1:8161:2d2c:10::8888"},
|
|
},
|
|
},
|
|
{
|
|
name: "fcidr in bridge subnet and bridge ip not in fcidr",
|
|
initialBridgeAddrs: []string{
|
|
"192.168.160.88/20", "192.168.176.88/20", "192.168.192.88/20",
|
|
"fdd1:8161:2d2c::8888/60", "fdd1:8161:2d2c:10::8888/60", "fdd1:8161:2d2c:20::8888/60",
|
|
},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.177.0/24",
|
|
"--fixed-cidr-v6", "fdd1:8161:2d2c:11::8888/64",
|
|
},
|
|
// Selected bridge subnet should be the one that encompasses fixed-cidr.
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", IPRange: "192.168.177.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fdd1:8161:2d2c:10::/60", IPRange: "fdd1:8161:2d2c:11::/64", Gateway: "fdd1:8161:2d2c:10::8888"},
|
|
},
|
|
},
|
|
{
|
|
name: "link-local fixed-cidr-v6",
|
|
initialBridgeAddrs: []string{"192.168.176.88/20"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fe80::/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fe80::/64", IPRange: "fe80::/64", Gateway: llGwPlaceholder},
|
|
},
|
|
},
|
|
{
|
|
name: "nonstandard link-local fixed-cidr-v6",
|
|
initialBridgeAddrs: []string{"192.168.176.88/20", "fe80:1234::88/56"},
|
|
daemonArgs: []string{
|
|
"--fixed-cidr", "192.168.176.0/24",
|
|
"--fixed-cidr-v6", "fe80:1234::/64",
|
|
},
|
|
expIPAMConfig: []network.IPAMConfig{
|
|
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"},
|
|
{Subnet: "fe80:1234::/56", IPRange: "fe80:1234::/64", Gateway: "fe80:1234::88"},
|
|
},
|
|
},
|
|
{
|
|
name: "fixed-cidr bigger than bridge subnet",
|
|
initialBridgeAddrs: []string{"192.168.176.88/24"},
|
|
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/20"},
|
|
ipv4Only: true,
|
|
// fixed-cidr (the range of allocatable addresses) is bigger than the
|
|
// bridge subnet - this is a configuration error, but has historically
|
|
// been allowed. Because IPRange is treated as an offset into Subnet, it
|
|
// would normally result in a docker network that allocated addresses
|
|
// within the selected subnet. So, fixed-cidr is dropped, making the
|
|
// whole subnet allocatable.
|
|
expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"}},
|
|
},
|
|
{
|
|
name: "no bridge ip within fixed-cidr",
|
|
initialBridgeAddrs: []string{"192.168.160.88/20"},
|
|
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/24"},
|
|
ipv4Only: true,
|
|
// fixed-cidr (the range of allocatable addresses) is outside the bridge
|
|
// subnet - this is a configuration error, but has historically been
|
|
// allowed. Because IPRange is treated as an offset into Subnet, it
|
|
// would normally result in a docker network that allocated addresses
|
|
// within the selected subnet. So, fixed-cidr is dropped, making the
|
|
// whole subnet allocatable.
|
|
expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.160.0/20", Gateway: "192.168.160.88"}},
|
|
},
|
|
{
|
|
name: "fixed-cidr contains bridge subnet",
|
|
initialBridgeAddrs: []string{"192.168.177.1/24"},
|
|
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/20"},
|
|
// fixed-cidr (the range of allocatable addresses) is bigger than the
|
|
// bridge subnet, and the bridge's address is not within fixed-cidr.
|
|
// This is a configuration error, but has historically been allowed.
|
|
// Because IPRange is treated as an offset into Subnet, it would
|
|
// normally result in a docker network that allocated addresses
|
|
// within the selected subnet. So, fixed-cidr is dropped, making the
|
|
// whole subnet allocatable.
|
|
ipv4Only: true,
|
|
expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.177.0/24", Gateway: "192.168.177.1"}},
|
|
},
|
|
|
|
{
|
|
name: "fixed-cidr-v6 bigger than bridge subnet",
|
|
initialBridgeAddrs: []string{"fdd1:8161:2d2c::8888/64"},
|
|
daemonArgs: []string{"--fixed-cidr-v6", "fdd1:8161:2d2c::/60"},
|
|
// fixed-cidr-v6 (the range of allocatable addresses) is bigger than the bridge
|
|
// subnet - this is a configuration error. Unlike IPv4, it has not historically
|
|
// been allowed, so it will prevent daemon startup.
|
|
expStartErr: true,
|
|
},
|
|
{
|
|
name: "no bridge ip within fixed-cidr-v6",
|
|
initialBridgeAddrs: []string{"fdd1:8161:2d2c::8888/60"},
|
|
daemonArgs: []string{"--fixed-cidr-v6", "fdd1:8161:2d2c:10::/64"},
|
|
// fixed-cidr-v6 (the range of allocatable addresses) is outside the bridge subnet -
|
|
// this is a configuration error. Unlike IPv4, it has not historically been
|
|
// allowed, so it will prevent daemon startup.
|
|
expStartErr: true,
|
|
},
|
|
{
|
|
name: "fixed-cidr-v6 contains bridge subnet",
|
|
initialBridgeAddrs: []string{"fdd1:8161:2d2c:10::1/64"},
|
|
daemonArgs: []string{"--fixed-cidr-v6", "fdd1:8161:2d2c:10::/60"},
|
|
// fixed-cidr-v6 (the range of allocatable addresses) is bigger than the
|
|
// bridge subnet, and the bridge's address is not within fixed-cidr.
|
|
// This is a configuration error, Unlike IPv4, it has not historically been
|
|
// allowed, so it will prevent daemon startup.
|
|
expStartErr: true,
|
|
},
|
|
}
|
|
for _, tc := range testcases {
|
|
tc.bridgeName = "br-dbi"
|
|
tc.userDefinedBridge = true
|
|
testDefaultBridgeIPAM(ctx, t, tc)
|
|
}
|
|
}
|
|
|
|
// llGwPlaceholder can be used as a value for "Gateway" in expected IPAM config,
|
|
// before comparison with actual results it'll be replaced by the kernel assigned
|
|
// link local IPv6 address for the bridge.
|
|
const llGwPlaceholder = "ll-gateway-placeholder"
|
|
|
|
type defaultBridgeIPAMTestCase struct {
|
|
name string
|
|
bridgeName string
|
|
userDefinedBridge bool
|
|
initialBridgeAddrs []string
|
|
daemonArgs []string
|
|
ipv4Only bool
|
|
expStartErr bool
|
|
expIPAMConfig []network.IPAMConfig
|
|
}
|
|
|
|
func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIPAMTestCase) {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
// Run this test in its own network namespace because it messes with the default
|
|
// bridge and, for example, iptables rules for the default bridge aren't deleted
|
|
// because the network can't be deleted. Then, rules with old addresses may break
|
|
// unrelated tests.
|
|
host, cleanup := newHostInL3Seg(t, "defbripam", "192.168.131.1", "fdf9:2eb5:ba8c::1")
|
|
defer cleanup()
|
|
|
|
host.Do(t, func() {
|
|
llAddr := createBridge(t, tc.bridgeName, tc.initialBridgeAddrs)
|
|
|
|
var dArgs []string
|
|
if !tc.ipv4Only {
|
|
dArgs = append(tc.daemonArgs, "--ipv6")
|
|
}
|
|
if tc.userDefinedBridge {
|
|
// If a bridge is supplied by the user, the daemon should use its addresses
|
|
// to infer --bip (which cannot be specified).
|
|
dArgs = append(dArgs, "--bridge", tc.bridgeName)
|
|
}
|
|
|
|
// Run without OTel because there's no routing from this netns for it - which
|
|
// means the daemon doesn't shut down cleanly, causing the test to fail.
|
|
d := daemon.New(t, daemon.WithEnvVars("OTEL_EXPORTER_OTLP_ENDPOINT="))
|
|
defer func() {
|
|
d.Stop(t)
|
|
d.Cleanup(t)
|
|
}()
|
|
|
|
if tc.expStartErr {
|
|
err := d.StartWithError(dArgs...)
|
|
assert.Check(t, is.ErrorContains(err, "daemon exited during startup"))
|
|
return
|
|
}
|
|
|
|
d.Start(t, dArgs...)
|
|
c := d.NewClientT(t)
|
|
defer c.Close()
|
|
|
|
insp, err := c.NetworkInspect(ctx, network.NetworkBridge, network.InspectOptions{})
|
|
assert.NilError(t, err)
|
|
expIPAMConfig := slices.Clone(tc.expIPAMConfig)
|
|
for i := range expIPAMConfig {
|
|
if expIPAMConfig[i].Gateway == llGwPlaceholder {
|
|
expIPAMConfig[i].Gateway = llAddr.String()
|
|
}
|
|
}
|
|
assert.Check(t, is.DeepEqual(insp.IPAM.Config, expIPAMConfig))
|
|
})
|
|
})
|
|
}
|
|
|
|
func newHostInL3Seg(t *testing.T, name, ip4, ip6 string) (networking.Host, func()) {
|
|
// Set up a netns for each test to avoid sysctl and iptables pollution.
|
|
addr4 := netip.MustParseAddr(ip4)
|
|
addr6 := netip.MustParseAddr(ip6)
|
|
l3 := networking.NewL3Segment(t, "test-"+name,
|
|
netip.PrefixFrom(addr4, 24),
|
|
netip.PrefixFrom(addr6, 64),
|
|
)
|
|
hostname := "host-" + name
|
|
l3.AddHost(t, hostname, "ns-"+name, "eth0",
|
|
netip.PrefixFrom(addr4.Next(), 24),
|
|
netip.PrefixFrom(addr6.Next(), 64),
|
|
)
|
|
return l3.Hosts[hostname], func() { l3.Destroy(t) }
|
|
}
|
|
|
|
// createBridge creates a bridge device named ifName, brings it up, waits for
|
|
// the kernel to assign a link-local IPv6 address, assigns addrs, and returns
|
|
// the kernel-assigned LL address.
|
|
func createBridge(t *testing.T, ifName string, addrs []string) net.IP {
|
|
t.Helper()
|
|
|
|
// Get a netlink handle in this netns.
|
|
nlh, err := nlwrap.NewHandle()
|
|
assert.NilError(t, err)
|
|
defer nlh.Close()
|
|
|
|
link := &netlink.Bridge{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
Name: ifName,
|
|
},
|
|
}
|
|
err = nlh.LinkAdd(link)
|
|
assert.NilError(t, err)
|
|
|
|
// Bring the interface up, and wait for the kernel to assign its link-local
|
|
// address (to cause maximum confusion - the LL address shouldn't be selected
|
|
// as "bip6").
|
|
brLink, err := nlh.LinkByName(ifName)
|
|
assert.NilError(t, err)
|
|
err = nlh.LinkSetUp(brLink)
|
|
assert.NilError(t, err)
|
|
var llAddr net.IP
|
|
poll.WaitOn(t, func(t poll.LogT) poll.Result {
|
|
addrs, err := nlh.AddrList(brLink, netlink.FAMILY_V6)
|
|
if err != nil {
|
|
return poll.Error(err)
|
|
}
|
|
if len(addrs) == 0 {
|
|
return poll.Continue("no IPv6 addresses")
|
|
}
|
|
llAddr = addrs[0].IP
|
|
return poll.Success()
|
|
})
|
|
|
|
for _, addr := range addrs {
|
|
ip, ipNet, err := net.ParseCIDR(addr)
|
|
assert.NilError(t, err)
|
|
ipNet.IP = ip
|
|
err = nlh.AddrAdd(link, &netlink.Addr{IPNet: ipNet})
|
|
assert.NilError(t, err)
|
|
}
|
|
return llAddr
|
|
}
|
|
|
|
// TestSwarmNoNftables checks that, because swarm does not yet have nftables support,
|
|
// it's not possible to:
|
|
// - enable Swarm when nftables is enabled, or to
|
|
// - restart an already Swarm enabled daemon with nftables enabled as well.
|
|
func TestSwarmNoNftables(t *testing.T) {
|
|
ctx := testutil.StartSpan(baseContext, t)
|
|
skip.If(t, testEnv.IsRemoteDaemon)
|
|
skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode")
|
|
|
|
t.Run("start", func(t *testing.T) {
|
|
d := daemon.New(t)
|
|
d.Start(t, "--firewall-backend=nftables")
|
|
defer d.Stop(t)
|
|
err := d.SwarmInitWithError(ctx, t, swarmtypes.InitRequest{AdvertiseAddr: "127.0.0.1:2377"})
|
|
assert.Check(t, is.ErrorContains(err, "--firewall-backend=nftables is incompatible with swarm mode"))
|
|
})
|
|
|
|
t.Run("restart", func(t *testing.T) {
|
|
d := daemon.New(t)
|
|
d.Start(t, "--firewall-backend=iptables")
|
|
defer d.Stop(t)
|
|
d.SwarmInit(ctx, t, swarmtypes.InitRequest{AdvertiseAddr: "127.0.0.1:2377"})
|
|
|
|
err := d.RestartWithError("--firewall-backend=nftables")
|
|
assert.Check(t, is.ErrorContains(err, "daemon exited during startup"))
|
|
})
|
|
}
|