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  }