|
@@ -0,0 +1,294 @@
|
|
|
+package networking
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "fmt"
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/docker/docker/api/types"
|
|
|
+ containertypes "github.com/docker/docker/api/types/container"
|
|
|
+ "github.com/docker/docker/integration/internal/container"
|
|
|
+ "github.com/docker/docker/integration/internal/network"
|
|
|
+ "github.com/docker/docker/testutil"
|
|
|
+ "github.com/docker/docker/testutil/daemon"
|
|
|
+ "gotest.tools/v3/assert"
|
|
|
+ is "gotest.tools/v3/assert/cmp"
|
|
|
+ "gotest.tools/v3/skip"
|
|
|
+)
|
|
|
+
|
|
|
+// TestBridgeICC tries to ping container ctr1 from container ctr2 using its hostname. Thus, this test checks:
|
|
|
+// 1. DNS resolution ; 2. ARP/NDP ; 3. whether containers can communicate with each other ; 4. kernel-assigned SLAAC
|
|
|
+// addresses.
|
|
|
+func TestBridgeICC(t *testing.T) {
|
|
|
+ skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
+
|
|
|
+ ctx := setupTest(t)
|
|
|
+
|
|
|
+ d := daemon.New(t)
|
|
|
+ d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables")
|
|
|
+ defer d.Stop(t)
|
|
|
+
|
|
|
+ c := d.NewClientT(t)
|
|
|
+ defer c.Close()
|
|
|
+
|
|
|
+ testcases := []struct {
|
|
|
+ name string
|
|
|
+ bridgeOpts []func(*types.NetworkCreate)
|
|
|
+ ctr1Opts []func(*container.TestContainerConfig)
|
|
|
+ linkLocal bool
|
|
|
+ pingHost string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "IPv4 non-internal network",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv4 internal network",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){
|
|
|
+ network.WithInternal(),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 ULA on non-internal network",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 ULA on internal network",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithInternal(),
|
|
|
+ network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 link-local address on non-internal network",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ // There's no real way to specify an IPv6 network is only used with SLAAC link-local IPv6 addresses.
|
|
|
+ // What we can do instead, is to tell the IPAM driver to assign addresses from the link-local prefix.
|
|
|
+ // Each container will have two link-local addresses: 1. a SLAAC address assigned by the kernel ;
|
|
|
+ // 2. the one dynamically assigned by the IPAM driver.
|
|
|
+ network.WithIPAM("fe80::/64", "fe80::1"),
|
|
|
+ },
|
|
|
+ linkLocal: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 link-local address on internal network",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithInternal(),
|
|
|
+ // See the note above about link-local addresses.
|
|
|
+ network.WithIPAM("fe80::/64", "fe80::1"),
|
|
|
+ },
|
|
|
+ linkLocal: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 non-internal network with SLAAC LL address",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
|
|
|
+ },
|
|
|
+ ctr1Opts: []func(*container.TestContainerConfig){
|
|
|
+ // Link-local address is derived from the MAC address, so we need to
|
|
|
+ // specify one here to hardcode the SLAAC LL address below.
|
|
|
+ container.WithMacAddress("02:42:ac:11:00:02"),
|
|
|
+ },
|
|
|
+ pingHost: "fe80::42:acff:fe11:2%eth0",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 internal network with SLAAC LL address",
|
|
|
+ bridgeOpts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
|
|
|
+ },
|
|
|
+ ctr1Opts: []func(*container.TestContainerConfig){
|
|
|
+ // Link-local address is derived from the MAC address, so we need to
|
|
|
+ // specify one here to hardcode the SLAAC LL address below.
|
|
|
+ container.WithMacAddress("02:42:ac:11:00:02"),
|
|
|
+ },
|
|
|
+ pingHost: "fe80::42:acff:fe11:2%eth0",
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for tcID, tc := range testcases {
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
+ ctx := testutil.StartSpan(ctx, t)
|
|
|
+
|
|
|
+ bridgeName := fmt.Sprintf("testnet-icc-%d", tcID)
|
|
|
+ network.CreateNoError(ctx, t, c, bridgeName, append(tc.bridgeOpts,
|
|
|
+ network.WithDriver("bridge"),
|
|
|
+ network.WithOption("com.docker.network.bridge.name", bridgeName))...)
|
|
|
+ defer network.RemoveNoError(ctx, t, c, bridgeName)
|
|
|
+
|
|
|
+ ctr1Name := fmt.Sprintf("ctr-icc-%d-1", tcID)
|
|
|
+ id1 := container.Run(ctx, t, c, append(tc.ctr1Opts,
|
|
|
+ container.WithName(ctr1Name),
|
|
|
+ container.WithImage("busybox:latest"),
|
|
|
+ container.WithCmd("top"),
|
|
|
+ container.WithNetworkMode(bridgeName))...)
|
|
|
+ defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{
|
|
|
+ Force: true,
|
|
|
+ })
|
|
|
+
|
|
|
+ pingHost := tc.pingHost
|
|
|
+ if pingHost == "" {
|
|
|
+ if tc.linkLocal {
|
|
|
+ inspect := container.Inspect(ctx, t, c, id1)
|
|
|
+ pingHost = inspect.NetworkSettings.Networks[bridgeName].GlobalIPv6Address + "%eth0"
|
|
|
+ } else {
|
|
|
+ pingHost = ctr1Name
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pingCmd := []string{"ping", "-c1", "-W3", pingHost}
|
|
|
+
|
|
|
+ ctr2Name := fmt.Sprintf("ctr-icc-%d-2", tcID)
|
|
|
+ attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
|
+ defer cancel()
|
|
|
+ res := container.RunAttach(attachCtx, t, c,
|
|
|
+ container.WithName(ctr2Name),
|
|
|
+ container.WithImage("busybox:latest"),
|
|
|
+ container.WithCmd(pingCmd...),
|
|
|
+ container.WithNetworkMode(bridgeName))
|
|
|
+ defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{
|
|
|
+ Force: true,
|
|
|
+ })
|
|
|
+
|
|
|
+ assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
|
+ assert.Check(t, is.Equal(res.Stderr.Len(), 0))
|
|
|
+ assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received"))
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// TestBridgeINC makes sure two containers on two different bridge networks can't communicate with each other.
|
|
|
+func TestBridgeINC(t *testing.T) {
|
|
|
+ skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
+
|
|
|
+ ctx := setupTest(t)
|
|
|
+
|
|
|
+ d := daemon.New(t)
|
|
|
+ d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables")
|
|
|
+ defer d.Stop(t)
|
|
|
+
|
|
|
+ c := d.NewClientT(t)
|
|
|
+ defer c.Close()
|
|
|
+
|
|
|
+ type bridgesOpts struct {
|
|
|
+ bridge1Opts []func(*types.NetworkCreate)
|
|
|
+ bridge2Opts []func(*types.NetworkCreate)
|
|
|
+ }
|
|
|
+
|
|
|
+ testcases := []struct {
|
|
|
+ name string
|
|
|
+ bridges bridgesOpts
|
|
|
+ ipv6 bool
|
|
|
+ stdout string
|
|
|
+ stderr string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "IPv4 non-internal network",
|
|
|
+ bridges: bridgesOpts{
|
|
|
+ bridge1Opts: []func(*types.NetworkCreate){},
|
|
|
+ bridge2Opts: []func(*types.NetworkCreate){},
|
|
|
+ },
|
|
|
+ stdout: "1 packets transmitted, 0 packets received",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv4 internal network",
|
|
|
+ bridges: bridgesOpts{
|
|
|
+ bridge1Opts: []func(*types.NetworkCreate){network.WithInternal()},
|
|
|
+ bridge2Opts: []func(*types.NetworkCreate){network.WithInternal()},
|
|
|
+ },
|
|
|
+ stderr: "sendto: Network is unreachable",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 ULA on non-internal network",
|
|
|
+ bridges: bridgesOpts{
|
|
|
+ bridge1Opts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"),
|
|
|
+ },
|
|
|
+ bridge2Opts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ipv6: true,
|
|
|
+ stdout: "1 packets transmitted, 0 packets received",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "IPv6 ULA on internal network",
|
|
|
+ bridges: bridgesOpts{
|
|
|
+ bridge1Opts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithInternal(),
|
|
|
+ network.WithIPAM("fdf1:a844:390c:b200::/64", "fdf1:a844:390c:b200::1"),
|
|
|
+ },
|
|
|
+ bridge2Opts: []func(*types.NetworkCreate){
|
|
|
+ network.WithIPv6(),
|
|
|
+ network.WithInternal(),
|
|
|
+ network.WithIPAM("fdf1:a844:390c:b247::/64", "fdf1:a844:390c:b247::1"),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ipv6: true,
|
|
|
+ stderr: "sendto: Network is unreachable",
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for tcID, tc := range testcases {
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
+ ctx := testutil.StartSpan(ctx, t)
|
|
|
+
|
|
|
+ bridge1 := fmt.Sprintf("testnet-inc-%d-1", tcID)
|
|
|
+ bridge2 := fmt.Sprintf("testnet-inc-%d-2", tcID)
|
|
|
+
|
|
|
+ network.CreateNoError(ctx, t, c, bridge1, append(tc.bridges.bridge1Opts,
|
|
|
+ network.WithDriver("bridge"),
|
|
|
+ network.WithOption("com.docker.network.bridge.name", bridge1))...)
|
|
|
+ defer network.RemoveNoError(ctx, t, c, bridge1)
|
|
|
+ network.CreateNoError(ctx, t, c, bridge2, append(tc.bridges.bridge2Opts,
|
|
|
+ network.WithDriver("bridge"),
|
|
|
+ network.WithOption("com.docker.network.bridge.name", bridge2))...)
|
|
|
+ defer network.RemoveNoError(ctx, t, c, bridge2)
|
|
|
+
|
|
|
+ ctr1Name := sanitizeCtrName(t.Name() + "-ctr1")
|
|
|
+ id1 := container.Run(ctx, t, c,
|
|
|
+ container.WithName(ctr1Name),
|
|
|
+ container.WithImage("busybox:latest"),
|
|
|
+ container.WithCmd("top"),
|
|
|
+ container.WithNetworkMode(bridge1))
|
|
|
+ defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{
|
|
|
+ Force: true,
|
|
|
+ })
|
|
|
+
|
|
|
+ ctr1Info := container.Inspect(ctx, t, c, id1)
|
|
|
+ targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress
|
|
|
+ if tc.ipv6 {
|
|
|
+ targetAddr = ctr1Info.NetworkSettings.Networks[bridge1].GlobalIPv6Address
|
|
|
+ }
|
|
|
+
|
|
|
+ pingCmd := []string{"ping", "-c1", "-W3", targetAddr}
|
|
|
+
|
|
|
+ ctr2Name := sanitizeCtrName(t.Name() + "-ctr2")
|
|
|
+ attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
|
+ defer cancel()
|
|
|
+ res := container.RunAttach(attachCtx, t, c,
|
|
|
+ container.WithName(ctr2Name),
|
|
|
+ container.WithImage("busybox:latest"),
|
|
|
+ container.WithCmd(pingCmd...),
|
|
|
+ container.WithNetworkMode(bridge2))
|
|
|
+ defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{
|
|
|
+ Force: true,
|
|
|
+ })
|
|
|
+
|
|
|
+ assert.Check(t, res.ExitCode != 0, "ping unexpectedly succeeded")
|
|
|
+ assert.Check(t, is.Contains(res.Stdout.String(), tc.stdout))
|
|
|
+ assert.Check(t, is.Contains(res.Stderr.String(), tc.stderr))
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|