github.com/moby/docker@v26.1.3+incompatible/integration/network/ipvlan/ipvlan_test.go (about)

     1  //go:build !windows
     2  
     3  package ipvlan // import "github.com/docker/docker/integration/network/ipvlan"
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	containertypes "github.com/docker/docker/api/types/container"
    13  	dclient "github.com/docker/docker/client"
    14  	"github.com/docker/docker/integration/internal/container"
    15  	net "github.com/docker/docker/integration/internal/network"
    16  	n "github.com/docker/docker/integration/network"
    17  	"github.com/docker/docker/testutil"
    18  	"github.com/docker/docker/testutil/daemon"
    19  	"gotest.tools/v3/assert"
    20  	is "gotest.tools/v3/assert/cmp"
    21  	"gotest.tools/v3/skip"
    22  )
    23  
    24  func TestDockerNetworkIpvlanPersistance(t *testing.T) {
    25  	// verify the driver automatically provisions the 802.1q link (di-dummy0.70)
    26  	skip.If(t, testEnv.IsRemoteDaemon)
    27  	skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
    28  
    29  	ctx := testutil.StartSpan(baseContext, t)
    30  
    31  	d := daemon.New(t)
    32  	d.StartWithBusybox(ctx, t)
    33  	defer d.Stop(t)
    34  
    35  	// master dummy interface 'di' notation represent 'docker ipvlan'
    36  	master := "di-dummy0"
    37  	n.CreateMasterDummy(ctx, t, master)
    38  	defer n.DeleteInterface(ctx, t, master)
    39  
    40  	c := d.NewClientT(t)
    41  
    42  	// create a network specifying the desired sub-interface name
    43  	netName := "di-persist"
    44  	net.CreateNoError(ctx, t, c, netName,
    45  		net.WithIPvlan("di-dummy0.70", ""),
    46  	)
    47  
    48  	assert.Check(t, n.IsNetworkAvailable(ctx, c, netName))
    49  	// Restart docker daemon to test the config has persisted to disk
    50  	d.Restart(t)
    51  	assert.Check(t, n.IsNetworkAvailable(ctx, c, netName))
    52  }
    53  
    54  func TestDockerNetworkIpvlan(t *testing.T) {
    55  	skip.If(t, testEnv.IsRemoteDaemon)
    56  	skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
    57  
    58  	ctx := testutil.StartSpan(baseContext, t)
    59  
    60  	for _, tc := range []struct {
    61  		name string
    62  		test func(*testing.T, context.Context, dclient.APIClient)
    63  	}{
    64  		{
    65  			name: "Subinterface",
    66  			test: testIpvlanSubinterface,
    67  		}, {
    68  			name: "OverlapParent",
    69  			test: testIpvlanOverlapParent,
    70  		}, {
    71  			name: "L2NilParent",
    72  			test: testIpvlanL2NilParent,
    73  		}, {
    74  			name: "L2InternalMode",
    75  			test: testIpvlanL2InternalMode,
    76  		}, {
    77  			name: "L3NilParent",
    78  			test: testIpvlanL3NilParent,
    79  		}, {
    80  			name: "L3InternalMode",
    81  			test: testIpvlanL3InternalMode,
    82  		}, {
    83  			name: "L2MultiSubnetWithParent",
    84  			test: testIpvlanL2MultiSubnetWithParent,
    85  		}, {
    86  			name: "L2MultiSubnetNoParent",
    87  			test: testIpvlanL2MultiSubnetNoParent,
    88  		}, {
    89  			name: "L3MultiSubnet",
    90  			test: testIpvlanL3MultiSubnet,
    91  		}, {
    92  			name: "L2Addressing",
    93  			test: testIpvlanL2Addressing,
    94  		}, {
    95  			name: "L3Addressing",
    96  			test: testIpvlanL3Addressing,
    97  		}, {
    98  			name: "NoIPv6",
    99  			test: testIpvlanNoIPv6,
   100  		},
   101  	} {
   102  
   103  		t.Run(tc.name, func(t *testing.T) {
   104  			testutil.StartSpan(ctx, t)
   105  			d := daemon.New(t)
   106  			t.Cleanup(func() { d.Stop(t) })
   107  			d.StartWithBusybox(ctx, t)
   108  			c := d.NewClientT(t)
   109  			tc.test(t, ctx, c)
   110  		})
   111  
   112  		// FIXME(vdemeester) clean network
   113  	}
   114  }
   115  
   116  func testIpvlanSubinterface(t *testing.T, ctx context.Context, client dclient.APIClient) {
   117  	master := "di-dummy0"
   118  	n.CreateMasterDummy(ctx, t, master)
   119  	defer n.DeleteInterface(ctx, t, master)
   120  
   121  	netName := "di-subinterface"
   122  	net.CreateNoError(ctx, t, client, netName,
   123  		net.WithIPvlan("di-dummy0.60", ""),
   124  	)
   125  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   126  
   127  	// delete the network while preserving the parent link
   128  	err := client.NetworkRemove(ctx, netName)
   129  	assert.NilError(t, err)
   130  
   131  	assert.Check(t, n.IsNetworkNotAvailable(ctx, client, netName))
   132  	// verify the network delete did not delete the predefined link
   133  	n.LinkExists(ctx, t, "di-dummy0")
   134  }
   135  
   136  func testIpvlanOverlapParent(t *testing.T, ctx context.Context, client dclient.APIClient) {
   137  	// verify the same parent interface cannot be used if already in use by an existing network
   138  	master := "di-dummy0"
   139  	parent := master + ".30"
   140  	n.CreateMasterDummy(ctx, t, master)
   141  	defer n.DeleteInterface(ctx, t, master)
   142  	n.CreateVlanInterface(ctx, t, master, parent, "30")
   143  
   144  	netName := "di-subinterface"
   145  	net.CreateNoError(ctx, t, client, netName,
   146  		net.WithIPvlan(parent, ""),
   147  	)
   148  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   149  
   150  	_, err := net.Create(ctx, client, netName,
   151  		net.WithIPvlan(parent, ""),
   152  	)
   153  	// verify that the overlap returns an error
   154  	assert.Check(t, err != nil)
   155  }
   156  
   157  func testIpvlanL2NilParent(t *testing.T, ctx context.Context, client dclient.APIClient) {
   158  	// ipvlan l2 mode - dummy parent interface is provisioned dynamically
   159  	netName := "di-nil-parent"
   160  	net.CreateNoError(ctx, t, client, netName,
   161  		net.WithIPvlan("", ""),
   162  	)
   163  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   164  
   165  	id1 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
   166  	id2 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
   167  
   168  	_, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
   169  	assert.NilError(t, err)
   170  }
   171  
   172  func testIpvlanL2InternalMode(t *testing.T, ctx context.Context, client dclient.APIClient) {
   173  	netName := "di-internal"
   174  	net.CreateNoError(ctx, t, client, netName,
   175  		net.WithIPvlan("", ""),
   176  		net.WithInternal(),
   177  	)
   178  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   179  
   180  	id1 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
   181  	id2 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
   182  
   183  	result, _ := container.Exec(ctx, client, id1, []string{"ping", "-c", "1", "8.8.8.8"})
   184  	assert.Check(t, strings.Contains(result.Combined(), "Network is unreachable"))
   185  
   186  	_, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
   187  	assert.NilError(t, err)
   188  }
   189  
   190  func testIpvlanL3NilParent(t *testing.T, ctx context.Context, client dclient.APIClient) {
   191  	netName := "di-nil-parent-l3"
   192  	net.CreateNoError(ctx, t, client, netName,
   193  		net.WithIPvlan("", "l3"),
   194  		net.WithIPAM("172.28.230.0/24", ""),
   195  		net.WithIPAM("172.28.220.0/24", ""),
   196  	)
   197  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   198  
   199  	id1 := container.Run(ctx, t, client,
   200  		container.WithNetworkMode(netName),
   201  		container.WithIPv4(netName, "172.28.220.10"),
   202  	)
   203  	id2 := container.Run(ctx, t, client,
   204  		container.WithNetworkMode(netName),
   205  		container.WithIPv4(netName, "172.28.230.10"),
   206  	)
   207  
   208  	_, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
   209  	assert.NilError(t, err)
   210  }
   211  
   212  func testIpvlanL3InternalMode(t *testing.T, ctx context.Context, client dclient.APIClient) {
   213  	netName := "di-internal-l3"
   214  	net.CreateNoError(ctx, t, client, netName,
   215  		net.WithIPvlan("", "l3"),
   216  		net.WithInternal(),
   217  		net.WithIPAM("172.28.230.0/24", ""),
   218  		net.WithIPAM("172.28.220.0/24", ""),
   219  	)
   220  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   221  
   222  	id1 := container.Run(ctx, t, client,
   223  		container.WithNetworkMode(netName),
   224  		container.WithIPv4(netName, "172.28.220.10"),
   225  	)
   226  	id2 := container.Run(ctx, t, client,
   227  		container.WithNetworkMode(netName),
   228  		container.WithIPv4(netName, "172.28.230.10"),
   229  	)
   230  
   231  	result, _ := container.Exec(ctx, client, id1, []string{"ping", "-c", "1", "8.8.8.8"})
   232  	assert.Check(t, strings.Contains(result.Combined(), "Network is unreachable"))
   233  
   234  	_, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
   235  	assert.NilError(t, err)
   236  }
   237  
   238  func testIpvlanL2MultiSubnetWithParent(t *testing.T, ctx context.Context, client dclient.APIClient) {
   239  	const parentIfName = "di-dummy0"
   240  	n.CreateMasterDummy(ctx, t, parentIfName)
   241  	defer n.DeleteInterface(ctx, t, parentIfName)
   242  	testIpvlanL2MultiSubnet(t, ctx, client, parentIfName)
   243  }
   244  
   245  func testIpvlanL2MultiSubnetNoParent(t *testing.T, ctx context.Context, client dclient.APIClient) {
   246  	testIpvlanL2MultiSubnet(t, ctx, client, "")
   247  }
   248  
   249  func testIpvlanL2MultiSubnet(t *testing.T, ctx context.Context, client dclient.APIClient, parent string) {
   250  	netName := "dualstackl2"
   251  	net.CreateNoError(ctx, t, client, netName,
   252  		net.WithIPvlan(parent, ""),
   253  		net.WithIPv6(),
   254  		net.WithIPAM("172.28.200.0/24", ""),
   255  		net.WithIPAM("172.28.202.0/24", "172.28.202.254"),
   256  		net.WithIPAM("2001:db8:abc8::/64", ""),
   257  		net.WithIPAM("2001:db8:abc6::/64", "2001:db8:abc6::254"),
   258  	)
   259  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   260  
   261  	// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.100.0/24 and 2001:db8:abc2::/64
   262  	id1 := container.Run(ctx, t, client,
   263  		container.WithNetworkMode(netName),
   264  		container.WithIPv4(netName, "172.28.200.20"),
   265  		container.WithIPv6(netName, "2001:db8:abc8::20"),
   266  	)
   267  	id2 := container.Run(ctx, t, client,
   268  		container.WithNetworkMode(netName),
   269  		container.WithIPv4(netName, "172.28.200.21"),
   270  		container.WithIPv6(netName, "2001:db8:abc8::21"),
   271  	)
   272  	c1, err := client.ContainerInspect(ctx, id1)
   273  	assert.NilError(t, err)
   274  	if parent == "" {
   275  		// Inspect the v4 gateway to ensure no default GW was assigned
   276  		assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].Gateway, ""))
   277  		// Inspect the v6 gateway to ensure no default GW was assigned
   278  		assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].IPv6Gateway, ""))
   279  	} else {
   280  		// Inspect the v4 gateway to ensure the proper default GW was assigned
   281  		assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].Gateway, "172.28.200.1"))
   282  		// Inspect the v6 gateway to ensure the proper default GW was assigned
   283  		assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc8::1"))
   284  	}
   285  
   286  	// verify ipv4 connectivity to the explicit --ip address second to first
   287  	_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
   288  	assert.NilError(t, err)
   289  	// verify ipv6 connectivity to the explicit --ip6 address second to first
   290  	_, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
   291  	assert.NilError(t, err)
   292  
   293  	// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64
   294  	id3 := container.Run(ctx, t, client,
   295  		container.WithNetworkMode(netName),
   296  		container.WithIPv4(netName, "172.28.202.20"),
   297  		container.WithIPv6(netName, "2001:db8:abc6::20"),
   298  	)
   299  	id4 := container.Run(ctx, t, client,
   300  		container.WithNetworkMode(netName),
   301  		container.WithIPv4(netName, "172.28.202.21"),
   302  		container.WithIPv6(netName, "2001:db8:abc6::21"),
   303  	)
   304  	c3, err := client.ContainerInspect(ctx, id3)
   305  	assert.NilError(t, err)
   306  	if parent == "" {
   307  		// Inspect the v4 gateway to ensure no default GW was assigned
   308  		assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].Gateway, ""))
   309  		// Inspect the v6 gateway to ensure no default GW was assigned
   310  		assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].IPv6Gateway, ""))
   311  	} else {
   312  		// Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
   313  		assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].Gateway, "172.28.202.254"))
   314  		// Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
   315  		assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc6::254"))
   316  	}
   317  
   318  	// verify ipv4 connectivity to the explicit --ip address from third to fourth
   319  	_, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
   320  	assert.NilError(t, err)
   321  	// verify ipv6 connectivity to the explicit --ip6 address from third to fourth
   322  	_, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
   323  	assert.NilError(t, err)
   324  }
   325  
   326  func testIpvlanL3MultiSubnet(t *testing.T, ctx context.Context, client dclient.APIClient) {
   327  	netName := "dualstackl3"
   328  	net.CreateNoError(ctx, t, client, netName,
   329  		net.WithIPvlan("", "l3"),
   330  		net.WithIPv6(),
   331  		net.WithIPAM("172.28.10.0/24", ""),
   332  		net.WithIPAM("172.28.12.0/24", "172.28.12.254"),
   333  		net.WithIPAM("2001:db8:abc9::/64", ""),
   334  		net.WithIPAM("2001:db8:abc7::/64", "2001:db8:abc7::254"),
   335  	)
   336  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   337  
   338  	// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.100.0/24 and 2001:db8:abc2::/64
   339  	id1 := container.Run(ctx, t, client,
   340  		container.WithNetworkMode(netName),
   341  		container.WithIPv4(netName, "172.28.10.20"),
   342  		container.WithIPv6(netName, "2001:db8:abc9::20"),
   343  	)
   344  	id2 := container.Run(ctx, t, client,
   345  		container.WithNetworkMode(netName),
   346  		container.WithIPv4(netName, "172.28.10.21"),
   347  		container.WithIPv6(netName, "2001:db8:abc9::21"),
   348  	)
   349  	c1, err := client.ContainerInspect(ctx, id1)
   350  	assert.NilError(t, err)
   351  
   352  	// verify ipv4 connectivity to the explicit --ipv address second to first
   353  	_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
   354  	assert.NilError(t, err)
   355  	// verify ipv6 connectivity to the explicit --ipv6 address second to first
   356  	_, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
   357  	assert.NilError(t, err)
   358  
   359  	// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64
   360  	id3 := container.Run(ctx, t, client,
   361  		container.WithNetworkMode(netName),
   362  		container.WithIPv4(netName, "172.28.12.20"),
   363  		container.WithIPv6(netName, "2001:db8:abc7::20"),
   364  	)
   365  	id4 := container.Run(ctx, t, client,
   366  		container.WithNetworkMode(netName),
   367  		container.WithIPv4(netName, "172.28.12.21"),
   368  		container.WithIPv6(netName, "2001:db8:abc7::21"),
   369  	)
   370  	c3, err := client.ContainerInspect(ctx, id3)
   371  	assert.NilError(t, err)
   372  
   373  	// verify ipv4 connectivity to the explicit --ipv address from third to fourth
   374  	_, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
   375  	assert.NilError(t, err)
   376  	// verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
   377  	_, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
   378  	assert.NilError(t, err)
   379  
   380  	// Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
   381  	assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "")
   382  	// Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
   383  	assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "")
   384  	// Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
   385  	assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "")
   386  	// Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
   387  	assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "")
   388  }
   389  
   390  // Verify ipvlan l2 mode sets the proper default gateway routes via netlink
   391  // for either an explicitly set route by the user or inferred via default IPAM
   392  func testIpvlanL2Addressing(t *testing.T, ctx context.Context, client dclient.APIClient) {
   393  	const parentIfName = "di-dummy0"
   394  	n.CreateMasterDummy(ctx, t, parentIfName)
   395  	defer n.DeleteInterface(ctx, t, parentIfName)
   396  
   397  	netNameL2 := "dualstackl2"
   398  	net.CreateNoError(ctx, t, client, netNameL2,
   399  		net.WithIPvlan(parentIfName, "l2"),
   400  		net.WithIPv6(),
   401  		net.WithIPAM("172.28.140.0/24", "172.28.140.254"),
   402  		net.WithIPAM("2001:db8:abcb::/64", ""),
   403  	)
   404  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netNameL2))
   405  
   406  	id := container.Run(ctx, t, client,
   407  		container.WithNetworkMode(netNameL2),
   408  	)
   409  	// Validate ipvlan l2 mode defaults gateway sets the default IPAM next-hop inferred from the subnet
   410  	result, err := container.Exec(ctx, client, id, []string{"ip", "route"})
   411  	assert.NilError(t, err)
   412  	assert.Check(t, is.Contains(result.Combined(), "default via 172.28.140.254 dev eth0"))
   413  	// Validate ipvlan l2 mode sets the v6 gateway to the user specified default gateway/next-hop
   414  	result, err = container.Exec(ctx, client, id, []string{"ip", "-6", "route"})
   415  	assert.NilError(t, err)
   416  	assert.Check(t, is.Contains(result.Combined(), "default via 2001:db8:abcb::1 dev eth0"))
   417  }
   418  
   419  // Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
   420  func testIpvlanL3Addressing(t *testing.T, ctx context.Context, client dclient.APIClient) {
   421  	const parentIfName = "di-dummy0"
   422  	n.CreateMasterDummy(ctx, t, parentIfName)
   423  	defer n.DeleteInterface(ctx, t, parentIfName)
   424  
   425  	netNameL3 := "dualstackl3"
   426  	net.CreateNoError(ctx, t, client, netNameL3,
   427  		net.WithIPvlan(parentIfName, "l3"),
   428  		net.WithIPv6(),
   429  		net.WithIPAM("172.28.160.0/24", "172.28.160.254"),
   430  		net.WithIPAM("2001:db8:abcd::/64", "2001:db8:abcd::254"),
   431  	)
   432  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netNameL3))
   433  
   434  	id := container.Run(ctx, t, client,
   435  		container.WithNetworkMode(netNameL3),
   436  	)
   437  	// Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
   438  	result, err := container.Exec(ctx, client, id, []string{"ip", "route"})
   439  	assert.NilError(t, err)
   440  	assert.Check(t, is.Contains(result.Combined(), "default dev eth0"))
   441  	// Validate ipvlan l3 mode sets the v6 gateway to dev eth0 and disregards any explicit or inferred next-hops
   442  	result, err = container.Exec(ctx, client, id, []string{"ip", "-6", "route"})
   443  	assert.NilError(t, err)
   444  	assert.Check(t, is.Contains(result.Combined(), "default dev eth0"))
   445  }
   446  
   447  // Check that an ipvlan interface with '--ipv6=false' doesn't get kernel-assigned
   448  // IPv6 addresses, but the loopback interface does still have an IPv6 address ('::1').
   449  func testIpvlanNoIPv6(t *testing.T, ctx context.Context, client dclient.APIClient) {
   450  	const netName = "ipvlannet"
   451  	net.CreateNoError(ctx, t, client, netName, net.WithIPvlan("", "l3"))
   452  	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
   453  
   454  	id := container.Run(ctx, t, client, container.WithNetworkMode(netName))
   455  
   456  	loRes := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "lo"})
   457  	assert.Check(t, is.Contains(loRes.Combined(), " inet "))
   458  	assert.Check(t, is.Contains(loRes.Combined(), " inet6 "))
   459  
   460  	eth0Res := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "eth0"})
   461  	assert.Check(t, is.Contains(eth0Res.Combined(), " inet "))
   462  	assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "),
   463  		"result.Combined(): %s", eth0Res.Combined())
   464  
   465  	sysctlRes := container.ExecT(ctx, t, client, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"})
   466  	assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1"))
   467  }
   468  
   469  // TestIPVlanDNS checks whether DNS is forwarded, for combinations of l2/l3 mode,
   470  // with/without a parent interface, and with '--internal'. Note that, there's no
   471  // attempt here to give the ipvlan network external connectivity - when this test
   472  // supplies a parent interface, it's a dummy. External DNS lookups only work
   473  // because the daemon is configured to see a host resolver on a loopback
   474  // interface, so the external DNS lookup happens in the host's namespace. The
   475  // test is checking that an automatically configured dummy interface causes the
   476  // network to behave as if it was '--internal'. Regression test for
   477  // https://github.com/moby/moby/issues/47662
   478  func TestIPVlanDNS(t *testing.T) {
   479  	skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
   480  	ctx := testutil.StartSpan(baseContext, t)
   481  
   482  	net.StartDaftDNS(t, "127.0.0.1")
   483  
   484  	tmpFileName := net.WriteTempResolvConf(t, "127.0.0.1")
   485  	d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
   486  	d.StartWithBusybox(ctx, t)
   487  	t.Cleanup(func() { d.Stop(t) })
   488  	c := d.NewClientT(t)
   489  
   490  	const parentIfName = "di-dummy0"
   491  	n.CreateMasterDummy(ctx, t, parentIfName)
   492  	defer n.DeleteInterface(ctx, t, parentIfName)
   493  
   494  	const netName = "ipvlan-dns-net"
   495  
   496  	testcases := []struct {
   497  		name     string
   498  		parent   string
   499  		internal bool
   500  		expDNS   bool
   501  	}{
   502  		{
   503  			name:   "with parent",
   504  			parent: parentIfName,
   505  			// External DNS should be used (even though the network has no external connectivity).
   506  			expDNS: true,
   507  		},
   508  		{
   509  			name: "no parent",
   510  			// External DNS should not be used, equivalent to '--internal'.
   511  		},
   512  		{
   513  			name:     "with parent, internal",
   514  			parent:   parentIfName,
   515  			internal: true,
   516  			// External DNS should not be used.
   517  		},
   518  	}
   519  
   520  	for _, mode := range []string{"l2", "l3"} {
   521  		for _, tc := range testcases {
   522  			name := fmt.Sprintf("Mode=%v/HasParent=%v/Internal=%v", mode, tc.parent != "", tc.internal)
   523  			t.Run(name, func(t *testing.T) {
   524  				ctx := testutil.StartSpan(ctx, t)
   525  				createOpts := []func(*types.NetworkCreate){
   526  					net.WithIPvlan(tc.parent, mode),
   527  				}
   528  				if tc.internal {
   529  					createOpts = append(createOpts, net.WithInternal())
   530  				}
   531  				net.CreateNoError(ctx, t, c, netName, createOpts...)
   532  				defer c.NetworkRemove(ctx, netName)
   533  
   534  				ctrId := container.Run(ctx, t, c, container.WithNetworkMode(netName))
   535  				defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true})
   536  				res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
   537  				assert.NilError(t, err)
   538  				if tc.expDNS {
   539  					assert.Check(t, is.Equal(res.ExitCode, 0))
   540  					assert.Check(t, is.Contains(res.Stdout(), net.DNSRespAddr))
   541  				} else {
   542  					assert.Check(t, is.Equal(res.ExitCode, 1))
   543  					assert.Check(t, is.Contains(res.Stdout(), "SERVFAIL"))
   544  				}
   545  			})
   546  		}
   547  	}
   548  }