Browse code

Test a container attached to l3-ipvlan and bridge networks

Check that when a container has endpoints in an l3-ipvlan and
another network type (bridge), there's no longer any clash
betwen the ipvlan's connected default route and the bridge's
default gateway.

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

Rob Murray authored on 2024/12/20 01:52:08
Showing 1 changed files
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"github.com/docker/docker/integration/internal/container"
19 19
 	"github.com/docker/docker/integration/internal/network"
20 20
 	"github.com/docker/docker/internal/testutils/networking"
21
+	"github.com/docker/docker/libnetwork/netlabel"
21 22
 	"github.com/docker/docker/testutil"
22 23
 	"github.com/docker/docker/testutil/daemon"
23 24
 	"gotest.tools/v3/assert"
... ...
@@ -387,9 +388,83 @@ func checkCtrRoutes(t *testing.T, ctx context.Context, apiClient client.APIClien
387 387
 		return s == ""
388 388
 	})
389 389
 
390
-	assert.Equal(t, len(routes), expRoutes, "expected %d routes, got %d:\n%s", expRoutes, len(routes), strings.Join(routes, "\n"))
390
+	assert.Check(t, is.Equal(len(routes), expRoutes), "expected %d routes, got %d:\n%s", expRoutes, len(routes), strings.Join(routes, "\n"))
391 391
 	defFound := slices.ContainsFunc(routes, func(s string) bool {
392 392
 		return strings.Contains(s, expDefRoute)
393 393
 	})
394
-	assert.Assert(t, defFound, "default route %q not found:\n%s", expDefRoute, strings.Join(routes, "\n"))
394
+	assert.Check(t, defFound, "default route %q not found:\n%s", expDefRoute, strings.Join(routes, "\n"))
395
+}
396
+
397
+// TestMixL3IPVlanAndBridge checks that a container can be connected to a layer-3
398
+// ipvlan network as well as a bridge ... the bridge network will set up a
399
+// default gateway, if selected as the gateway endpoint. The ipvlan driver sets
400
+// up a connected route to 0.0.0.0 or [::], a route via a specific interface with
401
+// no next-hop address (because the next-hop address can't be ARP'd to determine
402
+// the interface). These two types of route cannot be set up at the same time.
403
+// So, the ipvlan's route must be treated like the default gateway and only get
404
+// set up when the ipvlan is selected as the gateway endpoint.
405
+// Regression test for https://github.com/moby/moby/issues/48576
406
+func TestMixL3IPVlanAndBridge(t *testing.T) {
407
+	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no ipvlan on Windows")
408
+	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.48"), "gw-priority requires API v1.48")
409
+	skip.If(t, testEnv.IsRootless, "can't see the dummy parent interface from the rootless namespace")
410
+
411
+	ctx := testutil.StartSpan(baseContext, t)
412
+	d := daemon.New(t)
413
+	d.StartWithBusybox(ctx, t)
414
+	defer d.Stop(t)
415
+	c := d.NewClientT(t)
416
+	defer c.Close()
417
+
418
+	const brNetName = "brnet"
419
+	network.CreateNoError(ctx, t, c, brNetName,
420
+		network.WithOption(netlabel.ContainerIfacePrefix, "bri"),
421
+		network.WithIPv6(),
422
+		network.WithIPAM("192.168.123.0/24", "192.168.123.1"),
423
+		network.WithIPAM("fd6f:36f8:3005::/64", "fd6f:36f8:3005::1"),
424
+	)
425
+	defer network.RemoveNoError(ctx, t, c, brNetName)
426
+
427
+	// Create a dummy parent interface rather than letting the driver do it because,
428
+	// when the driver creates its own, it becomes a '--internal' network and no
429
+	// default route is configured.
430
+	const parentIfName = "di-dummy0"
431
+	CreateMasterDummy(ctx, t, parentIfName)
432
+	defer DeleteInterface(ctx, t, parentIfName)
433
+
434
+	const ipvNetName = "ipvnet"
435
+	network.CreateNoError(ctx, t, c, ipvNetName,
436
+		network.WithDriver("ipvlan"),
437
+		network.WithOption("ipvlan_mode", "l3"),
438
+		network.WithOption("parent", parentIfName),
439
+		network.WithIPv6(),
440
+		network.WithIPAM("192.168.124.0/24", ""),
441
+		network.WithIPAM("fd7d:8755:51ba::/64", ""),
442
+	)
443
+	defer network.RemoveNoError(ctx, t, c, ipvNetName)
444
+
445
+	// Create a container connected to both networks, bridge network acting as gateway.
446
+	ctrId := container.Run(ctx, t, c,
447
+		container.WithNetworkMode(brNetName),
448
+		container.WithEndpointSettings(brNetName,
449
+			&networktypes.EndpointSettings{GwPriority: 1},
450
+		),
451
+		container.WithEndpointSettings(ipvNetName, &networktypes.EndpointSettings{}),
452
+	)
453
+	defer container.Remove(ctx, t, c, ctrId, containertypes.RemoveOptions{Force: true})
454
+	// Expect three IPv4 routes: the default, plus one per network.
455
+	checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET, 3, "default via 192.168.123.1 dev bri")
456
+	// Expect seven IPv6 routes: the default, plus UL, LL, and multicast routes per network.
457
+	checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET6, 7, "default via fd6f:36f8:3005::1 dev bri")
458
+
459
+	// Disconnect the bridge network, expect the ipvlan's default route to be set up.
460
+	c.NetworkDisconnect(ctx, brNetName, ctrId, false)
461
+	checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET, 2, "default dev eth")
462
+	checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET6, 4, "default dev eth")
463
+
464
+	// Reconnect the bridge network, with gw-priority=1 so it gets the gateway back.
465
+	// Expect the ipvlan's default route to be removed.
466
+	c.NetworkConnect(ctx, brNetName, ctrId, &networktypes.EndpointSettings{GwPriority: 1})
467
+	checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET, 3, "default via 192.168.123.1 dev bri")
468
+	checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET6, 7, "default via fd6f:36f8:3005::1 dev bri")
395 469
 }