1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
|
package network
import (
"bytes"
"context"
"fmt"
"os/exec"
"slices"
"strings"
"syscall"
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/internal/testutils/networking"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/daemon"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/icmd"
"gotest.tools/v3/skip"
)
func TestRunContainerWithBridgeNone(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run")
skip.If(t, testEnv.IsUserNamespace)
skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
ctx := testutil.StartSpan(baseContext, t)
d := daemon.New(t)
d.StartWithBusybox(ctx, t, "-b", "none")
defer d.Stop(t)
c := d.NewClientT(t)
id1 := container.Run(ctx, t, c)
defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{Force: true})
result, err := container.Exec(ctx, c, id1, []string{"ip", "l"})
assert.NilError(t, err)
assert.Check(t, is.Equal(false, strings.Contains(result.Combined(), "eth0")), "There shouldn't be eth0 in container in default(bridge) mode when bridge network is disabled")
id2 := container.Run(ctx, t, c, container.WithNetworkMode("bridge"))
defer c.ContainerRemove(ctx, id2, containertypes.RemoveOptions{Force: true})
result, err = container.Exec(ctx, c, id2, []string{"ip", "l"})
assert.NilError(t, err)
assert.Check(t, is.Equal(false, strings.Contains(result.Combined(), "eth0")), "There shouldn't be eth0 in container in bridge mode when bridge network is disabled")
nsCommand := "ls -l /proc/self/ns/net | awk -F '->' '{print $2}'"
cmd := exec.Command("sh", "-c", nsCommand)
stdout := bytes.NewBuffer(nil)
cmd.Stdout = stdout
err = cmd.Run()
assert.NilError(t, err, "Failed to get current process network namespace: %+v", err)
id3 := container.Run(ctx, t, c, container.WithNetworkMode("host"))
defer c.ContainerRemove(ctx, id3, containertypes.RemoveOptions{Force: true})
result, err = container.Exec(ctx, c, id3, []string{"sh", "-c", nsCommand})
assert.NilError(t, err)
assert.Check(t, is.Equal(stdout.String(), result.Combined()), "The network namespace of container should be the same with host when --net=host and bridge network is disabled")
}
func TestHostIPv4BridgeLabel(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
ctx := testutil.StartSpan(baseContext, t)
d := daemon.New(t)
d.Start(t)
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
ipv4SNATAddr := "172.0.0.172"
// Create a bridge network with --opt com.docker.network.host_ipv4=172.0.0.172
bridgeName := "hostIPv4Bridge"
network.CreateNoError(ctx, t, c, bridgeName,
network.WithDriver("bridge"),
network.WithOption("com.docker.network.host_ipv4", ipv4SNATAddr),
network.WithOption("com.docker.network.bridge.name", bridgeName),
)
defer network.RemoveNoError(ctx, t, c, bridgeName)
out, err := c.NetworkInspect(ctx, bridgeName, networktypes.InspectOptions{Verbose: true})
assert.NilError(t, err)
assert.Assert(t, len(out.IPAM.Config) > 0)
// Make sure the SNAT rule exists
if testEnv.FirewallBackendDriver() == "nftables" {
chain := testutil.RunCommand(ctx, "nft", "--stateless", "list", "chain", "ip", "docker-bridges", "nat-postrouting-out__hostIPv4Bridge").Combined()
exp := fmt.Sprintf(`oifname != "hostIPv4Bridge" ip saddr %s counter snat to %s comment "SNAT"`,
out.IPAM.Config[0].Subnet, ipv4SNATAddr)
assert.Check(t, is.Contains(chain, exp))
} else {
testutil.RunCommand(ctx, "iptables", "-t", "nat", "-C", "POSTROUTING", "-s", out.IPAM.Config[0].Subnet, "!", "-o", bridgeName, "-j", "SNAT", "--to-source", ipv4SNATAddr).Assert(t, icmd.Success)
}
}
func TestDefaultNetworkOpts(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
ctx := testutil.StartSpan(baseContext, t)
tests := []struct {
name string
mtu int
configFrom bool
args []string
}{
{
name: "default value",
mtu: 1500,
args: []string{},
},
{
name: "cmdline value",
mtu: 1234,
args: []string{"--default-network-opt", "bridge=com.docker.network.driver.mtu=1234"},
},
{
name: "config-from value",
configFrom: true,
mtu: 1233,
args: []string{"--default-network-opt", "bridge=com.docker.network.driver.mtu=1234"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
d := daemon.New(t)
d.StartWithBusybox(ctx, t, tc.args...)
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
if tc.configFrom {
// Create a new network config
network.CreateNoError(ctx, t, c, "from-net", func(create *networktypes.CreateOptions) {
create.ConfigOnly = true
create.Options = map[string]string{
"com.docker.network.driver.mtu": fmt.Sprint(tc.mtu),
}
})
defer c.NetworkRemove(ctx, "from-net")
}
// Create a new network
networkName := "testnet"
networkId := network.CreateNoError(ctx, t, c, networkName, func(create *networktypes.CreateOptions) {
if tc.configFrom {
create.ConfigFrom = &networktypes.ConfigReference{
Network: "from-net",
}
}
})
defer c.NetworkRemove(ctx, networkName)
// Check the MTU of the bridge itself, before any devices are connected. (The
// bridge's MTU will be set to the minimum MTU of anything connected to it, but
// it's set explicitly on the bridge anyway - so it doesn't look like the option
// was ignored.)
cmd := exec.Command("ip", "link", "show", "br-"+networkId[:12])
output, err := cmd.CombinedOutput()
assert.NilError(t, err)
assert.Check(t, is.Contains(string(output), fmt.Sprintf(" mtu %d ", tc.mtu)), "Bridge MTU should have been set to %d", tc.mtu)
// Start a container to inspect the MTU of its network interface
id1 := container.Run(ctx, t, c, container.WithNetworkMode(networkName))
defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{Force: true})
result, err := container.Exec(ctx, c, id1, []string{"ip", "l", "show", "eth0"})
assert.NilError(t, err)
assert.Check(t, is.Contains(result.Combined(), fmt.Sprintf(" mtu %d ", tc.mtu)), "Network MTU should have been set to %d", tc.mtu)
})
}
}
func TestForbidDuplicateNetworkNames(t *testing.T) {
ctx := testutil.StartSpan(baseContext, t)
d := daemon.New(t)
d.StartWithBusybox(ctx, t)
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
network.CreateNoError(ctx, t, c, "testnet")
defer network.RemoveNoError(ctx, t, c, "testnet")
_, err := c.NetworkCreate(ctx, "testnet", networktypes.CreateOptions{})
assert.Error(t, err, "Error response from daemon: network with name testnet already exists", "2nd NetworkCreate call should have failed")
}
// TestHostGatewayFromDocker0 checks that, when docker0 has IPv6, host-gateway maps to both IPv4 and IPv6.
func TestHostGatewayFromDocker0(t *testing.T) {
ctx := testutil.StartSpan(baseContext, t)
// Run the daemon in its own n/w namespace, to avoid interfering with
// the docker0 bridge belonging to the daemon started by CI.
const name = "host-gw-ips"
l3 := networking.NewL3Segment(t, "test-"+name)
defer l3.Destroy(t)
l3.AddHost(t, "host-gw-ips", "host-gw-ips", "eth0")
// Run without OTEL because there's no routing from this netns for it - which
// means the daemon doesn't shut down cleanly, causing the test to fail.
d := daemon.New(t, daemon.WithEnvVars("OTEL_EXPORTER_OTLP_ENDPOINT="))
l3.Hosts[name].Do(t, func() {
d.StartWithBusybox(ctx, t, "--ipv6",
"--fixed-cidr", "192.168.50.0/24",
"--fixed-cidr-v6", "fddd:6ff4:6e08::/64",
)
})
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
res := container.RunAttach(ctx, t, c,
container.WithExtraHost("hg:host-gateway"),
container.WithCmd("grep", "hg$", "/etc/hosts"),
)
assert.Check(t, is.Equal(res.ExitCode, 0))
assert.Check(t, is.Contains(res.Stdout.String(), "192.168.50.1\thg"))
assert.Check(t, is.Contains(res.Stdout.String(), "fddd:6ff4:6e08::1\thg"))
}
func TestCreateWithPriority(t *testing.T) {
// This feature should work on Windows, but the test is skipped because:
// 1. Linux-specific tools are used here; 2. 'windows' IPAM driver doesn't
// support static allocations.
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.48"), "requires API v1.48")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
network.CreateNoError(ctx, t, apiClient, "testnet1",
network.WithIPv6(),
network.WithIPAM("10.100.20.0/24", "10.100.20.1"),
network.WithIPAM("fd54:7a1b:8269::/64", "fd54:7a1b:8269::1"))
defer network.RemoveNoError(ctx, t, apiClient, "testnet1")
network.CreateNoError(ctx, t, apiClient, "testnet2",
network.WithIPv6(),
network.WithIPAM("10.100.30.0/24", "10.100.30.1"),
network.WithIPAM("fdff:6dfe:37d2::/64", "fdff:6dfe:37d2::1"))
defer network.RemoveNoError(ctx, t, apiClient, "testnet2")
ctrID := container.Run(ctx, t, apiClient,
container.WithCmd("sleep", "infinity"),
container.WithNetworkMode("testnet1"),
container.WithEndpointSettings("testnet1", &networktypes.EndpointSettings{GwPriority: 10}),
container.WithEndpointSettings("testnet2", &networktypes.EndpointSettings{GwPriority: 100}))
defer container.Remove(ctx, t, apiClient, ctrID, containertypes.RemoveOptions{Force: true})
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 3, "default via 10.100.30.1 dev")
// IPv6 routing table will contain for each interface, one route for the LL
// address, one for the ULA, and one multicast.
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 7, "default via fdff:6dfe:37d2::1 dev")
}
func TestConnectWithPriority(t *testing.T) {
// This feature should work on Windows, but the test is skipped because:
// 1. Linux-specific tools are used here; 2. 'windows' IPAM driver doesn't
// support static allocations.
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.48"), "requires API v1.48")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
network.CreateNoError(ctx, t, apiClient, "testnet1",
network.WithIPv6(),
network.WithIPAM("10.100.10.0/24", "10.100.10.1"),
network.WithIPAM("fddd:4901:f594::/64", "fddd:4901:f594::1"))
defer network.RemoveNoError(ctx, t, apiClient, "testnet1")
network.CreateNoError(ctx, t, apiClient, "testnet2",
network.WithIPv6(),
network.WithIPAM("10.100.20.0/24", "10.100.20.1"),
network.WithIPAM("fd83:7683:7008::/64", "fd83:7683:7008::1"))
defer network.RemoveNoError(ctx, t, apiClient, "testnet2")
network.CreateNoError(ctx, t, apiClient, "testnet3",
network.WithDriver("bridge"),
network.WithIPv6(),
network.WithIPAM("10.100.30.0/24", "10.100.30.1"),
network.WithIPAM("fd72:de0:adad::/64", "fd72:de0:adad::1"))
defer network.RemoveNoError(ctx, t, apiClient, "testnet3")
network.CreateNoError(ctx, t, apiClient, "testnet4",
network.WithIPv6(),
network.WithIPAM("10.100.40.0/24", "10.100.40.1"),
network.WithIPAM("fd4c:c927:7d90::/64", "fd4c:c927:7d90::1"))
defer network.RemoveNoError(ctx, t, apiClient, "testnet4")
network.CreateNoError(ctx, t, apiClient, "testnet5",
network.WithIPv6(),
network.WithIPAM("10.100.50.0/24", "10.100.50.1"),
network.WithIPAM("fd4c:364b:1110::/64", "fd4c:364b:1110::1"))
defer network.RemoveNoError(ctx, t, apiClient, "testnet5")
ctrID := container.Run(ctx, t, apiClient,
container.WithCmd("sleep", "infinity"),
container.WithNetworkMode("testnet1"),
container.WithEndpointSettings("testnet1", &networktypes.EndpointSettings{}))
defer container.Remove(ctx, t, apiClient, ctrID, containertypes.RemoveOptions{Force: true})
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 2, "default via 10.100.10.1 dev eth0")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 4, "default via fddd:4901:f594::1 dev eth0")
// testnet5 has a negative priority -- the default gateway should not change.
err := apiClient.NetworkConnect(ctx, "testnet5", ctrID, &networktypes.EndpointSettings{GwPriority: -100})
assert.NilError(t, err)
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 3, "default via 10.100.10.1 dev eth0")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 7, "default via fddd:4901:f594::1 dev eth0")
// testnet2 has a higher priority. It should now provide the default gateway.
err = apiClient.NetworkConnect(ctx, "testnet2", ctrID, &networktypes.EndpointSettings{GwPriority: 100})
assert.NilError(t, err)
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 4, "default via 10.100.20.1 dev eth2")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 10, "default via fd83:7683:7008::1 dev eth2")
// testnet3 has a lower priority, so testnet2 should still provide the default gateway.
err = apiClient.NetworkConnect(ctx, "testnet3", ctrID, &networktypes.EndpointSettings{GwPriority: 10})
assert.NilError(t, err)
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 5, "default via 10.100.20.1 dev eth2")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 13, "default via fd83:7683:7008::1 dev eth2")
// testnet4 has the same priority as testnet3, but it sorts after in
// lexicographic order. For now, testnet2 stays the default gateway.
err = apiClient.NetworkConnect(ctx, "testnet4", ctrID, &networktypes.EndpointSettings{GwPriority: 10})
assert.NilError(t, err)
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 6, "default via 10.100.20.1 dev eth2")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 16, "default via fd83:7683:7008::1 dev eth2")
inspect := container.Inspect(ctx, t, apiClient, ctrID)
assert.Equal(t, inspect.NetworkSettings.Networks["testnet1"].GwPriority, 0)
assert.Equal(t, inspect.NetworkSettings.Networks["testnet2"].GwPriority, 100)
assert.Equal(t, inspect.NetworkSettings.Networks["testnet3"].GwPriority, 10)
assert.Equal(t, inspect.NetworkSettings.Networks["testnet4"].GwPriority, 10)
assert.Equal(t, inspect.NetworkSettings.Networks["testnet5"].GwPriority, -100)
// Disconnect testnet2, so testnet3 should now provide the default gateway.
// When two endpoints have the same priority (eg. testnet3 vs testnet4),
// the one that sorts first in lexicographic order is picked.
err = apiClient.NetworkDisconnect(ctx, "testnet2", ctrID, true)
assert.NilError(t, err)
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 5, "default via 10.100.30.1 dev eth3")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 13, "default via fd72:de0:adad::1 dev eth3")
// Disconnect testnet3, so testnet4 should now provide the default gateway.
err = apiClient.NetworkDisconnect(ctx, "testnet3", ctrID, true)
assert.NilError(t, err)
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 4, "default via 10.100.40.1 dev eth4")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 10, "default via fd4c:c927:7d90::1 dev eth4")
// Disconnect testnet4, so testnet1 should now provide the default gateway.
err = apiClient.NetworkDisconnect(ctx, "testnet4", ctrID, true)
assert.NilError(t, err)
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET, 3, "default via 10.100.10.1 dev eth0")
checkCtrRoutes(t, ctx, apiClient, ctrID, syscall.AF_INET6, 7, "default via fddd:4901:f594::1 dev eth0")
}
// checkCtrRoutes execute 'ip route show' in a container, and check that the
// number of routes matches expRoutes. It also checks that the default route
// matches expDefRoute. A substring match is used to avoid issues with
// non-stable interface names.
func checkCtrRoutes(t *testing.T, ctx context.Context, apiClient client.APIClient, ctrID string, af, expRoutes int, expDefRoute string) {
t.Helper()
fam := "-4"
if af == syscall.AF_INET6 {
fam = "-6"
}
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
res, err := container.Exec(ctx, apiClient, ctrID, []string{"ip", "-o", fam, "route", "show"})
assert.NilError(t, err)
assert.Equal(t, res.ExitCode, 0)
assert.Equal(t, res.Stderr(), "")
routes := slices.DeleteFunc(strings.Split(res.Stdout(), "\n"), func(s string) bool {
return s == ""
})
assert.Check(t, is.Equal(len(routes), expRoutes), "expected %d routes, got %d:\n%s", expRoutes, len(routes), strings.Join(routes, "\n"))
if expDefRoute == "" {
defFound := slices.ContainsFunc(routes, func(s string) bool {
return strings.HasPrefix(s, "default")
})
assert.Check(t, !defFound, "unexpected default route\n%s", strings.Join(routes, "\n"))
} else {
defFound := slices.ContainsFunc(routes, func(s string) bool {
return strings.Contains(s, expDefRoute)
})
assert.Check(t, defFound, "default route %q not found:\n%s", expDefRoute, strings.Join(routes, "\n"))
}
}
// TestMixL3IPVlanAndBridge checks that a container can be connected to a layer-3
// ipvlan network as well as a bridge ... the bridge network will set up a
// default gateway, if selected as the gateway endpoint. The ipvlan driver sets
// up a connected route to 0.0.0.0 or [::], a route via a specific interface with
// no next-hop address (because the next-hop address can't be ARP'd to determine
// the interface). These two types of route cannot be set up at the same time.
// So, the ipvlan's route must be treated like the default gateway and only get
// set up when the ipvlan is selected as the gateway endpoint.
// Regression test for https://github.com/moby/moby/issues/48576
func TestMixL3IPVlanAndBridge(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no ipvlan on Windows")
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.48"), "gw-priority requires API v1.48")
skip.If(t, testEnv.IsRootless, "can't see the dummy parent interface from the rootless namespace")
ctx := testutil.StartSpan(baseContext, t)
tests := []struct {
name string
liveRestore bool
}{
{
name: "no live restore",
},
{
// If the daemon is restarted with a running container, the osSbox structure
// must be repopulated correctly in order for gateways to be removed then
// re-added when network connections change.
name: "live restore",
liveRestore: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
d := daemon.New(t)
var daemonArgs []string
if tc.liveRestore {
daemonArgs = append(daemonArgs, "--live-restore")
}
d.StartWithBusybox(ctx, t, daemonArgs...)
defer d.Stop(t)
c := d.NewClientT(t)
defer c.Close()
const br46NetName = "br46net"
network.CreateNoError(ctx, t, c, br46NetName,
network.WithOption(netlabel.ContainerIfacePrefix, "bds"),
network.WithIPv6(),
network.WithIPAM("192.168.123.0/24", "192.168.123.1"),
network.WithIPAM("fd6f:36f8:3005::/64", "fd6f:36f8:3005::1"),
)
defer network.RemoveNoError(ctx, t, c, br46NetName)
const br6NetName = "br6net"
network.CreateNoError(ctx, t, c, br6NetName,
network.WithOption(netlabel.ContainerIfacePrefix, "bss"),
network.WithIPv4(false),
network.WithIPv6(),
network.WithIPAM("fdc9:adaf:b5da::/64", "fdc9:adaf:b5da::1"),
)
defer network.RemoveNoError(ctx, t, c, br6NetName)
// Create a dummy parent interface rather than letting the driver do it because,
// when the driver creates its own, it becomes a '--internal' network and no
// default route is configured.
const parentIfName = "di-dummy0"
CreateMasterDummy(ctx, t, parentIfName)
defer DeleteInterface(ctx, t, parentIfName)
const ipvNetName = "ipvnet"
network.CreateNoError(ctx, t, c, ipvNetName,
network.WithDriver("ipvlan"),
network.WithOption("ipvlan_mode", "l3"),
network.WithOption("parent", parentIfName),
network.WithIPv6(),
network.WithIPAM("192.168.124.0/24", ""),
network.WithIPAM("fd7d:8755:51ba::/64", ""),
)
defer network.RemoveNoError(ctx, t, c, ipvNetName)
// Create a container connected to all three networks, bridge network acting as gateway.
ctrId := container.Run(ctx, t, c,
container.WithNetworkMode(br46NetName),
container.WithEndpointSettings(br46NetName,
&networktypes.EndpointSettings{GwPriority: 1},
),
container.WithEndpointSettings(br6NetName, &networktypes.EndpointSettings{}),
container.WithEndpointSettings(ipvNetName, &networktypes.EndpointSettings{}),
)
defer container.Remove(ctx, t, c, ctrId, containertypes.RemoveOptions{Force: true})
if tc.liveRestore {
d.Restart(t, daemonArgs...)
}
// Expect three IPv4 routes: the default, plus one per network.
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET, 3, "default via 192.168.123.1 dev bds")
// Expect ten IPv6 routes: the default, plus UL, LL, and multicast routes per network.
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET6, 10, "default via fd6f:36f8:3005::1 dev bds")
// Disconnect the dual-stack bridge network, expect the ipvlan's default route to be set up.
c.NetworkDisconnect(ctx, br46NetName, ctrId, false)
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET, 2, "default dev eth")
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET6, 7, "default dev eth")
// Disconnect the ipvlan, expect the IPv6-only network to be the gateway, with no IPv4 gateway.
// (For this to work in the live-restore case the "dstName" of the interface must have been
// restored in the osSbox, based on matching the running interface's IPv6 address.)
c.NetworkDisconnect(ctx, ipvNetName, ctrId, false)
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET, 0, "")
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET6, 4, "default via fdc9:adaf:b5da::1 dev bss")
// Reconnect the dual-stack bridge, expect it to be the gateway for both addr families.
c.NetworkConnect(ctx, br46NetName, ctrId, &networktypes.EndpointSettings{GwPriority: 1})
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET, 2, "default via 192.168.123.1 dev bds")
checkCtrRoutes(t, ctx, c, ctrId, syscall.AF_INET6, 7, "default via fd6f:36f8:3005::1 dev bds")
})
}
}
|