Browse code

Make integration tests ready for nftables

Signed-off-by: Rob Murray <rob.murray@docker.com>

Rob Murray authored on 2025/04/25 23:45:27
Showing 8 changed files
... ...
@@ -734,6 +734,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) {
734 734
 
735 735
 func testLiveRestoreUserChainsSetup(t *testing.T) {
736 736
 	skip.If(t, testEnv.IsRootless(), "rootless daemon uses it's own network namespace")
737
+	skip.If(t, testEnv.FirewallBackendDriver() == "nftables", "nftables enabled, skipping iptables test")
737 738
 
738 739
 	t.Parallel()
739 740
 	ctx := testutil.StartSpan(baseContext, t)
... ...
@@ -306,17 +306,30 @@ func TestFilterForwardPolicy(t *testing.T) {
306 306
 			)
307 307
 			host := l3.Hosts[hostname]
308 308
 
309
-			getFwdPolicy := func(cmd string) string {
309
+			getFwdPolicy := func(usingNftables bool, fam string) string {
310 310
 				t.Helper()
311
-				out := host.MustRun(t, cmd, "-S", "FORWARD")
312
-				if strings.HasPrefix(out, "-P FORWARD ACCEPT") {
313
-					return "ACCEPT"
314
-				}
315
-				if strings.HasPrefix(out, "-P FORWARD DROP") {
316
-					return "DROP"
311
+				if usingNftables {
312
+					out := host.MustRun(t, "nft", "list chain "+fam+" docker-bridges filter-FORWARD")
313
+					if strings.Contains(out, "policy accept") {
314
+						return "ACCEPT"
315
+					}
316
+					if strings.Contains(out, "policy drop") {
317
+						return "DROP"
318
+					}
319
+					t.Fatalf("Failed to determine nftables filter-FORWARD policy: %s", out)
320
+					return ""
321
+				} else {
322
+					cmd := fam + "tables"
323
+					out := host.MustRun(t, cmd, "-S", "FORWARD")
324
+					if strings.HasPrefix(out, "-P FORWARD ACCEPT") {
325
+						return "ACCEPT"
326
+					}
327
+					if strings.HasPrefix(out, "-P FORWARD DROP") {
328
+						return "DROP"
329
+					}
330
+					t.Fatalf("Failed to determine %s FORWARD policy: %s", cmd, out)
331
+					return ""
317 332
 				}
318
-				t.Fatalf("Failed to determine %s FORWARD policy: %s", cmd, out)
319
-				return ""
320 333
 			}
321 334
 
322 335
 			type sysctls struct{ v4, v6def, v6all string }
... ...
@@ -342,21 +355,22 @@ func TestFilterForwardPolicy(t *testing.T) {
342 342
 				d.StartWithBusybox(ctx, t, tc.daemonArgs...)
343 343
 				t.Cleanup(func() { d.Stop(t) })
344 344
 			})
345
+			usingNftables := d.FirewallBackendDriver(t) == "nftables"
345 346
 			c := d.NewClientT(t)
346 347
 			t.Cleanup(func() { c.Close() })
347 348
 
348 349
 			// If necessary, the IPv4 policy should have been updated when the default bridge network was created.
349
-			assert.Check(t, is.Equal(getFwdPolicy("iptables"), tc.expPolicy))
350
+			assert.Check(t, is.Equal(getFwdPolicy(usingNftables, "ip"), tc.expPolicy))
350 351
 			// IPv6 policy should not have been updated yet.
351
-			assert.Check(t, is.Equal(getFwdPolicy("ip6tables"), "ACCEPT"))
352
+			assert.Check(t, is.Equal(getFwdPolicy(usingNftables, "ip6"), "ACCEPT"))
352 353
 			assert.Check(t, is.Equal(getSysctls(), sysctls{tc.expForwarding, tc.initForwarding, tc.initForwarding}))
353 354
 
354 355
 			// If necessary, creating an IPv6 network should update the sysctls and policy.
355 356
 			const netName = "testnetffp"
356 357
 			network.CreateNoError(ctx, t, c, netName, network.WithIPv6())
357 358
 			t.Cleanup(func() { network.RemoveNoError(ctx, t, c, netName) })
358
-			assert.Check(t, is.Equal(getFwdPolicy("iptables"), tc.expPolicy))
359
-			assert.Check(t, is.Equal(getFwdPolicy("ip6tables"), tc.expPolicy))
359
+			assert.Check(t, is.Equal(getFwdPolicy(usingNftables, "ip"), tc.expPolicy))
360
+			assert.Check(t, is.Equal(getFwdPolicy(usingNftables, "ip6"), tc.expPolicy))
360 361
 			assert.Check(t, is.Equal(getSysctls(), sysctls{tc.expForwarding, tc.expForwarding, tc.expForwarding}))
361 362
 		})
362 363
 	}
