Skip to content

Commit 57ada4b

Browse files
committed
Option to avoid deleting the kernel_ll address from bridges.
If env var DOCKER_BRIDGE_PRESERVE_KERNEL_LL=1, don't assign fe80::1/64 to a bridge, and don't delete any link local address with prefix fe80::/64. Signed-off-by: Rob Murray <[email protected]>
1 parent faf84d7 commit 57ada4b

3 files changed

Lines changed: 64 additions & 34 deletions

File tree

integration/networking/bridge_test.go

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,6 @@ func TestDefaultBridgeAddresses(t *testing.T) {
469469
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
470470

471471
ctx := setupTest(t)
472-
d := daemon.New(t)
473472

474473
type testStep struct {
475474
stepName string
@@ -487,21 +486,21 @@ func TestDefaultBridgeAddresses(t *testing.T) {
487486
{
488487
stepName: "Set up initial UL prefix",
489488
fixedCIDRV6: "fd1c:f1a0:5d8d:aaaa::/64",
490-
expAddrs: []string{"fd1c:f1a0:5d8d:aaaa::1/64", "fe80::1/64"},
489+
expAddrs: []string{"fd1c:f1a0:5d8d:aaaa::1/64", "fe80::"},
491490
},
492491
{
493492
// Modify that prefix, the default bridge's address must be deleted and re-added.
494493
stepName: "Modify UL prefix - address change",
495494
fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/64",
496-
expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"},
495+
expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::"},
497496
},
498497
{
499498
// Modify the prefix length, the default bridge's address should not change.
500499
stepName: "Modify UL prefix - no address change",
501500
fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/80",
502501
// The prefix length displayed by 'ip a' is not updated - it's informational, and
503502
// can't be changed without unnecessarily deleting and re-adding the address.
504-
expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"},
503+
expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::"},
505504
},
506505
},
507506
},
@@ -511,47 +510,63 @@ func TestDefaultBridgeAddresses(t *testing.T) {
511510
{
512511
stepName: "Standard LL subnet prefix",
513512
fixedCIDRV6: "fe80::/64",
514-
expAddrs: []string{"fe80::1/64"},
513+
expAddrs: []string{"fe80::"},
515514
},
516515
{
517516
// Modify that prefix, the default bridge's address must be deleted and re-added.
518517
// The bridge must still have an address in the required (standard) LL subnet.
519518
stepName: "Nonstandard LL prefix - address change",
520519
fixedCIDRV6: "fe80:1234::/32",
521-
expAddrs: []string{"fe80:1234::1/32", "fe80::1/64"},
520+
expAddrs: []string{"fe80:1234::1/32", "fe80::"},
522521
},
523522
{
524523
// Modify the prefix length, the addresses should not change.
525524
stepName: "Modify LL prefix - no address change",
526525
fixedCIDRV6: "fe80:1234::/64",
527526
// The prefix length displayed by 'ip a' is not updated - it's informational, and
528527
// can't be changed without unnecessarily deleting and re-adding the address.
529-
expAddrs: []string{"fe80:1234::1/", "fe80::1/64"},
528+
expAddrs: []string{"fe80:1234::1/", "fe80::"},
530529
},
531530
},
532531
},
533532
}
534533

535-
for _, tc := range testcases {
536-
t.Run(tc.name, func(t *testing.T) {
534+
for _, preserveKernelLL := range []bool{false, true} {
535+
var dopts []daemon.Option
536+
if preserveKernelLL {
537+
dopts = append(dopts, daemon.WithEnvVars("DOCKER_BRIDGE_PRESERVE_KERNEL_LL=1"))
538+
}
539+
d := daemon.New(t, dopts...)
540+
c := d.NewClientT(t)
541+
542+
for _, tc := range testcases {
537543
for _, step := range tc.steps {
538-
// Check that the daemon starts - regression test for:
539-
// https://github.com/moby/moby/issues/46829
540-
d.Start(t, "--experimental", "--ipv6", "--ip6tables", "--fixed-cidr-v6="+step.fixedCIDRV6)
541-
d.Stop(t)
542-
543-
// Check that the expected addresses have been applied to the bridge. (Skip in
544-
// rootless mode, because the bridge is in a different network namespace.)
545-
if !testEnv.IsRootless() {
546-
res := testutil.RunCommand(ctx, "ip", "-6", "addr", "show", "docker0")
547-
assert.Equal(t, res.ExitCode, 0, step.stepName)
548-
stdout := res.Stdout()
549-
for _, expAddr := range step.expAddrs {
550-
assert.Check(t, is.Contains(stdout, expAddr))
544+
tcName := fmt.Sprintf("kernel_ll_%v/%s/%s", preserveKernelLL, tc.name, step.stepName)
545+
t.Run(tcName, func(t *testing.T) {
546+
ctx := testutil.StartSpan(ctx, t)
547+
// Check that the daemon starts - regression test for:
548+
// https://github.com/moby/moby/issues/46829
549+
d.StartWithBusybox(ctx, t, "--experimental", "--ipv6", "--ip6tables", "--fixed-cidr-v6="+step.fixedCIDRV6)
550+
551+
// Start a container, so that the bridge is set "up" and gets a kernel_ll address.
552+
cID := container.Run(ctx, t, c)
553+
defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true})
554+
555+
d.Stop(t)
556+
557+
// Check that the expected addresses have been applied to the bridge. (Skip in
558+
// rootless mode, because the bridge is in a different network namespace.)
559+
if !testEnv.IsRootless() {
560+
res := testutil.RunCommand(ctx, "ip", "-6", "addr", "show", "docker0")
561+
assert.Equal(t, res.ExitCode, 0, step.stepName)
562+
stdout := res.Stdout()
563+
for _, expAddr := range step.expAddrs {
564+
assert.Check(t, is.Contains(stdout, expAddr))
565+
}
551566
}
552-
}
567+
})
553568
}
554-
})
569+
}
555570
}
556571
}
557572

