github.com/moby/docker@v26.1.3+incompatible/integration/networking/bridge_test.go (about) 1 package networking 2 3 import ( 4 "context" 5 "fmt" 6 "regexp" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 containertypes "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/integration/internal/container" 14 "github.com/docker/docker/integration/internal/network" 15 "github.com/docker/docker/testutil" 16 "github.com/docker/docker/testutil/daemon" 17 "github.com/google/go-cmp/cmp/cmpopts" 18 "gotest.tools/v3/assert" 19 is "gotest.tools/v3/assert/cmp" 20 "gotest.tools/v3/skip" 21 ) 22 23 // TestBridgeICC tries to ping container ctr1 from container ctr2 using its hostname. Thus, this test checks: 24 // 1. DNS resolution ; 2. ARP/NDP ; 3. whether containers can communicate with each other ; 4. kernel-assigned SLAAC 25 // addresses. 26 func TestBridgeICC(t *testing.T) { 27 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 28 29 ctx := setupTest(t) 30 31 d := daemon.New(t) 32 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 33 defer d.Stop(t) 34 35 c := d.NewClientT(t) 36 defer c.Close() 37 38 testcases := []struct { 39 name string 40 bridgeOpts []func(*types.NetworkCreate) 41 ctr1MacAddress string 42 isIPv6 bool 43 isLinkLocal bool 44 pingHost string 45 }{ 46 { 47 name: "IPv4 non-internal network", 48 bridgeOpts: []func(*types.NetworkCreate){}, 49 }, 50 { 51 name: "IPv4 internal network", 52 bridgeOpts: []func(*types.NetworkCreate){ 53 network.WithInternal(), 54 }, 55 }, 56 { 57 name: "IPv6 ULA on non-internal network", 58 bridgeOpts: []func(*types.NetworkCreate){ 59 network.WithIPv6(), 60 network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"), 61 }, 62 isIPv6: true, 63 }, 64 { 65 name: "IPv6 ULA on internal network", 66 bridgeOpts: []func(*types.NetworkCreate){ 67 network.WithIPv6(), 68 network.WithInternal(), 69 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 70 }, 71 isIPv6: true, 72 }, 73 { 74 name: "IPv6 link-local address on non-internal network", 75 bridgeOpts: []func(*types.NetworkCreate){ 76 network.WithIPv6(), 77 // There's no real way to specify an IPv6 network is only used with SLAAC link-local IPv6 addresses. 78 // What we can do instead, is to tell the IPAM driver to assign addresses from the link-local prefix. 79 // Each container will have two link-local addresses: 1. a SLAAC address assigned by the kernel ; 80 // 2. the one dynamically assigned by the IPAM driver. 81 network.WithIPAM("fe80::/64", "fe80::1"), 82 }, 83 isLinkLocal: true, 84 isIPv6: true, 85 }, 86 { 87 name: "IPv6 link-local address on internal network", 88 bridgeOpts: []func(*types.NetworkCreate){ 89 network.WithIPv6(), 90 network.WithInternal(), 91 // See the note above about link-local addresses. 92 network.WithIPAM("fe80::/64", "fe80::1"), 93 }, 94 isLinkLocal: true, 95 isIPv6: true, 96 }, 97 { 98 // As for 'LL non-internal', ping the container by name instead of by address 99 // - the busybox test containers only have one interface with a link local 100 // address, so the zone index is not required: 101 // RFC-4007, section 6: "[...] for nodes with only a single non-loopback 102 // interface (e.g., a single Ethernet interface), the common case, link-local 103 // addresses need not be qualified with a zone index." 104 // So, for this common case, LL addresses should be included in DNS config. 105 name: "IPv6 link-local address on non-internal network ping by name", 106 bridgeOpts: []func(*types.NetworkCreate){ 107 network.WithIPv6(), 108 network.WithIPAM("fe80::/64", "fe80::1"), 109 }, 110 isIPv6: true, 111 }, 112 { 113 name: "IPv6 nonstandard link-local subnet on non-internal network ping by name", 114 // No interfaces apart from the one on the bridge network with this non-default 115 // subnet will be on this link local subnet (it's not currently possible to 116 // configure two networks with the same LL subnet, although perhaps it should 117 // be). So, again, no zone index is required and the LL address should be 118 // included in DNS config. 119 bridgeOpts: []func(*types.NetworkCreate){ 120 network.WithIPv6(), 121 network.WithIPAM("fe80:1234::/64", "fe80:1234::1"), 122 }, 123 isIPv6: true, 124 }, 125 { 126 name: "IPv6 non-internal network with SLAAC LL address", 127 bridgeOpts: []func(*types.NetworkCreate){ 128 network.WithIPv6(), 129 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 130 }, 131 // Link-local address is derived from the MAC address, so we need to 132 // specify one here to hardcode the SLAAC LL address below. 133 ctr1MacAddress: "02:42:ac:11:00:02", 134 pingHost: "fe80::42:acff:fe11:2%eth0", 135 isIPv6: true, 136 }, 137 { 138 name: "IPv6 internal network with SLAAC LL address", 139 bridgeOpts: []func(*types.NetworkCreate){ 140 network.WithIPv6(), 141 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 142 }, 143 // Link-local address is derived from the MAC address, so we need to 144 // specify one here to hardcode the SLAAC LL address below. 145 ctr1MacAddress: "02:42:ac:11:00:02", 146 pingHost: "fe80::42:acff:fe11:2%eth0", 147 isIPv6: true, 148 }, 149 } 150 151 for tcID, tc := range testcases { 152 t.Run(tc.name, func(t *testing.T) { 153 ctx := testutil.StartSpan(ctx, t) 154 155 bridgeName := fmt.Sprintf("testnet-icc-%d", tcID) 156 network.CreateNoError(ctx, t, c, bridgeName, append(tc.bridgeOpts, 157 network.WithDriver("bridge"), 158 network.WithOption("com.docker.network.bridge.name", bridgeName))...) 159 defer network.RemoveNoError(ctx, t, c, bridgeName) 160 161 ctr1Name := fmt.Sprintf("ctr-icc-%d-1", tcID) 162 var ctr1Opts []func(config *container.TestContainerConfig) 163 if tc.ctr1MacAddress != "" { 164 ctr1Opts = append(ctr1Opts, container.WithMacAddress(bridgeName, tc.ctr1MacAddress)) 165 } 166 id1 := container.Run(ctx, t, c, append(ctr1Opts, 167 container.WithName(ctr1Name), 168 container.WithImage("busybox:latest"), 169 container.WithCmd("top"), 170 container.WithNetworkMode(bridgeName))...) 171 defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{ 172 Force: true, 173 }) 174 175 pingHost := tc.pingHost 176 if pingHost == "" { 177 if tc.isLinkLocal { 178 inspect := container.Inspect(ctx, t, c, id1) 179 pingHost = inspect.NetworkSettings.Networks[bridgeName].GlobalIPv6Address + "%eth0" 180 } else { 181 pingHost = ctr1Name 182 } 183 } 184 185 ipv := "-4" 186 if tc.isIPv6 { 187 ipv = "-6" 188 } 189 190 pingCmd := []string{"ping", "-c1", "-W3", ipv, pingHost} 191 192 ctr2Name := fmt.Sprintf("ctr-icc-%d-2", tcID) 193 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 194 defer cancel() 195 res := container.RunAttach(attachCtx, t, c, 196 container.WithName(ctr2Name), 197 container.WithImage("busybox:latest"), 198 container.WithCmd(pingCmd...), 199 container.WithNetworkMode(bridgeName)) 200 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 201 Force: true, 202 }) 203 204 assert.Check(t, is.Equal(res.ExitCode, 0)) 205 assert.Check(t, is.Equal(res.Stderr.Len(), 0)) 206 assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received")) 207 }) 208 } 209 } 210 211 // TestBridgeICCWindows tries to ping container ctr1 from container ctr2 using its hostname. 212 // Checks DNS resolution, and whether containers can communicate with each other. 213 // Regression test for https://github.com/moby/moby/issues/47370 214 func TestBridgeICCWindows(t *testing.T) { 215 skip.If(t, testEnv.DaemonInfo.OSType != "windows") 216 217 ctx := setupTest(t) 218 c := testEnv.APIClient() 219 220 testcases := []struct { 221 name string 222 netName string 223 }{ 224 { 225 name: "Default nat network", 226 netName: "nat", 227 }, 228 { 229 name: "User defined nat network", 230 netName: "mynat", 231 }, 232 } 233 234 for _, tc := range testcases { 235 t.Run(tc.name, func(t *testing.T) { 236 ctx := testutil.StartSpan(ctx, t) 237 238 if tc.netName != "nat" { 239 network.CreateNoError(ctx, t, c, tc.netName, 240 network.WithDriver("nat"), 241 ) 242 defer network.RemoveNoError(ctx, t, c, tc.netName) 243 } 244 245 const ctr1Name = "ctr1" 246 id1 := container.Run(ctx, t, c, 247 container.WithName(ctr1Name), 248 container.WithNetworkMode(tc.netName), 249 ) 250 defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{Force: true}) 251 252 pingCmd := []string{"ping", "-n", "1", "-w", "3000", ctr1Name} 253 254 const ctr2Name = "ctr2" 255 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 256 defer cancel() 257 res := container.RunAttach(attachCtx, t, c, 258 container.WithName(ctr2Name), 259 container.WithCmd(pingCmd...), 260 container.WithNetworkMode(tc.netName), 261 ) 262 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true}) 263 264 assert.Check(t, is.Equal(res.ExitCode, 0)) 265 assert.Check(t, is.Equal(res.Stderr.Len(), 0)) 266 assert.Check(t, is.Contains(res.Stdout.String(), "Sent = 1, Received = 1, Lost = 0")) 267 }) 268 } 269 } 270 271 // TestBridgeINC makes sure two containers on two different bridge networks can't communicate with each other. 272 func TestBridgeINC(t *testing.T) { 273 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 274 275 ctx := setupTest(t) 276 277 d := daemon.New(t) 278 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 279 defer d.Stop(t) 280 281 c := d.NewClientT(t) 282 defer c.Close() 283 284 type bridgesOpts struct { 285 bridge1Opts []func(*types.NetworkCreate) 286 bridge2Opts []func(*types.NetworkCreate) 287 } 288 289 testcases := []struct { 290 name string 291 bridges bridgesOpts 292 ipv6 bool 293 stdout string 294 stderr string 295 }{ 296 { 297 name: "IPv4 non-internal network", 298 bridges: bridgesOpts{ 299 bridge1Opts: []func(*types.NetworkCreate){}, 300 bridge2Opts: []func(*types.NetworkCreate){}, 301 }, 302 stdout: "1 packets transmitted, 0 packets received", 303 }, 304 { 305 name: "IPv4 internal network", 306 bridges: bridgesOpts{ 307 bridge1Opts: []func(*types.NetworkCreate){network.WithInternal()}, 308 bridge2Opts: []func(*types.NetworkCreate){network.WithInternal()}, 309 }, 310 stderr: "sendto: Network is unreachable", 311 }, 312 { 313 name: "IPv6 ULA on non-internal network", 314 bridges: bridgesOpts{ 315 bridge1Opts: []func(*types.NetworkCreate){ 316 network.WithIPv6(), 317 network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"), 318 }, 319 bridge2Opts: []func(*types.NetworkCreate){ 320 network.WithIPv6(), 321 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 322 }, 323 }, 324 ipv6: true, 325 stdout: "1 packets transmitted, 0 packets received", 326 }, 327 { 328 name: "IPv6 ULA on internal network", 329 bridges: bridgesOpts{ 330 bridge1Opts: []func(*types.NetworkCreate){ 331 network.WithIPv6(), 332 network.WithInternal(), 333 network.WithIPAM("fdf1:a844:390c:b200::/64", "fdf1:a844:390c:b200::1"), 334 }, 335 bridge2Opts: []func(*types.NetworkCreate){ 336 network.WithIPv6(), 337 network.WithInternal(), 338 network.WithIPAM("fdf1:a844:390c:b247::/64", "fdf1:a844:390c:b247::1"), 339 }, 340 }, 341 ipv6: true, 342 stderr: "sendto: Network is unreachable", 343 }, 344 } 345 346 for tcID, tc := range testcases { 347 t.Run(tc.name, func(t *testing.T) { 348 ctx := testutil.StartSpan(ctx, t) 349 350 bridge1 := fmt.Sprintf("testnet-inc-%d-1", tcID) 351 bridge2 := fmt.Sprintf("testnet-inc-%d-2", tcID) 352 353 network.CreateNoError(ctx, t, c, bridge1, append(tc.bridges.bridge1Opts, 354 network.WithDriver("bridge"), 355 network.WithOption("com.docker.network.bridge.name", bridge1))...) 356 defer network.RemoveNoError(ctx, t, c, bridge1) 357 network.CreateNoError(ctx, t, c, bridge2, append(tc.bridges.bridge2Opts, 358 network.WithDriver("bridge"), 359 network.WithOption("com.docker.network.bridge.name", bridge2))...) 360 defer network.RemoveNoError(ctx, t, c, bridge2) 361 362 ctr1Name := sanitizeCtrName(t.Name() + "-ctr1") 363 id1 := container.Run(ctx, t, c, 364 container.WithName(ctr1Name), 365 container.WithImage("busybox:latest"), 366 container.WithCmd("top"), 367 container.WithNetworkMode(bridge1)) 368 defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{ 369 Force: true, 370 }) 371 372 ctr1Info := container.Inspect(ctx, t, c, id1) 373 targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress 374 if tc.ipv6 { 375 targetAddr = ctr1Info.NetworkSettings.Networks[bridge1].GlobalIPv6Address 376 } 377 378 pingCmd := []string{"ping", "-c1", "-W3", targetAddr} 379 380 ctr2Name := sanitizeCtrName(t.Name() + "-ctr2") 381 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 382 defer cancel() 383 res := container.RunAttach(attachCtx, t, c, 384 container.WithName(ctr2Name), 385 container.WithImage("busybox:latest"), 386 container.WithCmd(pingCmd...), 387 container.WithNetworkMode(bridge2)) 388 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 389 Force: true, 390 }) 391 392 assert.Check(t, res.ExitCode != 0, "ping unexpectedly succeeded") 393 assert.Check(t, is.Contains(res.Stdout.String(), tc.stdout)) 394 assert.Check(t, is.Contains(res.Stderr.String(), tc.stderr)) 395 }) 396 } 397 } 398 399 func TestDefaultBridgeIPv6(t *testing.T) { 400 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 401 402 ctx := setupTest(t) 403 404 testcases := []struct { 405 name string 406 fixed_cidr_v6 string 407 }{ 408 { 409 name: "IPv6 ULA", 410 fixed_cidr_v6: "fd00:1234::/64", 411 }, 412 { 413 name: "IPv6 LLA only", 414 fixed_cidr_v6: "fe80::/64", 415 }, 416 { 417 name: "IPv6 nonstandard LLA only", 418 fixed_cidr_v6: "fe80:1234::/64", 419 }, 420 } 421 422 for _, tc := range testcases { 423 t.Run(tc.name, func(t *testing.T) { 424 ctx := testutil.StartSpan(ctx, t) 425 426 d := daemon.New(t) 427 d.StartWithBusybox(ctx, t, 428 "--experimental", 429 "--ip6tables", 430 "--ipv6", 431 "--fixed-cidr-v6", tc.fixed_cidr_v6, 432 ) 433 defer d.Stop(t) 434 435 c := d.NewClientT(t) 436 defer c.Close() 437 438 cID := container.Run(ctx, t, c, 439 container.WithImage("busybox:latest"), 440 container.WithCmd("top"), 441 ) 442 defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{ 443 Force: true, 444 }) 445 446 networkName := "bridge" 447 inspect := container.Inspect(ctx, t, c, cID) 448 pingHost := inspect.NetworkSettings.Networks[networkName].GlobalIPv6Address 449 450 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 451 defer cancel() 452 res := container.RunAttach(attachCtx, t, c, 453 container.WithImage("busybox:latest"), 454 container.WithCmd("ping", "-c1", "-W3", pingHost), 455 ) 456 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 457 Force: true, 458 }) 459 460 assert.Check(t, is.Equal(res.ExitCode, 0)) 461 assert.Check(t, is.Equal(res.Stderr.String(), "")) 462 assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received")) 463 }) 464 } 465 } 466 467 // Check that it's possible to change 'fixed-cidr-v6' and restart the daemon. 468 func TestDefaultBridgeAddresses(t *testing.T) { 469 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 470 471 ctx := setupTest(t) 472 473 type testStep struct { 474 stepName string 475 fixedCIDRV6 string 476 expAddrs []string 477 } 478 479 testcases := []struct { 480 name string 481 steps []testStep 482 }{ 483 { 484 name: "Unique-Local Subnet Changes", 485 steps: []testStep{ 486 { 487 stepName: "Set up initial UL prefix", 488 fixedCIDRV6: "fd1c:f1a0:5d8d:aaaa::/64", 489 expAddrs: []string{"fd1c:f1a0:5d8d:aaaa::1/64", "fe80::"}, 490 }, 491 { 492 // Modify that prefix, the default bridge's address must be deleted and re-added. 493 stepName: "Modify UL prefix - address change", 494 fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/64", 495 expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::"}, 496 }, 497 { 498 // Modify the prefix length, the default bridge's address should not change. 499 stepName: "Modify UL prefix - no address change", 500 fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/80", 501 // The prefix length displayed by 'ip a' is not updated - it's informational, and 502 // can't be changed without unnecessarily deleting and re-adding the address. 503 expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::"}, 504 }, 505 }, 506 }, 507 { 508 name: "Link-Local Subnet Changes", 509 steps: []testStep{ 510 { 511 stepName: "Standard LL subnet prefix", 512 fixedCIDRV6: "fe80::/64", 513 expAddrs: []string{"fe80::"}, 514 }, 515 { 516 // Modify that prefix, the default bridge's address must be deleted and re-added. 517 // The bridge must still have an address in the required (standard) LL subnet. 518 stepName: "Nonstandard LL prefix - address change", 519 fixedCIDRV6: "fe80:1234::/32", 520 expAddrs: []string{"fe80:1234::1/32", "fe80::"}, 521 }, 522 { 523 // Modify the prefix length, the addresses should not change. 524 stepName: "Modify LL prefix - no address change", 525 fixedCIDRV6: "fe80:1234::/64", 526 // The prefix length displayed by 'ip a' is not updated - it's informational, and 527 // can't be changed without unnecessarily deleting and re-adding the address. 528 expAddrs: []string{"fe80:1234::1/", "fe80::"}, 529 }, 530 }, 531 }, 532 } 533 534 for _, preserveKernelLL := range []bool{false, true} { 535 var dopts []daemon.Option 536 if preserveKernelLL { 537 dopts = append(dopts, daemon.WithEnvVars("DOCKER_BRIDGE_PRESERVE_KERNEL_LL=1")) 538 } 539 d := daemon.New(t, dopts...) 540 c := d.NewClientT(t) 541 542 for _, tc := range testcases { 543 for _, step := range tc.steps { 544 tcName := fmt.Sprintf("kernel_ll_%v/%s/%s", preserveKernelLL, tc.name, step.stepName) 545 t.Run(tcName, func(t *testing.T) { 546 ctx := testutil.StartSpan(ctx, t) 547 // Check that the daemon starts - regression test for: 548 // https://github.com/moby/moby/issues/46829 549 d.StartWithBusybox(ctx, t, "--experimental", "--ipv6", "--ip6tables", "--fixed-cidr-v6="+step.fixedCIDRV6) 550 551 // Start a container, so that the bridge is set "up" and gets a kernel_ll address. 552 cID := container.Run(ctx, t, c) 553 defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) 554 555 d.Stop(t) 556 557 // Check that the expected addresses have been applied to the bridge. (Skip in 558 // rootless mode, because the bridge is in a different network namespace.) 559 if !testEnv.IsRootless() { 560 res := testutil.RunCommand(ctx, "ip", "-6", "addr", "show", "docker0") 561 assert.Equal(t, res.ExitCode, 0, step.stepName) 562 stdout := res.Stdout() 563 for _, expAddr := range step.expAddrs { 564 assert.Check(t, is.Contains(stdout, expAddr)) 565 } 566 } 567 }) 568 } 569 } 570 } 571 } 572 573 // Test that a container on an 'internal' network has IP connectivity with 574 // the host (on its own subnet, because the n/w bridge has an address on that 575 // subnet, and it's in the host's namespace). 576 // Regression test for https://github.com/moby/moby/issues/47329 577 func TestInternalNwConnectivity(t *testing.T) { 578 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 579 580 ctx := setupTest(t) 581 582 d := daemon.New(t) 583 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 584 defer d.Stop(t) 585 586 c := d.NewClientT(t) 587 defer c.Close() 588 589 const bridgeName = "intnw" 590 const gw4 = "172.30.0.1" 591 const gw6 = "fda9:4130:4715::1234" 592 network.CreateNoError(ctx, t, c, bridgeName, 593 network.WithInternal(), 594 network.WithIPv6(), 595 network.WithIPAM("172.30.0.0/24", gw4), 596 network.WithIPAM("fda9:4130:4715::/64", gw6), 597 network.WithDriver("bridge"), 598 network.WithOption("com.docker.network.bridge.name", bridgeName), 599 ) 600 defer network.RemoveNoError(ctx, t, c, bridgeName) 601 602 const ctrName = "intctr" 603 id := container.Run(ctx, t, c, 604 container.WithName(ctrName), 605 container.WithImage("busybox:latest"), 606 container.WithCmd("top"), 607 container.WithNetworkMode(bridgeName), 608 ) 609 defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true}) 610 611 execCtx, cancel := context.WithTimeout(ctx, 20*time.Second) 612 defer cancel() 613 614 res := container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", gw4}) 615 assert.Check(t, is.Equal(res.ExitCode, 0)) 616 assert.Check(t, is.Equal(res.Stderr(), "")) 617 assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received")) 618 619 res = container.ExecT(execCtx, t, c, id, []string{"ping6", "-c1", "-W3", gw6}) 620 assert.Check(t, is.Equal(res.ExitCode, 0)) 621 assert.Check(t, is.Equal(res.Stderr(), "")) 622 assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received")) 623 624 // Addresses outside the internal subnet must not be accessible. 625 res = container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", "8.8.8.8"}) 626 assert.Check(t, is.Equal(res.ExitCode, 1)) 627 assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable")) 628 } 629 630 // Check that the container's interfaces have no IPv6 address when IPv6 is 631 // disabled in a container via sysctl (including 'lo'). 632 func TestDisableIPv6Addrs(t *testing.T) { 633 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 634 635 ctx := setupTest(t) 636 d := daemon.New(t) 637 d.StartWithBusybox(ctx, t) 638 defer d.Stop(t) 639 640 c := d.NewClientT(t) 641 defer c.Close() 642 643 testcases := []struct { 644 name string 645 sysctls map[string]string 646 expIPv6 bool 647 }{ 648 { 649 name: "IPv6 enabled", 650 expIPv6: true, 651 }, 652 { 653 name: "IPv6 disabled", 654 sysctls: map[string]string{"net.ipv6.conf.all.disable_ipv6": "1"}, 655 }, 656 } 657 658 const netName = "testnet" 659 network.CreateNoError(ctx, t, c, netName, 660 network.WithIPv6(), 661 network.WithIPAM("fda0:ef3d:6430:abcd::/64", "fda0:ef3d:6430:abcd::1"), 662 ) 663 defer network.RemoveNoError(ctx, t, c, netName) 664 665 inet6RE := regexp.MustCompile(`inet6[ \t]+[0-9a-f:]*`) 666 667 for _, tc := range testcases { 668 t.Run(tc.name, func(t *testing.T) { 669 ctx := testutil.StartSpan(ctx, t) 670 671 opts := []func(config *container.TestContainerConfig){ 672 container.WithCmd("ip", "a"), 673 container.WithNetworkMode(netName), 674 } 675 if len(tc.sysctls) > 0 { 676 opts = append(opts, container.WithSysctls(tc.sysctls)) 677 } 678 679 runRes := container.RunAttach(ctx, t, c, opts...) 680 defer c.ContainerRemove(ctx, runRes.ContainerID, 681 containertypes.RemoveOptions{Force: true}, 682 ) 683 684 stdout := runRes.Stdout.String() 685 inet6 := inet6RE.FindAllString(stdout, -1) 686 if tc.expIPv6 { 687 assert.Check(t, len(inet6) > 0, "Expected IPv6 addresses but found none.") 688 } else { 689 assert.Check(t, is.DeepEqual(inet6, []string{}, cmpopts.EquateEmpty())) 690 } 691 }) 692 } 693 } 694 695 // Check that an interface to an '--ipv6=false' network has no IPv6 696 // address - either IPAM assigned, or kernel-assigned LL, but the loopback 697 // interface does still have an IPv6 address ('::1'). 698 func TestNonIPv6Network(t *testing.T) { 699 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 700 701 ctx := setupTest(t) 702 d := daemon.New(t) 703 d.StartWithBusybox(ctx, t) 704 defer d.Stop(t) 705 706 c := d.NewClientT(t) 707 defer c.Close() 708 709 const netName = "testnet" 710 network.CreateNoError(ctx, t, c, netName) 711 defer network.RemoveNoError(ctx, t, c, netName) 712 713 id := container.Run(ctx, t, c, container.WithNetworkMode(netName)) 714 defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true}) 715 716 loRes := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "lo"}) 717 assert.Check(t, is.Contains(loRes.Combined(), " inet ")) 718 assert.Check(t, is.Contains(loRes.Combined(), " inet6 ")) 719 720 eth0Res := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "eth0"}) 721 assert.Check(t, is.Contains(eth0Res.Combined(), " inet ")) 722 assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "), 723 "result.Combined(): %s", eth0Res.Combined()) 724 725 sysctlRes := container.ExecT(ctx, t, c, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"}) 726 assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1")) 727 } 728 729 // Test that it's possible to set a sysctl on an interface in the container. 730 // Regression test for https://github.com/moby/moby/issues/47619 731 func TestSetInterfaceSysctl(t *testing.T) { 732 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no sysctl on Windows") 733 734 ctx := setupTest(t) 735 d := daemon.New(t) 736 d.StartWithBusybox(ctx, t) 737 defer d.Stop(t) 738 739 c := d.NewClientT(t) 740 defer c.Close() 741 742 const scName = "net.ipv4.conf.eth0.forwarding" 743 opts := []func(config *container.TestContainerConfig){ 744 container.WithCmd("sysctl", scName), 745 container.WithSysctls(map[string]string{scName: "1"}), 746 } 747 748 runRes := container.RunAttach(ctx, t, c, opts...) 749 defer c.ContainerRemove(ctx, runRes.ContainerID, 750 containertypes.RemoveOptions{Force: true}, 751 ) 752 753 stdout := runRes.Stdout.String() 754 assert.Check(t, is.Contains(stdout, scName)) 755 } 756 757 // With a read-only "/proc/sys/net" filesystem (simulated using env var 758 // DOCKER_TEST_RO_DISABLE_IPV6), check that if IPv6 can't be disabled on a 759 // container interface, container creation fails - unless the error is ignored by 760 // setting env var DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1. 761 // Regression test for https://github.com/moby/moby/issues/47751 762 func TestReadOnlySlashProc(t *testing.T) { 763 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 764 765 ctx := setupTest(t) 766 767 testcases := []struct { 768 name string 769 daemonEnv []string 770 expErr string 771 }{ 772 { 773 name: "Normality", 774 }, 775 { 776 name: "Read only no workaround", 777 daemonEnv: []string{ 778 "DOCKER_TEST_RO_DISABLE_IPV6=1", 779 }, 780 expErr: "failed to disable IPv6 on container's interface eth0, set env var DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1 to ignore this error", 781 }, 782 { 783 name: "Read only with workaround", 784 daemonEnv: []string{ 785 "DOCKER_TEST_RO_DISABLE_IPV6=1", 786 "DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1", 787 }, 788 }, 789 } 790 791 for _, tc := range testcases { 792 t.Run(tc.name, func(t *testing.T) { 793 ctx := testutil.StartSpan(ctx, t) 794 795 d := daemon.New(t, daemon.WithEnvVars(tc.daemonEnv...)) 796 d.StartWithBusybox(ctx, t) 797 defer d.Stop(t) 798 c := d.NewClientT(t) 799 800 const net4Name = "testnet4" 801 network.CreateNoError(ctx, t, c, net4Name) 802 defer network.RemoveNoError(ctx, t, c, net4Name) 803 id4 := container.Create(ctx, t, c, 804 container.WithNetworkMode(net4Name), 805 container.WithCmd("ls"), 806 ) 807 defer c.ContainerRemove(ctx, id4, containertypes.RemoveOptions{Force: true}) 808 err := c.ContainerStart(ctx, id4, containertypes.StartOptions{}) 809 if tc.expErr == "" { 810 assert.Check(t, err) 811 } else { 812 assert.Check(t, is.ErrorContains(err, tc.expErr)) 813 } 814 815 // It should always be possible to create a container on an IPv6 network (IPv6 816 // doesn't need to be disabled on the interface). 817 const net6Name = "testnet6" 818 network.CreateNoError(ctx, t, c, net6Name, 819 network.WithIPv6(), 820 network.WithIPAM("fd5c:15e3:0b62:5395::/64", "fd5c:15e3:0b62:5395::1"), 821 ) 822 defer network.RemoveNoError(ctx, t, c, net6Name) 823 id6 := container.Run(ctx, t, c, 824 container.WithNetworkMode(net6Name), 825 container.WithCmd("ls"), 826 ) 827 defer c.ContainerRemove(ctx, id6, containertypes.RemoveOptions{Force: true}) 828 }) 829 } 830 }