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  }