... ...
@@ -217,6 +217,7 @@ var iptCmds = map[iptCmdType][]string{
217 217
 func TestBridgeIptablesDoc(t *testing.T) {
218 218
 	skip.If(t, networking.FirewalldRunning(), "can't document iptables rules, running under firewalld")
219 219
 	skip.If(t, testEnv.IsRootless)
220
+	skip.If(t, testEnv.FirewallBackendDriver() == "nftables")
220 221
 	ctx := setupTest(t)
221 222
 
222 223
 	// Get the full path for "bundles/TestBridgeIptablesDoc".
... ...
@@ -93,7 +93,14 @@ func TestHostIPv4BridgeLabel(t *testing.T) {
93 93
 	assert.NilError(t, err)
94 94
 	assert.Assert(t, len(out.IPAM.Config) > 0)
95 95
 	// Make sure the SNAT rule exists
96
-	testutil.RunCommand(ctx, "iptables", "-t", "nat", "-C", "POSTROUTING", "-s", out.IPAM.Config[0].Subnet, "!", "-o", bridgeName, "-j", "SNAT", "--to-source", ipv4SNATAddr).Assert(t, icmd.Success)
96
+	if testEnv.FirewallBackendDriver() == "nftables" {
97
+		chain := testutil.RunCommand(ctx, "nft", "--stateless", "list", "chain", "ip", "docker-bridges", "nat-postrouting-out__hostIPv4Bridge").Combined()
98
+		exp := fmt.Sprintf(`oifname != "hostIPv4Bridge" ip saddr %s counter snat to %s comment "SNAT"`,
99
+			out.IPAM.Config[0].Subnet, ipv4SNATAddr)
100
+		assert.Check(t, is.Contains(chain, exp))
101
+	} else {
102
+		testutil.RunCommand(ctx, "iptables", "-t", "nat", "-C", "POSTROUTING", "-s", out.IPAM.Config[0].Subnet, "!", "-o", bridgeName, "-j", "SNAT", "--to-source", ipv4SNATAddr).Assert(t, icmd.Success)
103
+	}
97 104
 }
98 105
 
99 106
 func TestDefaultNetworkOpts(t *testing.T) {
... ...
@@ -510,23 +510,26 @@ func TestRoutedAccessToPublishedPort(t *testing.T) {
510 510
 	ctx := setupTest(t)
511 511
 
512 512
 	testcases := []struct {
513
-		name          string
514
-		userlandProxy bool
515
-		skipINC       bool
516
-		expResponse   bool
513
+		name                string
514
+		userlandProxy       bool
515
+		skipINC             bool
516
+		expResponseIptables bool
517
+		expResponseNftables bool
517 518
 	}{
518 519
 		{
519
-			name:          "proxy=true/skipICC=false",
520
-			userlandProxy: true,
521
-			expResponse:   true,
520
+			name:                "proxy=true/skipINC=false",
521
+			userlandProxy:       true,
522
+			expResponseIptables: true,
523
+			expResponseNftables: true,
522 524
 		},
523 525
 		{
524
-			name: "proxy=false/skipICC=false",
526
+			name:                "proxy=false/skipINC=false",
527
+			expResponseNftables: true,
525 528
 		},
526 529
 		{
527
-			name:        "proxy=false/skipICC=true",
528
-			skipINC:     true,
529
-			expResponse: true,
530
+			name:                "proxy=false/skipINC=true",
531
+			skipINC:             true,
532
+			expResponseIptables: true,
530 533
 		},
531 534
 	}
532 535
 
... ...
@@ -535,6 +538,10 @@ func TestRoutedAccessToPublishedPort(t *testing.T) {
535 535
 			d := daemon.New(t)
536 536
 			d.StartWithBusybox(ctx, t, "--ipv6", "--userland-proxy="+strconv.FormatBool(tc.userlandProxy))
537 537
 			defer d.Stop(t)
538
+			usingNftables := d.FirewallBackendDriver(t) == "nftables"
539
+			if usingNftables && tc.skipINC {
540
+				t.Skip("Skipping iptables skip-INC test, using nftables")
541
+			}
538 542
 
539 543
 			c := d.NewClientT(t)
540 544
 			defer c.Close()
... ...
@@ -603,7 +610,7 @@ func TestRoutedAccessToPublishedPort(t *testing.T) {
603 603
 						container.WithNetworkMode(routedNetName),
604 604
 						container.WithCmd("wget", "-O-", "-T3", url),
605 605
 					)
606
-					if tc.expResponse {
606
+					if (usingNftables && tc.expResponseNftables) || (!usingNftables && tc.expResponseIptables) {
607 607
 						// 404 Not Found means the server responded, but it's got nothing to serve.
608 608
 						assert.Check(t, is.Contains(res.Stderr.String(), "404 Not Found"), "url: %s", url)
609 609
 					} else {
... ...
@@ -1032,16 +1039,23 @@ func TestNoIP6Tables(t *testing.T) {
1032 1032
 			id := container.Run(ctx, t, c, container.WithNetworkMode(netName))
1033 1033
 			defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
1034 1034
 
1035
-			res, err := exec.Command("/usr/sbin/ip6tables-save").CombinedOutput()
1035
+			var cmd *exec.Cmd
1036
+			if d.FirewallBackendDriver(t) == "nftables" {
1037
+				cmd = exec.Command("nft", "list", "table", "ip6", "docker-bridges")
1038
+			} else {
1039
+				cmd = exec.Command("/usr/sbin/ip6tables-save")
1040
+			}
1041
+			res, err := cmd.CombinedOutput()
1036 1042
 			assert.NilError(t, err)
1043
+			dump := string(res)
1037 1044
 			if tc.expIPTables {
1038
-				assert.Check(t, is.Contains(string(res), subnet))
1039
-				assert.Check(t, is.Contains(string(res), bridgeName))
1045
+				assert.Check(t, is.Contains(dump, subnet))
1046
+				assert.Check(t, is.Contains(dump, bridgeName))
1040 1047
 			} else {
1041
-				assert.Check(t, !strings.Contains(string(res), subnet),
1042
-					fmt.Sprintf("Didn't expect to find '%s' in '%s'", subnet, string(res)))
1043
-				assert.Check(t, !strings.Contains(string(res), bridgeName),
1044
-					fmt.Sprintf("Didn't expect to find '%s' in '%s'", bridgeName, string(res)))
1048
+				assert.Check(t, !strings.Contains(dump, subnet),
1049
+					fmt.Sprintf("Didn't expect to find '%s' in '%s'", subnet, dump))
1050
+				assert.Check(t, !strings.Contains(dump, bridgeName),
1051
+					fmt.Sprintf("Didn't expect to find '%s' in '%s'", bridgeName, dump))
1045 1052
 			}
1046 1053
 		})
1047 1054
 	}
... ...
@@ -1690,6 +1704,8 @@ func TestNetworkInspectGateway(t *testing.T) {
1690 1690
 func TestDropInForwardChain(t *testing.T) {
1691 1691
 	skip.If(t, networking.FirewalldRunning(), "can't use firewalld in host netns to add rules in L3Segment")
1692 1692
 	skip.If(t, testEnv.IsRootless, "rootless has its own netns")
1693
+	skip.If(t, !strings.Contains(testEnv.FirewallBackendDriver(), "iptables"),
1694
+		"test is iptables specific, and iptables isn't in use")
1693 1695
 
1694 1696
 	// Run the test in its own netns, to avoid interfering with iptables on the test host.
1695 1697
 	const l3SegHost = "difc"
... ...
@@ -1228,6 +1228,8 @@ func getContainerStdout(t *testing.T, ctx context.Context, c *client.Client, ctr
1228 1228
 // See https://github.com/moby/moby/issues/49557
1229 1229
 func TestSkipRawRules(t *testing.T) {
1230 1230
 	skip.If(t, networking.FirewalldRunning(), "can't use firewalld in host netns to add rules in L3Segment")
1231
+	skip.If(t, !strings.Contains(testEnv.FirewallBackendDriver(), "iptables"),
1232
+		"test is iptables specific, and iptables isn't in use")
1231 1233
 	skip.If(t, testEnv.IsRootless, "can't use L3Segment, or check iptables rules")
1232 1234
 
1233 1235
 	testcases := []struct {
... ...
@@ -184,6 +184,7 @@ func TestSwarmScopedNetFromConfig(t *testing.T) {
184 184
 func TestDockerIngressChainPosition(t *testing.T) {
185 185
 	skip.If(t, testEnv.IsRemoteDaemon)
186 186
 	skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode")
187
+	skip.If(t, testEnv.FirewallBackendDriver() == "nftables")
187 188
 	skip.If(t, networking.FirewalldRunning(), "can't use firewalld in host netns to add rules in L3Segment")
188 189
 	ctx := setupTest(t)
189 190
 
... ...
@@ -232,3 +232,12 @@ func (e *Execution) GitHubActions() bool {
232 232
 func (e *Execution) NotAmd64() bool {
233 233
 	return e.DaemonVersion.Arch != "amd64"
234 234
 }
235
+
236
+// FirewallBackendDriver returns the value of FirewallBackend.Driver from
237
+// system Info if set, else the empty string.
238
+func (e *Execution) FirewallBackendDriver() string {
239
+	if e.DaemonInfo.FirewallBackend == nil {
240
+		return ""
241
+	}
242
+	return e.DaemonInfo.FirewallBackend.Driver
243
+}