These tests create iptables rules for different addresses on
docker0 but, unlike tests that do that for user-defined bridges,
those rules aren't removed when the test deletes the network,
because the default bridge network can't be deleted.
So, use (abuse) the L3Segment code to run the tests in their
own network namespace.
Signed-off-by: Rob Murray <rob.murray@docker.com>
| ... | ... |
@@ -3,43 +3,45 @@ package daemon // import "github.com/docker/docker/integration/daemon" |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"net" |
| 6 |
+ "net/netip" |
|
| 6 | 7 |
"testing" |
| 7 | 8 |
|
| 8 | 9 |
"github.com/docker/docker/api/types/network" |
| 10 |
+ "github.com/docker/docker/internal/nlwrap" |
|
| 11 |
+ "github.com/docker/docker/internal/testutils/networking" |
|
| 9 | 12 |
"github.com/docker/docker/testutil" |
| 10 | 13 |
"github.com/docker/docker/testutil/daemon" |
| 11 | 14 |
"github.com/vishvananda/netlink" |
| 12 | 15 |
"gotest.tools/v3/assert" |
| 13 | 16 |
is "gotest.tools/v3/assert/cmp" |
| 14 |
- "gotest.tools/v3/icmd" |
|
| 15 | 17 |
"gotest.tools/v3/skip" |
| 16 | 18 |
) |
| 17 | 19 |
|
| 20 |
+// Check that the daemon will start with fixed-cidr set, but no bip. |
|
| 21 |
+// Regression test for https://github.com/moby/moby/issues/45356 |
|
| 18 | 22 |
func TestDaemonDefaultBridgeWithFixedCidrButNoBip(t *testing.T) {
|
| 23 |
+ skip.If(t, testEnv.IsRootless, "can't create L3 segment in rootless namespace") |
|
| 19 | 24 |
ctx := testutil.StartSpan(baseContext, t) |
| 20 | 25 |
|
| 21 |
- bridgeName := "ext-bridge1" |
|
| 22 |
- d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_CREATE_DEFAULT_BRIDGE="+bridgeName))
|
|
| 23 |
- defer func() {
|
|
| 24 |
- d.Stop(t) |
|
| 25 |
- d.Cleanup(t) |
|
| 26 |
- }() |
|
| 26 |
+ host, cleanup := newHostInL3Seg(t, "fcnobip", "192.168.130.1", "fd69:d2cd:f7df::1") |
|
| 27 |
+ defer cleanup() |
|
| 27 | 28 |
|
| 28 |
- defer func() {
|
|
| 29 |
- // No need to clean up when running this test in rootless mode, as the |
|
| 30 |
- // interface is deleted when the daemon is stopped and the netns |
|
| 31 |
- // reclaimed by the kernel. |
|
| 32 |
- if !testEnv.IsRootless() {
|
|
| 33 |
- deleteInterface(t, bridgeName) |
|
| 34 |
- } |
|
| 35 |
- }() |
|
| 36 |
- d.StartWithBusybox(ctx, t, "--bridge", bridgeName, "--fixed-cidr", "192.168.130.0/24") |
|
| 29 |
+ host.Do(t, func() {
|
|
| 30 |
+ // Run without OTel because there's no routing from this netns for it - which |
|
| 31 |
+ // means the daemon doesn't shut down cleanly, causing the test to fail. |
|
| 32 |
+ d := daemon.New(t, daemon.WithEnvVars("OTEL_EXPORTER_OTLP_ENDPOINT="))
|
|
| 33 |
+ d.StartWithBusybox(ctx, t, "--fixed-cidr", "192.168.130.0/24") |
|
| 34 |
+ defer func() {
|
|
| 35 |
+ d.Stop(t) |
|
| 36 |
+ d.Cleanup(t) |
|
| 37 |
+ }() |
|
| 38 |
+ }) |
|
| 37 | 39 |
} |
| 38 | 40 |
|
| 39 | 41 |
// Test fixed-cidr and bip options, with various addresses on the bridge |
| 40 | 42 |
// before the daemon starts. |
| 41 | 43 |
func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
|
| 42 |
- skip.If(t, testEnv.IsRootless, "can't create test bridge in rootless namespace") |
|
| 44 |
+ skip.If(t, testEnv.IsRootless, "can't create L3 segment in rootless namespace") |
|
| 43 | 45 |
ctx := testutil.StartSpan(baseContext, t) |
| 44 | 46 |
|
| 45 | 47 |
testcases := []defaultBridgeIPAMTestCase{
|
| ... | ... |
@@ -168,6 +170,7 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
|
| 168 | 168 |
}, |
| 169 | 169 |
} |
| 170 | 170 |
for _, tc := range testcases {
|
| 171 |
+ tc.bridgeName = "docker0" |
|
| 171 | 172 |
testDefaultBridgeIPAM(ctx, t, tc) |
| 172 | 173 |
} |
| 173 | 174 |
} |
| ... | ... |
@@ -175,7 +178,7 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
|
| 175 | 175 |
// Like TestDaemonUserDefaultBridgeIPAMDocker0, but with a user-defined/supplied |
| 176 | 176 |
// bridge, instead of docker0. |
| 177 | 177 |
func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
|
| 178 |
- skip.If(t, testEnv.IsRootless, "can't create test bridge in rootless namespace") |
|
| 178 |
+ skip.If(t, testEnv.IsRootless, "can't create L3 segment in rootless namespace") |
|
| 179 | 179 |
ctx := testutil.StartSpan(baseContext, t) |
| 180 | 180 |
|
| 181 | 181 |
testcases := []defaultBridgeIPAMTestCase{
|
| ... | ... |
@@ -301,6 +304,7 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
|
| 301 | 301 |
}, |
| 302 | 302 |
} |
| 303 | 303 |
for _, tc := range testcases {
|
| 304 |
+ tc.bridgeName = "br-dbi" |
|
| 304 | 305 |
tc.userDefinedBridge = true |
| 305 | 306 |
testDefaultBridgeIPAM(ctx, t, tc) |
| 306 | 307 |
} |
| ... | ... |
@@ -308,6 +312,7 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
|
| 308 | 308 |
|
| 309 | 309 |
type defaultBridgeIPAMTestCase struct {
|
| 310 | 310 |
name string |
| 311 |
+ bridgeName string |
|
| 311 | 312 |
userDefinedBridge bool |
| 312 | 313 |
initialBridgeAddrs []string |
| 313 | 314 |
daemonArgs []string |
| ... | ... |
@@ -319,71 +324,89 @@ type defaultBridgeIPAMTestCase struct {
|
| 319 | 319 |
func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIPAMTestCase) {
|
| 320 | 320 |
t.Run(tc.name, func(t *testing.T) {
|
| 321 | 321 |
ctx := testutil.StartSpan(ctx, t) |
| 322 |
- const bridgeName = "br-dbi" |
|
| 323 | 322 |
|
| 324 |
- createBridge(t, bridgeName, tc.initialBridgeAddrs) |
|
| 325 |
- defer deleteInterface(t, bridgeName) |
|
| 323 |
+ // Run this test in its own network namespace because it messes with the default |
|
| 324 |
+ // bridge and, for example, iptables rules for the default bridge aren't deleted |
|
| 325 |
+ // because the network can't be deleted. Then, rules with old addresses may break |
|
| 326 |
+ // unrelated tests. |
|
| 327 |
+ host, cleanup := newHostInL3Seg(t, "defbripam", "192.168.131.1", "fdf9:2eb5:ba8c::1") |
|
| 328 |
+ defer cleanup() |
|
| 326 | 329 |
|
| 327 |
- var dOpts []daemon.Option |
|
| 328 |
- var dArgs []string |
|
| 329 |
- if !tc.ipv4Only {
|
|
| 330 |
- dArgs = append(tc.daemonArgs, "--ipv6") |
|
| 331 |
- } |
|
| 332 |
- if tc.userDefinedBridge {
|
|
| 333 |
- // If a bridge is supplied by the user, the daemon should use its addresses |
|
| 334 |
- // to infer --bip (which cannot be specified). |
|
| 335 |
- dArgs = append(dArgs, "--bridge", bridgeName) |
|
| 336 |
- } else {
|
|
| 337 |
- // The bridge is created and managed by docker, it's always called "docker0", |
|
| 338 |
- // unless this test-only env var is set - to avoid conflict with the docker0 |
|
| 339 |
- // belonging to the daemon started in CI runs. |
|
| 340 |
- dOpts = append(dOpts, daemon.WithEnvVars("DOCKER_TEST_CREATE_DEFAULT_BRIDGE="+bridgeName))
|
|
| 341 |
- } |
|
| 330 |
+ host.Do(t, func() {
|
|
| 331 |
+ createBridge(t, tc.bridgeName, tc.initialBridgeAddrs) |
|
| 342 | 332 |
|
| 343 |
- d := daemon.New(t, dOpts...) |
|
| 344 |
- defer func() {
|
|
| 345 |
- d.Stop(t) |
|
| 346 |
- d.Cleanup(t) |
|
| 347 |
- }() |
|
| 333 |
+ var dArgs []string |
|
| 334 |
+ if !tc.ipv4Only {
|
|
| 335 |
+ dArgs = append(tc.daemonArgs, "--ipv6") |
|
| 336 |
+ } |
|
| 337 |
+ if tc.userDefinedBridge {
|
|
| 338 |
+ // If a bridge is supplied by the user, the daemon should use its addresses |
|
| 339 |
+ // to infer --bip (which cannot be specified). |
|
| 340 |
+ dArgs = append(dArgs, "--bridge", tc.bridgeName) |
|
| 341 |
+ } |
|
| 348 | 342 |
|
| 349 |
- if tc.expStartErr {
|
|
| 350 |
- err := d.StartWithError(dArgs...) |
|
| 351 |
- assert.Check(t, is.ErrorContains(err, "daemon exited during startup")) |
|
| 352 |
- return |
|
| 353 |
- } |
|
| 343 |
+ // Run without OTel because there's no routing from this netns for it - which |
|
| 344 |
+ // means the daemon doesn't shut down cleanly, causing the test to fail. |
|
| 345 |
+ d := daemon.New(t, daemon.WithEnvVars("OTEL_EXPORTER_OTLP_ENDPOINT="))
|
|
| 346 |
+ defer func() {
|
|
| 347 |
+ d.Stop(t) |
|
| 348 |
+ d.Cleanup(t) |
|
| 349 |
+ }() |
|
| 354 | 350 |
|
| 355 |
- d.Start(t, dArgs...) |
|
| 356 |
- c := d.NewClientT(t) |
|
| 357 |
- defer c.Close() |
|
| 351 |
+ if tc.expStartErr {
|
|
| 352 |
+ err := d.StartWithError(dArgs...) |
|
| 353 |
+ assert.Check(t, is.ErrorContains(err, "daemon exited during startup")) |
|
| 354 |
+ return |
|
| 355 |
+ } |
|
| 358 | 356 |
|
| 359 |
- insp, err := c.NetworkInspect(ctx, network.NetworkBridge, network.InspectOptions{})
|
|
| 360 |
- assert.NilError(t, err) |
|
| 361 |
- assert.Check(t, is.DeepEqual(insp.IPAM.Config, tc.expIPAMConfig)) |
|
| 357 |
+ d.Start(t, dArgs...) |
|
| 358 |
+ c := d.NewClientT(t) |
|
| 359 |
+ defer c.Close() |
|
| 360 |
+ |
|
| 361 |
+ insp, err := c.NetworkInspect(ctx, network.NetworkBridge, network.InspectOptions{})
|
|
| 362 |
+ assert.NilError(t, err) |
|
| 363 |
+ assert.Check(t, is.DeepEqual(insp.IPAM.Config, tc.expIPAMConfig)) |
|
| 364 |
+ }) |
|
| 362 | 365 |
}) |
| 363 | 366 |
} |
| 364 | 367 |
|
| 368 |
+func newHostInL3Seg(t *testing.T, name, ip4, ip6 string) (networking.Host, func()) {
|
|
| 369 |
+ // Set up a netns for each test to avoid sysctl and iptables pollution. |
|
| 370 |
+ addr4 := netip.MustParseAddr(ip4) |
|
| 371 |
+ addr6 := netip.MustParseAddr(ip6) |
|
| 372 |
+ l3 := networking.NewL3Segment(t, "test-"+name, |
|
| 373 |
+ netip.PrefixFrom(addr4, 24), |
|
| 374 |
+ netip.PrefixFrom(addr6, 64), |
|
| 375 |
+ ) |
|
| 376 |
+ hostname := "host-" + name |
|
| 377 |
+ l3.AddHost(t, hostname, "ns-"+name, "eth0", |
|
| 378 |
+ netip.PrefixFrom(addr4.Next(), 24), |
|
| 379 |
+ netip.PrefixFrom(addr6.Next(), 64), |
|
| 380 |
+ ) |
|
| 381 |
+ return l3.Hosts[hostname], func() { l3.Destroy(t) }
|
|
| 382 |
+} |
|
| 383 |
+ |
|
| 365 | 384 |
func createBridge(t *testing.T, ifName string, addrs []string) {
|
| 366 | 385 |
t.Helper() |
| 367 | 386 |
|
| 387 |
+ // Get a netlink handle in this netns. |
|
| 388 |
+ nlh, err := nlwrap.NewHandle() |
|
| 389 |
+ assert.NilError(t, err) |
|
| 390 |
+ defer nlh.Close() |
|
| 391 |
+ |
|
| 368 | 392 |
link := &netlink.Bridge{
|
| 369 | 393 |
LinkAttrs: netlink.LinkAttrs{
|
| 370 | 394 |
Name: ifName, |
| 371 | 395 |
}, |
| 372 | 396 |
} |
| 373 | 397 |
|
| 374 |
- err := netlink.LinkAdd(link) |
|
| 398 |
+ err = nlh.LinkAdd(link) |
|
| 375 | 399 |
assert.NilError(t, err) |
| 376 | 400 |
for _, addr := range addrs {
|
| 377 | 401 |
ip, ipNet, err := net.ParseCIDR(addr) |
| 378 | 402 |
assert.NilError(t, err) |
| 379 | 403 |
ipNet.IP = ip |
| 380 |
- err = netlink.AddrAdd(link, &netlink.Addr{IPNet: ipNet})
|
|
| 404 |
+ err = nlh.AddrAdd(link, &netlink.Addr{IPNet: ipNet})
|
|
| 381 | 405 |
assert.NilError(t, err) |
| 382 | 406 |
} |
| 383 | 407 |
} |
| 384 |
- |
|
| 385 |
-func deleteInterface(t *testing.T, ifName string) {
|
|
| 386 |
- icmd.RunCommand("ip", "link", "delete", ifName).Assert(t, icmd.Success)
|
|
| 387 |
- icmd.RunCommand("iptables", "-t", "nat", "--flush").Assert(t, icmd.Success)
|
|
| 388 |
- icmd.RunCommand("iptables", "--flush").Assert(t, icmd.Success)
|
|
| 389 |
-} |