Skip to content

Ignore kernel-assigned LL addrs when selecting "bip6"#49022

Merged
thaJeztah merged 3 commits intomoby:masterfrom
robmry:ignore_kernel_ll_for_fcidrv6
Dec 6, 2024
Merged

Ignore kernel-assigned LL addrs when selecting "bip6"#49022
thaJeztah merged 3 commits intomoby:masterfrom
robmry:ignore_kernel_ll_for_fcidrv6

Conversation

@robmry
Copy link
Copy Markdown
Contributor

@robmry robmry commented Dec 3, 2024

- What I did

Noticed that, sometimes (under conditions I've not managed to repro reliably) when enabling IPv6 on the default bridge without specifying a fixed-cidr-v6, docker0 wasn't assigned an address from the daemon's implicit ULA subnet - it only got an IPv4 address and the kernel-assigned link local address.

Commit facb232 (#48319 - not shipped) aligned the way the default bridge's IPv6 subnet and gateway addresses are selected with IPv4.

Part of that involved looking at addresses already on the bridge, along with daemon config options. But, for IPv6, the kernel will assign a link-local address to the bridge - and that was confusing things.

- How I did it

Run tests that change docker0 in their own netns

These tests create iptables rules for different addresses on docker0 but, unlike tests that do that for user-defined bridges, those rules aren't removed when the test deletes the network, because the default bridge network can't be deleted.

Because of this, adding tests for LL addresses in the third commit in this PR caused TestAccessPublishedPortFromHost to fail - it uses a custom bridge and wasn't affected by the daemon change in this PR, but NAT rules left behind by the new tests broke it.

So, use (abuse) the L3Segment code to run the tests in their own network namespace.

A minor drawback is that TestDaemonDefaultBridgeWithFixedCidrButNoBip can no longer run in rootless mode, because the L3Segment can't be created in rootlesskit's netns. But the test isn't looking at anything specific to rootless mode.

Remove test env var DOCKER_TEST_CREATE_DEFAULT_BRIDGE

Env var DOCKER_TEST_CREATE_DEFAULT_BRIDGE could be set to override the name of the default bridge - without the bridge being user-managed (unlike the --bridge daemon option).

It was needed by tests looking at docker0 behaviour, using their own instance of the daemon, without breaking the
docker0 instance belonging to CI's daemon. Now, those tests run in their own netns using the name docker0.

So, remove the unused env var.

Ignore kernel-assigned LL addrs when selecting "bip6"

Make sure the kernel-assigned LL address is ignored when selecting "bip6" (when bip6 is not explicitly specified).

This is made slightly complicated because we allow fixed-cidr-v6 to be a link-local subnet (either the standard "fe80::/64", or any other non-overlapping LL subnet in "fe80::/10").

Following this change, if fixed-cidr-v6 is (or is included by) "fe80::/64", the bridge's kernel-assigned LL address may be used as the network's gateway address - even though it may also get an IPAM-assigned LL address.

- How to verify it

Updated the integration tests to make sure the bridge always has a kernel-assigned LL address - and added test cases for link-local subnets.

Without the fix, two of the updated tests fail ...

Running /go/src/github.com/docker/docker/integration/daemon (arm64.integration.daemon) flags=-test.v -test.timeout=10m -test.run TestDaemonDefaultBridgeIPAM
INFO: Testing against a local daemon
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/no_config
    daemon_linux_test.go:421: assertion failed:
        --- insp.IPAM.Config
        +++ expIPAMConfig
          []network.IPAMConfig{
          	{
        - 		Subnet:     "fe80::/64",
        + 		Subnet:     "192.168.176.0/24",
          		IPRange:    "",
        - 		Gateway:    "fe80::5448:e2ff:fe6a:4500",
        + 		Gateway:    "192.168.176.1",
          		AuxAddress: nil,
          	},
          	{
        - 		Subnet:     "192.168.176.0/24",
        + 		Subnet:     "fdd1:8161:2d2c::/64",
          		IPRange:    "",
        - 		Gateway:    "192.168.176.1",
        + 		Gateway:    "fdd1:8161:2d2c::1/64",
          		AuxAddress: nil,
          	},
          }

=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/fixed-cidr_only
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/bip_only
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/existing_bridge_address_only
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/fixed-cidr_within_old_bridge_subnet
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/link-local_fixed-cidr-v6
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/nonstandard_link-local_fixed-cidr-v6
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/fixed-cidr_within_old_bridge_subnet_with_new_bip
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/old_bridge_subnet_within_fixed-cidr
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/old_bridge_subnet_outside_fixed-cidr
=== RUN   TestDaemonDefaultBridgeIPAM_Docker0/old_bridge_subnet_outside_fixed-cidr_with_bip
--- FAIL: TestDaemonDefaultBridgeIPAM_Docker0 (6.55s)
    --- FAIL: TestDaemonDefaultBridgeIPAM_Docker0/no_config (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/fixed-cidr_only (0.59s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/bip_only (0.59s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/existing_bridge_address_only (0.59s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/fixed-cidr_within_old_bridge_subnet (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/link-local_fixed-cidr-v6 (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/nonstandard_link-local_fixed-cidr-v6 (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/fixed-cidr_within_old_bridge_subnet_with_new_bip (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/old_bridge_subnet_within_fixed-cidr (0.59s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/old_bridge_subnet_outside_fixed-cidr (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_Docker0/old_bridge_subnet_outside_fixed-cidr_with_bip (0.60s)
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/bridge_only
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_only
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.378304680Z" level=debug msg="/usr/sbin/ip6tables, [--wait -t filter -C DOCKER-USER -j RETURN]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.378768055Z" level=debug msg="/usr/sbin/ip6tables, [--wait -t filter -C FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.379219805Z" level=debug msg="/usr/sbin/ip6tables, [--wait -D FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.379704430Z" level=debug msg="/usr/sbin/ip6tables, [--wait -I FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.380792555Z" level=debug msg="/usr/sbin/iptables, [--wait -t filter -n -L DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.381250055Z" level=debug msg="/usr/sbin/iptables, [--wait -t filter -C DOCKER-USER -j RETURN]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.381705305Z" level=debug msg="/usr/sbin/iptables, [--wait -t filter -C FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.382163430Z" level=debug msg="/usr/sbin/iptables, [--wait -D FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.382651096Z" level=debug msg="/usr/sbin/iptables, [--wait -I FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.383120971Z" level=debug msg="/usr/sbin/ip6tables, [--wait -t filter -n -L DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.383612430Z" level=debug msg="/usr/sbin/ip6tables, [--wait -t filter -C DOCKER-USER -j RETURN]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.384440888Z" level=debug msg="/usr/sbin/ip6tables, [--wait -t filter -C FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.384975930Z" level=debug msg="/usr/sbin/ip6tables, [--wait -D FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.385480846Z" level=debug msg="/usr/sbin/ip6tables, [--wait -I FORWARD -j DOCKER-USER]"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.386415763Z" level=debug msg="daemon configured with a 15 seconds minimum shutdown timeout"
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.386447055Z" level=debug msg="start clean shutdown of all containers with a 15 seconds timeout..."
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.386827471Z" level=debug msg="Unix socket /tmp/dxr/daf4351ff91cf/libnetwork/940813862d38.sock was closed. The external key listener will stop."
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.387220346Z" level=debug msg="Cleaning up old mountid : start."
    daemon.go:318: [daf4351ff91cf] time="2024-12-03T17:58:32.387999721Z" level=debug msg="Cleaning up old mountid : done."
    daemon.go:318: [daf4351ff91cf] failed to start daemon: Error initializing network controller: fixed-cidr-v6=fdd1:8161:2d2c::/64 is outside any subnet implied by addresses on the user-managed default bridge
    daemon_linux_test.go:409: [daf4351ff91cf] failed to start daemon with arguments [--data-root /go/src/github.com/docker/docker/bundles/test-integration/TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_only/daf4351ff91cf/root --exec-root /tmp/dxr/daf4351ff91cf --pidfile /go/src/github.com/docker/docker/bundles/test-integration/TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_only/daf4351ff91cf/docker.pid --userland-proxy=true --containerd-namespace daf4351ff91cf --containerd-plugins-namespace daf4351ff91cfp --containerd /var/run/docker/containerd/containerd.sock --host unix:///tmp/docker-integration/daf4351ff91cf.sock --debug --storage-driver vfs --fixed-cidr 192.168.176.0/24 --fixed-cidr-v6 fdd1:8161:2d2c::/64 --ipv6 --bridge br-dbi] : [daf4351ff91cf] daemon exited during startup: exit status 1
    daemon_linux_test.go:399: [daf4351ff91cf] daemon is not started
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/fcidr_in_bridge_subnet_and_bridge_ip_in_fcidr
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/fcidr_in_bridge_subnet_and_bridge_ip_not_in_fcidr
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/link-local_fixed-cidr-v6
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/nonstandard_link-local_fixed-cidr-v6
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_bigger_than_bridge_subnet
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/no_bridge_ip_within_fixed-cidr
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_contains_bridge_subnet
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr-v6_bigger_than_bridge_subnet
    daemon_linux_test.go:399: [d77287f8daafc] daemon is not started
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/no_bridge_ip_within_fixed-cidr-v6
    daemon_linux_test.go:399: [dff5af1a1a58e] daemon is not started
=== RUN   TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr-v6_contains_bridge_subnet
    daemon_linux_test.go:399: [d47d53e1bbe3d] daemon is not started
--- FAIL: TestDaemonDefaultBridgeIPAM_UserBr (7.09s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/bridge_only (0.60s)
    --- FAIL: TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_only (0.56s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/fcidr_in_bridge_subnet_and_bridge_ip_in_fcidr (0.61s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/fcidr_in_bridge_subnet_and_bridge_ip_not_in_fcidr (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/link-local_fixed-cidr-v6 (0.59s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/nonstandard_link-local_fixed-cidr-v6 (0.60s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_bigger_than_bridge_subnet (0.59s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/no_bridge_ip_within_fixed-cidr (0.62s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr_contains_bridge_subnet (0.59s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr-v6_bigger_than_bridge_subnet (0.58s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/no_bridge_ip_within_fixed-cidr-v6 (0.58s)
    --- PASS: TestDaemonDefaultBridgeIPAM_UserBr/fixed-cidr-v6_contains_bridge_subnet (0.58s)
FAIL

- Description for the changelog

n/a

@robmry robmry added area/networking Networking kind/bugfix PR's that fix bugs area/networking/ipv6 Networking area/networking/d/bridge Networking labels Dec 3, 2024
@robmry robmry added this to the 28.0.0 milestone Dec 3, 2024
@robmry robmry self-assigned this Dec 3, 2024
@robmry robmry force-pushed the ignore_kernel_ll_for_fcidrv6 branch 5 times, most recently from 21253ae to b78c41d Compare December 4, 2024 12:01
These tests create iptables rules for different addresses on
docker0 but, unlike tests that do that for user-defined bridges,
those rules aren't removed when the test deletes the network,
because the default bridge network can't be deleted.

So, use (abuse) the L3Segment code to run the tests in their
own network namespace.

Signed-off-by: Rob Murray <[email protected]>
Env var DOCKER_TEST_CREATE_DEFAULT_BRIDGE could be set to
override the name of the default bridge - without the bridge
being user-managed (unlike the '--bridge' daemon option).

It was needed by tests looking at docker0 behaviour, using
their own instance of the daemon, without breaking the
docker0 instance belonging to CI's daemon.

Now, those tests run in their own netns using the name docker0.

So, remove the unused env var.

Signed-off-by: Rob Murray <[email protected]>
@robmry robmry force-pushed the ignore_kernel_ll_for_fcidrv6 branch from b78c41d to b4f3ba0 Compare December 4, 2024 12:02
@robmry robmry marked this pull request as ready for review December 4, 2024 14:22
Copy link
Copy Markdown
Member

@akerouanton akerouanton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra debug file that need to go away 😉 Otherwise, LGTM.

host.Do(t, func() {
// 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="))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should create a new util function in github.com/docker/docker/testutil/daemon to disable OTel. I think it'll become more common to do that as we're planning to update unit tests to use L3Segments for better test isolation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, or maybe we can have the L3Segment stuff set up a default route so that the daemon can work (more) normally in its own netns. It needs more thought.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be something we could control through the ctx := testutil.StartSpan(baseContext, t) utility? It looks like that checks if Otel is configured in the environment; perhaps that could also take an argument to run t.SetEnv() for the test (and restore it after). Not sure if t.SetEnv would be problematic if we want to run things in parallel though.

(definitely in favour of having an option for this, so that we don't have to know about specific env-vars everywhere)

Commit facb232 aligned the way the default bridge's IPv6 subnet
and gateway addresses are selected with IPv4.

Part of that involved looking at addresses already on the bridge,
along with daemon config options. But, for IPv6, the kernel will
assign a link-local address to the bridge.

Make sure that address is ignored when selecting "bip6" when it's
not explicitly specified.

This is made slightly complicated because we allow fixed-cidr-v6
to be a link-local subnet (either the standard "fe80::/64", or
any other non-overlapping LL subnet in "fe80::/10").

Following this change, if fixed-cidr-v6 is (or is included by)
"fe80::/64", the bridge's kernel-assigned LL address may be used
as the network's gateway address - even though it may also get an
IPAM-assigned LL address.

Signed-off-by: Rob Murray <[email protected]>
@robmry robmry force-pushed the ignore_kernel_ll_for_fcidrv6 branch from b4f3ba0 to 56eb47c Compare December 4, 2024 18:20
@robmry
Copy link
Copy Markdown
Contributor Author

robmry commented Dec 4, 2024

Unrelated test failures ...

=== FAIL: github.com/docker/docker/integration/container TestWaitNonBlocked/wait-nonblocking-exit-0 (60.34s)
    wait_test.go:44: assertion failed: error is not nil: error during connect: Post "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.48/containers/create": EOF
=== FAIL: github.com/docker/docker/integration/container TestLogs/driver_json-file/without_tty/only_stderr (60.47s)
    logs_test.go:133: assertion failed: error is not nil: error during connect: Post "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.48/containers/create": EOF

Copy link
Copy Markdown
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

host.Do(t, func() {
// 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="))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be something we could control through the ctx := testutil.StartSpan(baseContext, t) utility? It looks like that checks if Otel is configured in the environment; perhaps that could also take an argument to run t.SetEnv() for the test (and restore it after). Not sure if t.SetEnv would be problematic if we want to run things in parallel though.

(definitely in favour of having an option for this, so that we don't have to know about specific env-vars everywhere)

Comment thread daemon/daemon_unix.go
// link-local addresses, unless fixed-cidr-v6 has the standard link-local
// prefix. (If fixed-cidr-v6 is the standard LL prefix, the kernel-assigned
// address is likely to be used instead of an IPAM assigned address.)
if family == netlink.FAMILY_V6 && (fCidrIP == nil || !isStandardLL(fCidrIP)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (not need to fix); looks like the fCidrIP == nil is redundant here, because isStandardLL already returns false if it's nil

@thaJeztah thaJeztah merged commit 68dbb8b into moby:master Dec 6, 2024
@robmry robmry deleted the ignore_kernel_ll_for_fcidrv6 branch December 9, 2024 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants