github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/integration/networking/bridge_test.go (about) 1 package networking 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/docker/docker/api/types" 10 containertypes "github.com/docker/docker/api/types/container" 11 "github.com/docker/docker/integration/internal/container" 12 "github.com/docker/docker/integration/internal/network" 13 "github.com/docker/docker/testutil" 14 "github.com/docker/docker/testutil/daemon" 15 "gotest.tools/v3/assert" 16 is "gotest.tools/v3/assert/cmp" 17 "gotest.tools/v3/skip" 18 ) 19 20 // TestBridgeICC tries to ping container ctr1 from container ctr2 using its hostname. Thus, this test checks: 21 // 1. DNS resolution ; 2. ARP/NDP ; 3. whether containers can communicate with each other ; 4. kernel-assigned SLAAC 22 // addresses. 23 func TestBridgeICC(t *testing.T) { 24 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 25 26 ctx := setupTest(t) 27 28 d := daemon.New(t) 29 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 30 defer d.Stop(t) 31 32 c := d.NewClientT(t) 33 defer c.Close() 34 35 testcases := []struct { 36 name string 37 bridgeOpts []func(*types.NetworkCreate) 38 ctr1MacAddress string 39 linkLocal bool 40 pingHost string 41 }{ 42 { 43 name: "IPv4 non-internal network", 44 bridgeOpts: []func(*types.NetworkCreate){}, 45 }, 46 { 47 name: "IPv4 internal network", 48 bridgeOpts: []func(*types.NetworkCreate){ 49 network.WithInternal(), 50 }, 51 }, 52 { 53 name: "IPv6 ULA on non-internal network", 54 bridgeOpts: []func(*types.NetworkCreate){ 55 network.WithIPv6(), 56 network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"), 57 }, 58 }, 59 { 60 name: "IPv6 ULA on internal network", 61 bridgeOpts: []func(*types.NetworkCreate){ 62 network.WithIPv6(), 63 network.WithInternal(), 64 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 65 }, 66 }, 67 { 68 name: "IPv6 link-local address on non-internal network", 69 bridgeOpts: []func(*types.NetworkCreate){ 70 network.WithIPv6(), 71 // There's no real way to specify an IPv6 network is only used with SLAAC link-local IPv6 addresses. 72 // What we can do instead, is to tell the IPAM driver to assign addresses from the link-local prefix. 73 // Each container will have two link-local addresses: 1. a SLAAC address assigned by the kernel ; 74 // 2. the one dynamically assigned by the IPAM driver. 75 network.WithIPAM("fe80::/64", "fe80::1"), 76 }, 77 linkLocal: true, 78 }, 79 { 80 name: "IPv6 link-local address on internal network", 81 bridgeOpts: []func(*types.NetworkCreate){ 82 network.WithIPv6(), 83 network.WithInternal(), 84 // See the note above about link-local addresses. 85 network.WithIPAM("fe80::/64", "fe80::1"), 86 }, 87 linkLocal: true, 88 }, 89 { 90 // As for 'LL non-internal', but ping the container by name instead of by address 91 // - the busybox test containers only have one interface with a link local 92 // address, so the zone index is not required: 93 // RFC-4007, section 6: "[...] for nodes with only a single non-loopback 94 // interface (e.g., a single Ethernet interface), the common case, link-local 95 // addresses need not be qualified with a zone index." 96 // So, for this common case, LL addresses should be included in DNS config. 97 name: "IPv6 link-local address on non-internal network ping by name", 98 bridgeOpts: []func(*types.NetworkCreate){ 99 network.WithIPv6(), 100 network.WithIPAM("fe80::/64", "fe80::1"), 101 }, 102 }, 103 { 104 name: "IPv6 nonstandard link-local subnet on non-internal network ping by name", 105 // No interfaces apart from the one on the bridge network with this non-default 106 // subnet will be on this link local subnet (it's not currently possible to 107 // configure two networks with the same LL subnet, although perhaps it should 108 // be). So, again, no zone index is required and the LL address should be 109 // included in DNS config. 110 bridgeOpts: []func(*types.NetworkCreate){ 111 network.WithIPv6(), 112 network.WithIPAM("fe80:1234::/64", "fe80:1234::1"), 113 }, 114 }, 115 { 116 name: "IPv6 non-internal network with SLAAC LL address", 117 bridgeOpts: []func(*types.NetworkCreate){ 118 network.WithIPv6(), 119 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 120 }, 121 // Link-local address is derived from the MAC address, so we need to 122 // specify one here to hardcode the SLAAC LL address below. 123 ctr1MacAddress: "02:42:ac:11:00:02", 124 pingHost: "fe80::42:acff:fe11:2%eth0", 125 }, 126 { 127 name: "IPv6 internal network with SLAAC LL address", 128 bridgeOpts: []func(*types.NetworkCreate){ 129 network.WithIPv6(), 130 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 131 }, 132 // Link-local address is derived from the MAC address, so we need to 133 // specify one here to hardcode the SLAAC LL address below. 134 ctr1MacAddress: "02:42:ac:11:00:02", 135 pingHost: "fe80::42:acff:fe11:2%eth0", 136 }, 137 } 138 139 for tcID, tc := range testcases { 140 t.Run(tc.name, func(t *testing.T) { 141 ctx := testutil.StartSpan(ctx, t) 142 143 bridgeName := fmt.Sprintf("testnet-icc-%d", tcID) 144 network.CreateNoError(ctx, t, c, bridgeName, append(tc.bridgeOpts, 145 network.WithDriver("bridge"), 146 network.WithOption("com.docker.network.bridge.name", bridgeName))...) 147 defer network.RemoveNoError(ctx, t, c, bridgeName) 148 149 ctr1Name := fmt.Sprintf("ctr-icc-%d-1", tcID) 150 var ctr1Opts []func(config *container.TestContainerConfig) 151 if tc.ctr1MacAddress != "" { 152 ctr1Opts = append(ctr1Opts, container.WithMacAddress(bridgeName, tc.ctr1MacAddress)) 153 } 154 id1 := container.Run(ctx, t, c, append(ctr1Opts, 155 container.WithName(ctr1Name), 156 container.WithImage("busybox:latest"), 157 container.WithCmd("top"), 158 container.WithNetworkMode(bridgeName))...) 159 defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{ 160 Force: true, 161 }) 162 163 pingHost := tc.pingHost 164 if pingHost == "" { 165 if tc.linkLocal { 166 inspect := container.Inspect(ctx, t, c, id1) 167 pingHost = inspect.NetworkSettings.Networks[bridgeName].GlobalIPv6Address + "%eth0" 168 } else { 169 pingHost = ctr1Name 170 } 171 } 172 173 pingCmd := []string{"ping", "-c1", "-W3", pingHost} 174 175 ctr2Name := fmt.Sprintf("ctr-icc-%d-2", tcID) 176 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 177 defer cancel() 178 res := container.RunAttach(attachCtx, t, c, 179 container.WithName(ctr2Name), 180 container.WithImage("busybox:latest"), 181 container.WithCmd(pingCmd...), 182 container.WithNetworkMode(bridgeName)) 183 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 184 Force: true, 185 }) 186 187 assert.Check(t, is.Equal(res.ExitCode, 0)) 188 assert.Check(t, is.Equal(res.Stderr.Len(), 0)) 189 assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received")) 190 }) 191 } 192 } 193 194 // TestBridgeICCWindows tries to ping container ctr1 from container ctr2 using its hostname. 195 // Checks DNS resolution, and whether containers can communicate with each other. 196 // Regression test for https://github.com/moby/moby/issues/47370 197 func TestBridgeICCWindows(t *testing.T) { 198 skip.If(t, testEnv.DaemonInfo.OSType != "windows") 199 200 ctx := setupTest(t) 201 c := testEnv.APIClient() 202 203 testcases := []struct { 204 name string 205 netName string 206 }{ 207 { 208 name: "Default nat network", 209 netName: "nat", 210 }, 211 { 212 name: "User defined nat network", 213 netName: "mynat", 214 }, 215 } 216 217 for _, tc := range testcases { 218 t.Run(tc.name, func(t *testing.T) { 219 ctx := testutil.StartSpan(ctx, t) 220 221 if tc.netName != "nat" { 222 network.CreateNoError(ctx, t, c, tc.netName, 223 network.WithDriver("nat"), 224 ) 225 defer network.RemoveNoError(ctx, t, c, tc.netName) 226 } 227 228 const ctr1Name = "ctr1" 229 id1 := container.Run(ctx, t, c, 230 container.WithName(ctr1Name), 231 container.WithNetworkMode(tc.netName), 232 ) 233 defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{Force: true}) 234 235 pingCmd := []string{"ping", "-n", "1", "-w", "3000", ctr1Name} 236 237 const ctr2Name = "ctr2" 238 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 239 defer cancel() 240 res := container.RunAttach(attachCtx, t, c, 241 container.WithName(ctr2Name), 242 container.WithCmd(pingCmd...), 243 container.WithNetworkMode(tc.netName), 244 ) 245 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true}) 246 247 assert.Check(t, is.Equal(res.ExitCode, 0)) 248 assert.Check(t, is.Equal(res.Stderr.Len(), 0)) 249 assert.Check(t, is.Contains(res.Stdout.String(), "Sent = 1, Received = 1, Lost = 0")) 250 }) 251 } 252 } 253 254 // TestBridgeINC makes sure two containers on two different bridge networks can't communicate with each other. 255 func TestBridgeINC(t *testing.T) { 256 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 257 258 ctx := setupTest(t) 259 260 d := daemon.New(t) 261 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 262 defer d.Stop(t) 263 264 c := d.NewClientT(t) 265 defer c.Close() 266 267 type bridgesOpts struct { 268 bridge1Opts []func(*types.NetworkCreate) 269 bridge2Opts []func(*types.NetworkCreate) 270 } 271 272 testcases := []struct { 273 name string 274 bridges bridgesOpts 275 ipv6 bool 276 stdout string 277 stderr string 278 }{ 279 { 280 name: "IPv4 non-internal network", 281 bridges: bridgesOpts{ 282 bridge1Opts: []func(*types.NetworkCreate){}, 283 bridge2Opts: []func(*types.NetworkCreate){}, 284 }, 285 stdout: "1 packets transmitted, 0 packets received", 286 }, 287 { 288 name: "IPv4 internal network", 289 bridges: bridgesOpts{ 290 bridge1Opts: []func(*types.NetworkCreate){network.WithInternal()}, 291 bridge2Opts: []func(*types.NetworkCreate){network.WithInternal()}, 292 }, 293 stderr: "sendto: Network is unreachable", 294 }, 295 { 296 name: "IPv6 ULA on non-internal network", 297 bridges: bridgesOpts{ 298 bridge1Opts: []func(*types.NetworkCreate){ 299 network.WithIPv6(), 300 network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"), 301 }, 302 bridge2Opts: []func(*types.NetworkCreate){ 303 network.WithIPv6(), 304 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 305 }, 306 }, 307 ipv6: true, 308 stdout: "1 packets transmitted, 0 packets received", 309 }, 310 { 311 name: "IPv6 ULA on internal network", 312 bridges: bridgesOpts{ 313 bridge1Opts: []func(*types.NetworkCreate){ 314 network.WithIPv6(), 315 network.WithInternal(), 316 network.WithIPAM("fdf1:a844:390c:b200::/64", "fdf1:a844:390c:b200::1"), 317 }, 318 bridge2Opts: []func(*types.NetworkCreate){ 319 network.WithIPv6(), 320 network.WithInternal(), 321 network.WithIPAM("fdf1:a844:390c:b247::/64", "fdf1:a844:390c:b247::1"), 322 }, 323 }, 324 ipv6: true, 325 stderr: "sendto: Network is unreachable", 326 }, 327 } 328 329 for tcID, tc := range testcases { 330 t.Run(tc.name, func(t *testing.T) { 331 ctx := testutil.StartSpan(ctx, t) 332 333 bridge1 := fmt.Sprintf("testnet-inc-%d-1", tcID) 334 bridge2 := fmt.Sprintf("testnet-inc-%d-2", tcID) 335 336 network.CreateNoError(ctx, t, c, bridge1, append(tc.bridges.bridge1Opts, 337 network.WithDriver("bridge"), 338 network.WithOption("com.docker.network.bridge.name", bridge1))...) 339 defer network.RemoveNoError(ctx, t, c, bridge1) 340 network.CreateNoError(ctx, t, c, bridge2, append(tc.bridges.bridge2Opts, 341 network.WithDriver("bridge"), 342 network.WithOption("com.docker.network.bridge.name", bridge2))...) 343 defer network.RemoveNoError(ctx, t, c, bridge2) 344 345 ctr1Name := sanitizeCtrName(t.Name() + "-ctr1") 346 id1 := container.Run(ctx, t, c, 347 container.WithName(ctr1Name), 348 container.WithImage("busybox:latest"), 349 container.WithCmd("top"), 350 container.WithNetworkMode(bridge1)) 351 defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{ 352 Force: true, 353 }) 354 355 ctr1Info := container.Inspect(ctx, t, c, id1) 356 targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress 357 if tc.ipv6 { 358 targetAddr = ctr1Info.NetworkSettings.Networks[bridge1].GlobalIPv6Address 359 } 360 361 pingCmd := []string{"ping", "-c1", "-W3", targetAddr} 362 363 ctr2Name := sanitizeCtrName(t.Name() + "-ctr2") 364 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 365 defer cancel() 366 res := container.RunAttach(attachCtx, t, c, 367 container.WithName(ctr2Name), 368 container.WithImage("busybox:latest"), 369 container.WithCmd(pingCmd...), 370 container.WithNetworkMode(bridge2)) 371 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 372 Force: true, 373 }) 374 375 assert.Check(t, res.ExitCode != 0, "ping unexpectedly succeeded") 376 assert.Check(t, is.Contains(res.Stdout.String(), tc.stdout)) 377 assert.Check(t, is.Contains(res.Stderr.String(), tc.stderr)) 378 }) 379 } 380 } 381 382 func TestDefaultBridgeIPv6(t *testing.T) { 383 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 384 385 ctx := setupTest(t) 386 387 testcases := []struct { 388 name string 389 fixed_cidr_v6 string 390 }{ 391 { 392 name: "IPv6 ULA", 393 fixed_cidr_v6: "fd00:1234::/64", 394 }, 395 { 396 name: "IPv6 LLA only", 397 fixed_cidr_v6: "fe80::/64", 398 }, 399 { 400 name: "IPv6 nonstandard LLA only", 401 fixed_cidr_v6: "fe80:1234::/64", 402 }, 403 } 404 405 for _, tc := range testcases { 406 t.Run(tc.name, func(t *testing.T) { 407 ctx := testutil.StartSpan(ctx, t) 408 409 d := daemon.New(t) 410 d.StartWithBusybox(ctx, t, 411 "--experimental", 412 "--ip6tables", 413 "--ipv6", 414 "--fixed-cidr-v6", tc.fixed_cidr_v6, 415 ) 416 defer d.Stop(t) 417 418 c := d.NewClientT(t) 419 defer c.Close() 420 421 cID := container.Run(ctx, t, c, 422 container.WithImage("busybox:latest"), 423 container.WithCmd("top"), 424 ) 425 defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{ 426 Force: true, 427 }) 428 429 networkName := "bridge" 430 inspect := container.Inspect(ctx, t, c, cID) 431 pingHost := inspect.NetworkSettings.Networks[networkName].GlobalIPv6Address 432 433 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 434 defer cancel() 435 res := container.RunAttach(attachCtx, t, c, 436 container.WithImage("busybox:latest"), 437 container.WithCmd("ping", "-c1", "-W3", pingHost), 438 ) 439 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 440 Force: true, 441 }) 442 443 assert.Check(t, is.Equal(res.ExitCode, 0)) 444 assert.Check(t, is.Equal(res.Stderr.String(), "")) 445 assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received")) 446 }) 447 } 448 } 449 450 // Check that it's possible to change 'fixed-cidr-v6' and restart the daemon. 451 func TestDefaultBridgeAddresses(t *testing.T) { 452 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 453 454 ctx := setupTest(t) 455 d := daemon.New(t) 456 457 type testStep struct { 458 stepName string 459 fixedCIDRV6 string 460 expAddrs []string 461 } 462 463 testcases := []struct { 464 name string 465 steps []testStep 466 }{ 467 { 468 name: "Unique-Local Subnet Changes", 469 steps: []testStep{ 470 { 471 stepName: "Set up initial UL prefix", 472 fixedCIDRV6: "fd1c:f1a0:5d8d:aaaa::/64", 473 expAddrs: []string{"fd1c:f1a0:5d8d:aaaa::1/64", "fe80::1/64"}, 474 }, 475 { 476 // Modify that prefix, the default bridge's address must be deleted and re-added. 477 stepName: "Modify UL prefix - address change", 478 fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/64", 479 expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"}, 480 }, 481 { 482 // Modify the prefix length, the default bridge's address should not change. 483 stepName: "Modify UL prefix - no address change", 484 fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/80", 485 // The prefix length displayed by 'ip a' is not updated - it's informational, and 486 // can't be changed without unnecessarily deleting and re-adding the address. 487 expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"}, 488 }, 489 }, 490 }, 491 { 492 name: "Link-Local Subnet Changes", 493 steps: []testStep{ 494 { 495 stepName: "Standard LL subnet prefix", 496 fixedCIDRV6: "fe80::/64", 497 expAddrs: []string{"fe80::1/64"}, 498 }, 499 { 500 // Modify that prefix, the default bridge's address must be deleted and re-added. 501 // The bridge must still have an address in the required (standard) LL subnet. 502 stepName: "Nonstandard LL prefix - address change", 503 fixedCIDRV6: "fe80:1234::/32", 504 expAddrs: []string{"fe80:1234::1/32", "fe80::1/64"}, 505 }, 506 { 507 // Modify the prefix length, the addresses should not change. 508 stepName: "Modify LL prefix - no address change", 509 fixedCIDRV6: "fe80:1234::/64", 510 // The prefix length displayed by 'ip a' is not updated - it's informational, and 511 // can't be changed without unnecessarily deleting and re-adding the address. 512 expAddrs: []string{"fe80:1234::1/", "fe80::1/64"}, 513 }, 514 }, 515 }, 516 } 517 518 for _, tc := range testcases { 519 t.Run(tc.name, func(t *testing.T) { 520 for _, step := range tc.steps { 521 // Check that the daemon starts - regression test for: 522 // https://github.com/moby/moby/issues/46829 523 d.Start(t, "--experimental", "--ipv6", "--ip6tables", "--fixed-cidr-v6="+step.fixedCIDRV6) 524 d.Stop(t) 525 526 // Check that the expected addresses have been applied to the bridge. (Skip in 527 // rootless mode, because the bridge is in a different network namespace.) 528 if !testEnv.IsRootless() { 529 res := testutil.RunCommand(ctx, "ip", "-6", "addr", "show", "docker0") 530 assert.Equal(t, res.ExitCode, 0, step.stepName) 531 stdout := res.Stdout() 532 for _, expAddr := range step.expAddrs { 533 assert.Check(t, is.Contains(stdout, expAddr)) 534 } 535 } 536 } 537 }) 538 } 539 } 540 541 // Test that a container on an 'internal' network has IP connectivity with 542 // the host (on its own subnet, because the n/w bridge has an address on that 543 // subnet, and it's in the host's namespace). 544 // Regression test for https://github.com/moby/moby/issues/47329 545 func TestInternalNwConnectivity(t *testing.T) { 546 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 547 548 ctx := setupTest(t) 549 550 d := daemon.New(t) 551 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 552 defer d.Stop(t) 553 554 c := d.NewClientT(t) 555 defer c.Close() 556 557 const bridgeName = "intnw" 558 const gw4 = "172.30.0.1" 559 const gw6 = "fda9:4130:4715::1234" 560 network.CreateNoError(ctx, t, c, bridgeName, 561 network.WithInternal(), 562 network.WithIPv6(), 563 network.WithIPAM("172.30.0.0/24", gw4), 564 network.WithIPAM("fda9:4130:4715::/64", gw6), 565 network.WithDriver("bridge"), 566 network.WithOption("com.docker.network.bridge.name", bridgeName), 567 ) 568 defer network.RemoveNoError(ctx, t, c, bridgeName) 569 570 const ctrName = "intctr" 571 id := container.Run(ctx, t, c, 572 container.WithName(ctrName), 573 container.WithImage("busybox:latest"), 574 container.WithCmd("top"), 575 container.WithNetworkMode(bridgeName), 576 ) 577 defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true}) 578 579 execCtx, cancel := context.WithTimeout(ctx, 20*time.Second) 580 defer cancel() 581 582 res := container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", gw4}) 583 assert.Check(t, is.Equal(res.ExitCode, 0)) 584 assert.Check(t, is.Equal(res.Stderr(), "")) 585 assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received")) 586 587 res = container.ExecT(execCtx, t, c, id, []string{"ping6", "-c1", "-W3", gw6}) 588 assert.Check(t, is.Equal(res.ExitCode, 0)) 589 assert.Check(t, is.Equal(res.Stderr(), "")) 590 assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received")) 591 592 // Addresses outside the internal subnet must not be accessible. 593 res = container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", "8.8.8.8"}) 594 assert.Check(t, is.Equal(res.ExitCode, 1)) 595 assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable")) 596 }