github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/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 // TestBridgeINC makes sure two containers on two different bridge networks can't communicate with each other. 195 func TestBridgeINC(t *testing.T) { 196 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 197 198 ctx := setupTest(t) 199 200 d := daemon.New(t) 201 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 202 defer d.Stop(t) 203 204 c := d.NewClientT(t) 205 defer c.Close() 206 207 type bridgesOpts struct { 208 bridge1Opts []func(*types.NetworkCreate) 209 bridge2Opts []func(*types.NetworkCreate) 210 } 211 212 testcases := []struct { 213 name string 214 bridges bridgesOpts 215 ipv6 bool 216 stdout string 217 stderr string 218 }{ 219 { 220 name: "IPv4 non-internal network", 221 bridges: bridgesOpts{ 222 bridge1Opts: []func(*types.NetworkCreate){}, 223 bridge2Opts: []func(*types.NetworkCreate){}, 224 }, 225 stdout: "1 packets transmitted, 0 packets received", 226 }, 227 { 228 name: "IPv4 internal network", 229 bridges: bridgesOpts{ 230 bridge1Opts: []func(*types.NetworkCreate){network.WithInternal()}, 231 bridge2Opts: []func(*types.NetworkCreate){network.WithInternal()}, 232 }, 233 stderr: "sendto: Network is unreachable", 234 }, 235 { 236 name: "IPv6 ULA on non-internal network", 237 bridges: bridgesOpts{ 238 bridge1Opts: []func(*types.NetworkCreate){ 239 network.WithIPv6(), 240 network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"), 241 }, 242 bridge2Opts: []func(*types.NetworkCreate){ 243 network.WithIPv6(), 244 network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"), 245 }, 246 }, 247 ipv6: true, 248 stdout: "1 packets transmitted, 0 packets received", 249 }, 250 { 251 name: "IPv6 ULA on internal network", 252 bridges: bridgesOpts{ 253 bridge1Opts: []func(*types.NetworkCreate){ 254 network.WithIPv6(), 255 network.WithInternal(), 256 network.WithIPAM("fdf1:a844:390c:b200::/64", "fdf1:a844:390c:b200::1"), 257 }, 258 bridge2Opts: []func(*types.NetworkCreate){ 259 network.WithIPv6(), 260 network.WithInternal(), 261 network.WithIPAM("fdf1:a844:390c:b247::/64", "fdf1:a844:390c:b247::1"), 262 }, 263 }, 264 ipv6: true, 265 stderr: "sendto: Network is unreachable", 266 }, 267 } 268 269 for tcID, tc := range testcases { 270 t.Run(tc.name, func(t *testing.T) { 271 ctx := testutil.StartSpan(ctx, t) 272 273 bridge1 := fmt.Sprintf("testnet-inc-%d-1", tcID) 274 bridge2 := fmt.Sprintf("testnet-inc-%d-2", tcID) 275 276 network.CreateNoError(ctx, t, c, bridge1, append(tc.bridges.bridge1Opts, 277 network.WithDriver("bridge"), 278 network.WithOption("com.docker.network.bridge.name", bridge1))...) 279 defer network.RemoveNoError(ctx, t, c, bridge1) 280 network.CreateNoError(ctx, t, c, bridge2, append(tc.bridges.bridge2Opts, 281 network.WithDriver("bridge"), 282 network.WithOption("com.docker.network.bridge.name", bridge2))...) 283 defer network.RemoveNoError(ctx, t, c, bridge2) 284 285 ctr1Name := sanitizeCtrName(t.Name() + "-ctr1") 286 id1 := container.Run(ctx, t, c, 287 container.WithName(ctr1Name), 288 container.WithImage("busybox:latest"), 289 container.WithCmd("top"), 290 container.WithNetworkMode(bridge1)) 291 defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{ 292 Force: true, 293 }) 294 295 ctr1Info := container.Inspect(ctx, t, c, id1) 296 targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress 297 if tc.ipv6 { 298 targetAddr = ctr1Info.NetworkSettings.Networks[bridge1].GlobalIPv6Address 299 } 300 301 pingCmd := []string{"ping", "-c1", "-W3", targetAddr} 302 303 ctr2Name := sanitizeCtrName(t.Name() + "-ctr2") 304 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 305 defer cancel() 306 res := container.RunAttach(attachCtx, t, c, 307 container.WithName(ctr2Name), 308 container.WithImage("busybox:latest"), 309 container.WithCmd(pingCmd...), 310 container.WithNetworkMode(bridge2)) 311 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 312 Force: true, 313 }) 314 315 assert.Check(t, res.ExitCode != 0, "ping unexpectedly succeeded") 316 assert.Check(t, is.Contains(res.Stdout.String(), tc.stdout)) 317 assert.Check(t, is.Contains(res.Stderr.String(), tc.stderr)) 318 }) 319 } 320 } 321 322 func TestDefaultBridgeIPv6(t *testing.T) { 323 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 324 325 ctx := setupTest(t) 326 327 testcases := []struct { 328 name string 329 fixed_cidr_v6 string 330 }{ 331 { 332 name: "IPv6 ULA", 333 fixed_cidr_v6: "fd00:1234::/64", 334 }, 335 { 336 name: "IPv6 LLA only", 337 fixed_cidr_v6: "fe80::/64", 338 }, 339 { 340 name: "IPv6 nonstandard LLA only", 341 fixed_cidr_v6: "fe80:1234::/64", 342 }, 343 } 344 345 for _, tc := range testcases { 346 t.Run(tc.name, func(t *testing.T) { 347 ctx := testutil.StartSpan(ctx, t) 348 349 d := daemon.New(t) 350 d.StartWithBusybox(ctx, t, 351 "--experimental", 352 "--ip6tables", 353 "--ipv6", 354 "--fixed-cidr-v6", tc.fixed_cidr_v6, 355 ) 356 defer d.Stop(t) 357 358 c := d.NewClientT(t) 359 defer c.Close() 360 361 cID := container.Run(ctx, t, c, 362 container.WithImage("busybox:latest"), 363 container.WithCmd("top"), 364 ) 365 defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{ 366 Force: true, 367 }) 368 369 networkName := "bridge" 370 inspect := container.Inspect(ctx, t, c, cID) 371 pingHost := inspect.NetworkSettings.Networks[networkName].GlobalIPv6Address 372 373 attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 374 defer cancel() 375 res := container.RunAttach(attachCtx, t, c, 376 container.WithImage("busybox:latest"), 377 container.WithCmd("ping", "-c1", "-W3", pingHost), 378 ) 379 defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{ 380 Force: true, 381 }) 382 383 assert.Check(t, is.Equal(res.ExitCode, 0)) 384 assert.Check(t, is.Equal(res.Stderr.String(), "")) 385 assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received")) 386 }) 387 } 388 } 389 390 // Check that it's possible to change 'fixed-cidr-v6' and restart the daemon. 391 func TestDefaultBridgeAddresses(t *testing.T) { 392 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 393 394 ctx := setupTest(t) 395 d := daemon.New(t) 396 397 type testStep struct { 398 stepName string 399 fixedCIDRV6 string 400 expAddrs []string 401 } 402 403 testcases := []struct { 404 name string 405 steps []testStep 406 }{ 407 { 408 name: "Unique-Local Subnet Changes", 409 steps: []testStep{ 410 { 411 stepName: "Set up initial UL prefix", 412 fixedCIDRV6: "fd1c:f1a0:5d8d:aaaa::/64", 413 expAddrs: []string{"fd1c:f1a0:5d8d:aaaa::1/64", "fe80::1/64"}, 414 }, 415 { 416 // Modify that prefix, the default bridge's address must be deleted and re-added. 417 stepName: "Modify UL prefix - address change", 418 fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/64", 419 expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"}, 420 }, 421 { 422 // Modify the prefix length, the default bridge's address should not change. 423 stepName: "Modify UL prefix - no address change", 424 fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/80", 425 // The prefix length displayed by 'ip a' is not updated - it's informational, and 426 // can't be changed without unnecessarily deleting and re-adding the address. 427 expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"}, 428 }, 429 }, 430 }, 431 { 432 name: "Link-Local Subnet Changes", 433 steps: []testStep{ 434 { 435 stepName: "Standard LL subnet prefix", 436 fixedCIDRV6: "fe80::/64", 437 expAddrs: []string{"fe80::1/64"}, 438 }, 439 { 440 // Modify that prefix, the default bridge's address must be deleted and re-added. 441 // The bridge must still have an address in the required (standard) LL subnet. 442 stepName: "Nonstandard LL prefix - address change", 443 fixedCIDRV6: "fe80:1234::/32", 444 expAddrs: []string{"fe80:1234::1/32", "fe80::1/64"}, 445 }, 446 { 447 // Modify the prefix length, the addresses should not change. 448 stepName: "Modify LL prefix - no address change", 449 fixedCIDRV6: "fe80:1234::/64", 450 // The prefix length displayed by 'ip a' is not updated - it's informational, and 451 // can't be changed without unnecessarily deleting and re-adding the address. 452 expAddrs: []string{"fe80:1234::1/", "fe80::1/64"}, 453 }, 454 }, 455 }, 456 } 457 458 for _, tc := range testcases { 459 t.Run(tc.name, func(t *testing.T) { 460 for _, step := range tc.steps { 461 // Check that the daemon starts - regression test for: 462 // https://github.com/moby/moby/issues/46829 463 d.Start(t, "--experimental", "--ipv6", "--ip6tables", "--fixed-cidr-v6="+step.fixedCIDRV6) 464 d.Stop(t) 465 466 // Check that the expected addresses have been applied to the bridge. (Skip in 467 // rootless mode, because the bridge is in a different network namespace.) 468 if !testEnv.IsRootless() { 469 res := testutil.RunCommand(ctx, "ip", "-6", "addr", "show", "docker0") 470 assert.Equal(t, res.ExitCode, 0, step.stepName) 471 stdout := res.Stdout() 472 for _, expAddr := range step.expAddrs { 473 assert.Check(t, is.Contains(stdout, expAddr)) 474 } 475 } 476 } 477 }) 478 } 479 } 480 481 // Test that a container on an 'internal' network has IP connectivity with 482 // the host (on its own subnet, because the n/w bridge has an address on that 483 // subnet, and it's in the host's namespace). 484 // Regression test for https://github.com/moby/moby/issues/47329 485 func TestInternalNwConnectivity(t *testing.T) { 486 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 487 488 ctx := setupTest(t) 489 490 d := daemon.New(t) 491 d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables") 492 defer d.Stop(t) 493 494 c := d.NewClientT(t) 495 defer c.Close() 496 497 const bridgeName = "intnw" 498 const gw4 = "172.30.0.1" 499 const gw6 = "fda9:4130:4715::1234" 500 network.CreateNoError(ctx, t, c, bridgeName, 501 network.WithInternal(), 502 network.WithIPv6(), 503 network.WithIPAM("172.30.0.0/24", gw4), 504 network.WithIPAM("fda9:4130:4715::/64", gw6), 505 network.WithDriver("bridge"), 506 network.WithOption("com.docker.network.bridge.name", bridgeName), 507 ) 508 defer network.RemoveNoError(ctx, t, c, bridgeName) 509 510 const ctrName = "intctr" 511 id := container.Run(ctx, t, c, 512 container.WithName(ctrName), 513 container.WithImage("busybox:latest"), 514 container.WithCmd("top"), 515 container.WithNetworkMode(bridgeName), 516 ) 517 defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true}) 518 519 execCtx, cancel := context.WithTimeout(ctx, 20*time.Second) 520 defer cancel() 521 522 res := container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", gw4}) 523 assert.Check(t, is.Equal(res.ExitCode, 0)) 524 assert.Check(t, is.Equal(res.Stderr(), "")) 525 assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received")) 526 527 res = container.ExecT(execCtx, t, c, id, []string{"ping6", "-c1", "-W3", gw6}) 528 assert.Check(t, is.Equal(res.ExitCode, 0)) 529 assert.Check(t, is.Equal(res.Stderr(), "")) 530 assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received")) 531 532 // Addresses outside the internal subnet must not be accessible. 533 res = container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", "8.8.8.8"}) 534 assert.Check(t, is.Equal(res.ExitCode, 1)) 535 assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable")) 536 }