libnetwork/drivers/bridge/interface_linux.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net"
77
"net/netip"
8+
"os"
89

910
"github.com/containerd/log"
1011
"github.com/docker/docker/errdefs"
@@ -73,18 +74,20 @@ func (i *bridgeInterface) addresses(family int) ([]netlink.Addr, error) {
7374
func getRequiredIPv6Addrs(config *networkConfiguration) (requiredAddrs map[netip.Addr]netip.Prefix, err error) {
7475
requiredAddrs = make(map[netip.Addr]netip.Prefix)
7576

76-
// Always give the bridge 'fe80::1' - every interface is required to have an
77-
// address in 'fe80::/64'. Linux may assign an address, but we'll replace it with
78-
// 'fe80::1'. Then, if the configured prefix is 'fe80::/64', the IPAM pool
79-
// assigned address will not be a second address in the LL subnet.
80-
ra, ok := netiputil.ToPrefix(bridgeIPv6)
81-
if !ok {
82-
err = fmt.Errorf("Failed to convert Link-Local IPv6 address to netip.Prefix")
83-
return nil, err
77+
if os.Getenv("DOCKER_BRIDGE_PRESERVE_KERNEL_LL") != "1" {
78+
// Always give the bridge 'fe80::1' - every interface is required to have an
79+
// address in 'fe80::/64'. Linux may assign an address, but we'll replace it with
80+
// 'fe80::1'. Then, if the configured prefix is 'fe80::/64', the IPAM pool
81+
// assigned address will not be a second address in the LL subnet.
82+
ra, ok := netiputil.ToPrefix(bridgeIPv6)
83+
if !ok {
84+
err = fmt.Errorf("Failed to convert Link-Local IPv6 address to netip.Prefix")
85+
return nil, err
86+
}
87+
requiredAddrs[ra.Addr()] = ra
8488
}
85-
requiredAddrs[ra.Addr()] = ra
8689

87-
ra, ok = netiputil.ToPrefix(config.AddressIPv6)
90+
ra, ok := netiputil.ToPrefix(config.AddressIPv6)
8891
if !ok {
8992
err = fmt.Errorf("failed to convert bridge IPv6 address '%s' to netip.Prefix", config.AddressIPv6.String())
9093
return nil, err
@@ -116,6 +119,14 @@ func (i *bridgeInterface) programIPv6Addresses(config *networkConfiguration) err
116119
if !ok {
117120
return errdefs.System(fmt.Errorf("Failed to convert IPv6 address '%s' to netip.Addr", config.AddressIPv6))
118121
}
122+
// Optionally, avoid deleting the kernel-assigned link local address.
123+
// (Don't delete fe80::1 either - if it was previously assigned to the bridge, and the
124+
// kernel_ll address was deleted, the bridge won't get a new kernel_ll address.)
125+
if os.Getenv("DOCKER_BRIDGE_PRESERVE_KERNEL_LL") == "1" {
126+
if p, _ := ea.Prefix(64); p == linkLocalPrefix {
127+
continue
128+
}
129+
}
119130
// Ignore the prefix length when comparing addresses, it's informational
120131
// (RFC-5942 section 4), and removing/re-adding an address that's still valid
121132
// would disrupt traffic on live-restore.

libnetwork/drivers/bridge/setup_ipv6_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"net"
7+
"net/netip"
78
"os"
89

910
"github.com/containerd/log"
@@ -13,6 +14,9 @@ import (
1314
// bridgeIPv6 is the default, link-local IPv6 address for the bridge (fe80::1/64)
1415
var bridgeIPv6 = &net.IPNet{IP: net.ParseIP("fe80::1"), Mask: net.CIDRMask(64, 128)}
1516

17+
// Standard link local prefix
18+
var linkLocalPrefix = netip.MustParsePrefix("fe80::/64")
19+
1620
const (
1721
ipv6ForwardConfPerm = 0o644
1822
ipv6ForwardConfDefault = "/proc/sys/net/ipv6/conf/default/forwarding"

0 commit comments

Comments
 (0)