From 26e487db78d729d35ea1bbbca05cd31129d5c8c3 Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Mon, 21 Jul 2025 17:40:38 +0100 Subject: [PATCH 1/2] testutil: Fix sense of hasFwBackendArg check Introduced by commit 39ab393 ("Add daemon option --firewall-backend"). Signed-off-by: Rob Murray --- testutil/daemon/daemon.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testutil/daemon/daemon.go b/testutil/daemon/daemon.go index bf7e98cc83..6c5437f433 100644 --- a/testutil/daemon/daemon.go +++ b/testutil/daemon/daemon.go @@ -530,10 +530,10 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { d.args = append(d.args, "--storage-driver", d.storageDriver) } - hasFwBackendArg := !slices.ContainsFunc(providedArgs, func(s string) bool { + hasFwBackendArg := slices.ContainsFunc(providedArgs, func(s string) bool { return strings.HasPrefix(s, "--firewall-backend") }) - if hasFwBackendArg { + if !hasFwBackendArg { if fw := os.Getenv("DOCKER_FIREWALL_BACKEND"); fw != "" { d.args = append(d.args, "--firewall-backend="+fw) } From 090c319f2ef892fd9e743be6223d226538390bad Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Mon, 21 Jul 2025 17:44:20 +0100 Subject: [PATCH 2/2] Don't allow the daemon to start with nftables and Swarm enabled Signed-off-by: Rob Murray --- daemon/config/config_linux.go | 5 ++++ integration/daemon/daemon_linux_test.go | 29 +++++++++++++++++++++ integration/network/overlay/overlay_test.go | 2 +- integration/system/ping_test.go | 2 +- testutil/daemon/swarm.go | 15 ++++++++--- 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/daemon/config/config_linux.go b/daemon/config/config_linux.go index 1d11a3d3ec..b9278e2c8d 100644 --- a/daemon/config/config_linux.go +++ b/daemon/config/config_linux.go @@ -126,6 +126,11 @@ func (conf *Config) IsSwarmCompatible() error { if conf.LiveRestoreEnabled { return errors.New("--live-restore daemon configuration is incompatible with swarm mode") } + // Swarm has not yet been updated to use nftables. But, if "iptables" is disabled, it + // doesn't add rules anyway. + if conf.FirewallBackend == "nftables" && conf.EnableIPTables { + return errors.New("--firewall-backend=nftables is incompatible with swarm mode") + } return nil } diff --git a/integration/daemon/daemon_linux_test.go b/integration/daemon/daemon_linux_test.go index da68b81332..20d9e2e76d 100644 --- a/integration/daemon/daemon_linux_test.go +++ b/integration/daemon/daemon_linux_test.go @@ -12,6 +12,7 @@ import ( "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" @@ -494,3 +495,31 @@ func createBridge(t *testing.T, ifName string, addrs []string) net.IP { } 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")) + }) +} diff --git a/integration/network/overlay/overlay_test.go b/integration/network/overlay/overlay_test.go index bd11962ffb..bea91666ec 100644 --- a/integration/network/overlay/overlay_test.go +++ b/integration/network/overlay/overlay_test.go @@ -65,7 +65,7 @@ func TestHostPortMappings(t *testing.T) { ctx := setupTest(t) d := daemon.New(t) - d.StartWithBusybox(ctx, t) + d.StartNodeWithBusybox(ctx, t) defer d.Stop(t) d.SwarmInit(ctx, t, swarmtypes.InitRequest{AdvertiseAddr: "127.0.0.1:2377"}) diff --git a/integration/system/ping_test.go b/integration/system/ping_test.go index d57ab1255b..8f26483218 100644 --- a/integration/system/ping_test.go +++ b/integration/system/ping_test.go @@ -60,7 +60,7 @@ func TestPingSwarmHeader(t *testing.T) { ctx := setupTest(t) d := daemon.New(t) - d.Start(t) + d.StartNode(t) defer d.Stop(t) apiClient := d.NewClientT(t) defer apiClient.Close() diff --git a/testutil/daemon/swarm.go b/testutil/daemon/swarm.go index aa8e649744..9eb89fab0e 100644 --- a/testutil/daemon/swarm.go +++ b/testutil/daemon/swarm.go @@ -77,8 +77,8 @@ func (d *Daemon) NodeID() string { return d.CachedInfo.Swarm.NodeID } -// SwarmInit initializes a new swarm cluster. -func (d *Daemon) SwarmInit(ctx context.Context, t testing.TB, req swarm.InitRequest) { +// SwarmInitWithError initializes a new swarm cluster and returns an error. +func (d *Daemon) SwarmInitWithError(ctx context.Context, t testing.TB, req swarm.InitRequest) error { t.Helper() if req.ListenAddr == "" { req.ListenAddr = fmt.Sprintf("%s:%d", d.swarmListenAddr, d.SwarmPort) @@ -93,8 +93,17 @@ func (d *Daemon) SwarmInit(ctx context.Context, t testing.TB, req swarm.InitRequ cli := d.NewClientT(t) defer cli.Close() _, err := cli.SwarmInit(ctx, req) + if err == nil { + d.CachedInfo = d.Info(t) + } + return err +} + +// SwarmInit initializes a new swarm cluster. +func (d *Daemon) SwarmInit(ctx context.Context, t testing.TB, req swarm.InitRequest) { + t.Helper() + err := d.SwarmInitWithError(ctx, t, req) assert.NilError(t, err, "initializing swarm") - d.CachedInfo = d.Info(t) } // SwarmJoin joins a daemon to an existing cluster.