fde80fe2e7
Partially reverts 0046b16
"daemon: set libnetwork sandbox key w/o OCI hook"
Running SetKey to store the OCI Sandbox key after task creation, rather
than from the OCI prestart hook, meant it happened after sysctl settings
were applied by the runtime - which was the intention, we wanted to
complete Sandbox configuration after IPv6 had been disabled by a sysctl
if that was going to happen.
But, it meant '--sysctl' options for a specfic network interface caused
container task creation to fail, because the interface is only moved into
the network namespace during SetKey.
This change restores the SetKey prestart hook, and regenerates config
files that depend on the container's support for IPv6 after the task has
been created. It also adds a regression test that makes sure it's possible
to set an interface-specfic sysctl.
Signed-off-by: Rob Murray <rob.murray@docker.com>
705 lines
22 KiB
Go
705 lines
22 KiB
Go
package networking
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"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"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"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)
|
|
ctr1MacAddress string
|
|
isIPv6 bool
|
|
isLinkLocal 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"),
|
|
},
|
|
isIPv6: true,
|
|
},
|
|
{
|
|
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"),
|
|
},
|
|
isIPv6: true,
|
|
},
|
|
{
|
|
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"),
|
|
},
|
|
isLinkLocal: true,
|
|
isIPv6: 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"),
|
|
},
|
|
isLinkLocal: true,
|
|
isIPv6: true,
|
|
},
|
|
{
|
|
// As for 'LL non-internal', ping the container by name instead of by address
|
|
// - the busybox test containers only have one interface with a link local
|
|
// address, so the zone index is not required:
|
|
// RFC-4007, section 6: "[...] for nodes with only a single non-loopback
|
|
// interface (e.g., a single Ethernet interface), the common case, link-local
|
|
// addresses need not be qualified with a zone index."
|
|
// So, for this common case, LL addresses should be included in DNS config.
|
|
name: "IPv6 link-local address on non-internal network ping by name",
|
|
bridgeOpts: []func(*types.NetworkCreate){
|
|
network.WithIPv6(),
|
|
network.WithIPAM("fe80::/64", "fe80::1"),
|
|
},
|
|
isIPv6: true,
|
|
},
|
|
{
|
|
name: "IPv6 nonstandard link-local subnet on non-internal network ping by name",
|
|
// No interfaces apart from the one on the bridge network with this non-default
|
|
// subnet will be on this link local subnet (it's not currently possible to
|
|
// configure two networks with the same LL subnet, although perhaps it should
|
|
// be). So, again, no zone index is required and the LL address should be
|
|
// included in DNS config.
|
|
bridgeOpts: []func(*types.NetworkCreate){
|
|
network.WithIPv6(),
|
|
network.WithIPAM("fe80:1234::/64", "fe80:1234::1"),
|
|
},
|
|
isIPv6: 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"),
|
|
},
|
|
// Link-local address is derived from the MAC address, so we need to
|
|
// specify one here to hardcode the SLAAC LL address below.
|
|
ctr1MacAddress: "02:42:ac:11:00:02",
|
|
pingHost: "fe80::42:acff:fe11:2%eth0",
|
|
isIPv6: true,
|
|
},
|
|
{
|
|
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"),
|
|
},
|
|
// Link-local address is derived from the MAC address, so we need to
|
|
// specify one here to hardcode the SLAAC LL address below.
|
|
ctr1MacAddress: "02:42:ac:11:00:02",
|
|
pingHost: "fe80::42:acff:fe11:2%eth0",
|
|
isIPv6: true,
|
|
},
|
|
}
|
|
|
|
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)
|
|
var ctr1Opts []func(config *container.TestContainerConfig)
|
|
if tc.ctr1MacAddress != "" {
|
|
ctr1Opts = append(ctr1Opts, container.WithMacAddress(bridgeName, tc.ctr1MacAddress))
|
|
}
|
|
id1 := container.Run(ctx, t, c, append(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.isLinkLocal {
|
|
inspect := container.Inspect(ctx, t, c, id1)
|
|
pingHost = inspect.NetworkSettings.Networks[bridgeName].GlobalIPv6Address + "%eth0"
|
|
} else {
|
|
pingHost = ctr1Name
|
|
}
|
|
}
|
|
|
|
ipv := "-4"
|
|
if tc.isIPv6 {
|
|
ipv = "-6"
|
|
}
|
|
|
|
pingCmd := []string{"ping", "-c1", "-W3", ipv, 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"))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBridgeICCWindows tries to ping container ctr1 from container ctr2 using its hostname.
|
|
// Checks DNS resolution, and whether containers can communicate with each other.
|
|
// Regression test for https://github.com/moby/moby/issues/47370
|
|
func TestBridgeICCWindows(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType != "windows")
|
|
|
|
ctx := setupTest(t)
|
|
c := testEnv.APIClient()
|
|
|
|
testcases := []struct {
|
|
name string
|
|
netName string
|
|
}{
|
|
{
|
|
name: "Default nat network",
|
|
netName: "nat",
|
|
},
|
|
{
|
|
name: "User defined nat network",
|
|
netName: "mynat",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
if tc.netName != "nat" {
|
|
network.CreateNoError(ctx, t, c, tc.netName,
|
|
network.WithDriver("nat"),
|
|
)
|
|
defer network.RemoveNoError(ctx, t, c, tc.netName)
|
|
}
|
|
|
|
const ctr1Name = "ctr1"
|
|
id1 := container.Run(ctx, t, c,
|
|
container.WithName(ctr1Name),
|
|
container.WithNetworkMode(tc.netName),
|
|
)
|
|
defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{Force: true})
|
|
|
|
pingCmd := []string{"ping", "-n", "1", "-w", "3000", ctr1Name}
|
|
|
|
const ctr2Name = "ctr2"
|
|
attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
res := container.RunAttach(attachCtx, t, c,
|
|
container.WithName(ctr2Name),
|
|
container.WithCmd(pingCmd...),
|
|
container.WithNetworkMode(tc.netName),
|
|
)
|
|
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(), "Sent = 1, Received = 1, Lost = 0"))
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultBridgeIPv6(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
|
ctx := setupTest(t)
|
|
|
|
testcases := []struct {
|
|
name string
|
|
fixed_cidr_v6 string
|
|
}{
|
|
{
|
|
name: "IPv6 ULA",
|
|
fixed_cidr_v6: "fd00:1234::/64",
|
|
},
|
|
{
|
|
name: "IPv6 LLA only",
|
|
fixed_cidr_v6: "fe80::/64",
|
|
},
|
|
{
|
|
name: "IPv6 nonstandard LLA only",
|
|
fixed_cidr_v6: "fe80:1234::/64",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
d := daemon.New(t)
|
|
d.StartWithBusybox(ctx, t,
|
|
"--experimental",
|
|
"--ip6tables",
|
|
"--ipv6",
|
|
"--fixed-cidr-v6", tc.fixed_cidr_v6,
|
|
)
|
|
defer d.Stop(t)
|
|
|
|
c := d.NewClientT(t)
|
|
defer c.Close()
|
|
|
|
cID := container.Run(ctx, t, c,
|
|
container.WithImage("busybox:latest"),
|
|
container.WithCmd("top"),
|
|
)
|
|
defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{
|
|
Force: true,
|
|
})
|
|
|
|
networkName := "bridge"
|
|
inspect := container.Inspect(ctx, t, c, cID)
|
|
pingHost := inspect.NetworkSettings.Networks[networkName].GlobalIPv6Address
|
|
|
|
attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
res := container.RunAttach(attachCtx, t, c,
|
|
container.WithImage("busybox:latest"),
|
|
container.WithCmd("ping", "-c1", "-W3", pingHost),
|
|
)
|
|
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.String(), ""))
|
|
assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received"))
|
|
})
|
|
}
|
|
}
|
|
|
|
// Check that it's possible to change 'fixed-cidr-v6' and restart the daemon.
|
|
func TestDefaultBridgeAddresses(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
|
ctx := setupTest(t)
|
|
d := daemon.New(t)
|
|
|
|
type testStep struct {
|
|
stepName string
|
|
fixedCIDRV6 string
|
|
expAddrs []string
|
|
}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
steps []testStep
|
|
}{
|
|
{
|
|
name: "Unique-Local Subnet Changes",
|
|
steps: []testStep{
|
|
{
|
|
stepName: "Set up initial UL prefix",
|
|
fixedCIDRV6: "fd1c:f1a0:5d8d:aaaa::/64",
|
|
expAddrs: []string{"fd1c:f1a0:5d8d:aaaa::1/64", "fe80::1/64"},
|
|
},
|
|
{
|
|
// Modify that prefix, the default bridge's address must be deleted and re-added.
|
|
stepName: "Modify UL prefix - address change",
|
|
fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/64",
|
|
expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"},
|
|
},
|
|
{
|
|
// Modify the prefix length, the default bridge's address should not change.
|
|
stepName: "Modify UL prefix - no address change",
|
|
fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/80",
|
|
// The prefix length displayed by 'ip a' is not updated - it's informational, and
|
|
// can't be changed without unnecessarily deleting and re-adding the address.
|
|
expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Link-Local Subnet Changes",
|
|
steps: []testStep{
|
|
{
|
|
stepName: "Standard LL subnet prefix",
|
|
fixedCIDRV6: "fe80::/64",
|
|
expAddrs: []string{"fe80::1/64"},
|
|
},
|
|
{
|
|
// Modify that prefix, the default bridge's address must be deleted and re-added.
|
|
// The bridge must still have an address in the required (standard) LL subnet.
|
|
stepName: "Nonstandard LL prefix - address change",
|
|
fixedCIDRV6: "fe80:1234::/32",
|
|
expAddrs: []string{"fe80:1234::1/32", "fe80::1/64"},
|
|
},
|
|
{
|
|
// Modify the prefix length, the addresses should not change.
|
|
stepName: "Modify LL prefix - no address change",
|
|
fixedCIDRV6: "fe80:1234::/64",
|
|
// The prefix length displayed by 'ip a' is not updated - it's informational, and
|
|
// can't be changed without unnecessarily deleting and re-adding the address.
|
|
expAddrs: []string{"fe80:1234::1/", "fe80::1/64"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
for _, step := range tc.steps {
|
|
// Check that the daemon starts - regression test for:
|
|
// https://github.com/moby/moby/issues/46829
|
|
d.Start(t, "--experimental", "--ipv6", "--ip6tables", "--fixed-cidr-v6="+step.fixedCIDRV6)
|
|
d.Stop(t)
|
|
|
|
// Check that the expected addresses have been applied to the bridge. (Skip in
|
|
// rootless mode, because the bridge is in a different network namespace.)
|
|
if !testEnv.IsRootless() {
|
|
res := testutil.RunCommand(ctx, "ip", "-6", "addr", "show", "docker0")
|
|
assert.Equal(t, res.ExitCode, 0, step.stepName)
|
|
stdout := res.Stdout()
|
|
for _, expAddr := range step.expAddrs {
|
|
assert.Check(t, is.Contains(stdout, expAddr))
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that a container on an 'internal' network has IP connectivity with
|
|
// the host (on its own subnet, because the n/w bridge has an address on that
|
|
// subnet, and it's in the host's namespace).
|
|
// Regression test for https://github.com/moby/moby/issues/47329
|
|
func TestInternalNwConnectivity(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()
|
|
|
|
const bridgeName = "intnw"
|
|
const gw4 = "172.30.0.1"
|
|
const gw6 = "fda9:4130:4715::1234"
|
|
network.CreateNoError(ctx, t, c, bridgeName,
|
|
network.WithInternal(),
|
|
network.WithIPv6(),
|
|
network.WithIPAM("172.30.0.0/24", gw4),
|
|
network.WithIPAM("fda9:4130:4715::/64", gw6),
|
|
network.WithDriver("bridge"),
|
|
network.WithOption("com.docker.network.bridge.name", bridgeName),
|
|
)
|
|
defer network.RemoveNoError(ctx, t, c, bridgeName)
|
|
|
|
const ctrName = "intctr"
|
|
id := container.Run(ctx, t, c,
|
|
container.WithName(ctrName),
|
|
container.WithImage("busybox:latest"),
|
|
container.WithCmd("top"),
|
|
container.WithNetworkMode(bridgeName),
|
|
)
|
|
defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
|
|
|
|
execCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
|
defer cancel()
|
|
|
|
res := container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", gw4})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
assert.Check(t, is.Equal(res.Stderr(), ""))
|
|
assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
|
|
|
|
res = container.ExecT(execCtx, t, c, id, []string{"ping6", "-c1", "-W3", gw6})
|
|
assert.Check(t, is.Equal(res.ExitCode, 0))
|
|
assert.Check(t, is.Equal(res.Stderr(), ""))
|
|
assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
|
|
|
|
// Addresses outside the internal subnet must not be accessible.
|
|
res = container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", "8.8.8.8"})
|
|
assert.Check(t, is.Equal(res.ExitCode, 1))
|
|
assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable"))
|
|
}
|
|
|
|
// Check that the container's interface has no IPv6 address when IPv6 is
|
|
// disabled in a container via sysctl.
|
|
func TestDisableIPv6Addrs(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
|
|
|
ctx := setupTest(t)
|
|
d := daemon.New(t)
|
|
d.StartWithBusybox(ctx, t)
|
|
defer d.Stop(t)
|
|
|
|
c := d.NewClientT(t)
|
|
defer c.Close()
|
|
|
|
testcases := []struct {
|
|
name string
|
|
sysctls map[string]string
|
|
expIPv6 bool
|
|
}{
|
|
{
|
|
name: "IPv6 enabled",
|
|
expIPv6: true,
|
|
},
|
|
{
|
|
name: "IPv6 disabled",
|
|
sysctls: map[string]string{"net.ipv6.conf.all.disable_ipv6": "1"},
|
|
},
|
|
}
|
|
|
|
const netName = "testnet"
|
|
network.CreateNoError(ctx, t, c, netName,
|
|
network.WithIPv6(),
|
|
network.WithIPAM("fda0:ef3d:6430:abcd::/64", "fda0:ef3d:6430:abcd::1"),
|
|
)
|
|
defer network.RemoveNoError(ctx, t, c, netName)
|
|
|
|
inet6RE := regexp.MustCompile(`inet6[ \t]+[0-9a-f:]*`)
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx := testutil.StartSpan(ctx, t)
|
|
|
|
opts := []func(config *container.TestContainerConfig){
|
|
container.WithCmd("ip", "a"),
|
|
container.WithNetworkMode(netName),
|
|
}
|
|
if len(tc.sysctls) > 0 {
|
|
opts = append(opts, container.WithSysctls(tc.sysctls))
|
|
}
|
|
|
|
runRes := container.RunAttach(ctx, t, c, opts...)
|
|
defer c.ContainerRemove(ctx, runRes.ContainerID,
|
|
containertypes.RemoveOptions{Force: true},
|
|
)
|
|
|
|
stdout := runRes.Stdout.String()
|
|
inet6 := inet6RE.FindAllString(stdout, -1)
|
|
if tc.expIPv6 {
|
|
assert.Check(t, len(inet6) > 0, "Expected IPv6 addresses but found none.")
|
|
} else {
|
|
assert.Check(t, is.DeepEqual(inet6, []string{}, cmpopts.EquateEmpty()))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that it's possible to set a sysctl on an interface in the container.
|
|
// Regression test for https://github.com/moby/moby/issues/47619
|
|
func TestSetInterfaceSysctl(t *testing.T) {
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no sysctl on Windows")
|
|
|
|
ctx := setupTest(t)
|
|
d := daemon.New(t)
|
|
d.StartWithBusybox(ctx, t)
|
|
defer d.Stop(t)
|
|
|
|
c := d.NewClientT(t)
|
|
defer c.Close()
|
|
|
|
const scName = "net.ipv4.conf.eth0.forwarding"
|
|
opts := []func(config *container.TestContainerConfig){
|
|
container.WithCmd("sysctl", scName),
|
|
container.WithSysctls(map[string]string{scName: "1"}),
|
|
}
|
|
|
|
runRes := container.RunAttach(ctx, t, c, opts...)
|
|
defer c.ContainerRemove(ctx, runRes.ContainerID,
|
|
containertypes.RemoveOptions{Force: true},
|
|
)
|
|
|
|
stdout := runRes.Stdout.String()
|
|
assert.Check(t, is.Contains(stdout, scName))
|
|
}
|