Skip to content

Commit

Permalink
Allow for a read-only "/proc/sys/net".
Browse files Browse the repository at this point in the history
If dockerd runs on a host with a read-only /proc/sys/net filesystem,
it isn't able to enable or disable IPv6 on network interfaces when
attaching a container to a network (including initial networks during
container creation).

In release 26.0.2, a read-only /proc/sys/net meant container creation
failed in all cases.

So, don't attempt to enable/disable IPv6 on an interface if it's already
set appropriately.

If it's not possible to enable IPv6 when it's needed, just log (because
that's what libnetwork has always done if IPv6 is disabled in the
kernel).

If it's not possible to disable IPv6 when it needs to be disabled,
refuse to create the container and raise an error that suggests setting
environment variable "DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1", to tell
the daemon it's ok to ignore the problem.

Signed-off-by: Rob Murray <rob.murray@docker.com>
(cherry picked from commit 01ea18f)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
  • Loading branch information
robmry authored and vvoland committed Apr 29, 2024
1 parent 21da192 commit 4061808
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 4 deletions.
75 changes: 75 additions & 0 deletions integration/networking/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -738,3 +738,78 @@ func TestSetInterfaceSysctl(t *testing.T) {
stdout := runRes.Stdout.String()
assert.Check(t, is.Contains(stdout, scName))
}

// With a read-only "/proc/sys/net" filesystem (simulated using env var
// DOCKER_TEST_RO_DISABLE_IPV6), check that if IPv6 can't be disabled on a
// container interface, container creation fails - unless the error is ignored by
// setting env var DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1.
// Regression test for https://github.com/moby/moby/issues/47751
func TestReadOnlySlashProc(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows")

ctx := setupTest(t)

testcases := []struct {
name string
daemonEnv []string
expErr string
}{
{
name: "Normality",
},
{
name: "Read only no workaround",
daemonEnv: []string{
"DOCKER_TEST_RO_DISABLE_IPV6=1",
},
expErr: "failed to disable IPv6 on container's interface eth0, set env var DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1 to ignore this error",
},
{
name: "Read only with workaround",
daemonEnv: []string{
"DOCKER_TEST_RO_DISABLE_IPV6=1",
"DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1",
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)

d := daemon.New(t, daemon.WithEnvVars(tc.daemonEnv...))
d.StartWithBusybox(ctx, t)
defer d.Stop(t)
c := d.NewClientT(t)

const net4Name = "testnet4"
network.CreateNoError(ctx, t, c, net4Name)
defer network.RemoveNoError(ctx, t, c, net4Name)
id4 := container.Create(ctx, t, c,
container.WithNetworkMode(net4Name),
container.WithCmd("ls"),
)
defer c.ContainerRemove(ctx, id4, containertypes.RemoveOptions{Force: true})
err := c.ContainerStart(ctx, id4, containertypes.StartOptions{})
if tc.expErr == "" {
assert.Check(t, err)
} else {
assert.Check(t, is.ErrorContains(err, tc.expErr))
}

// It should always be possible to create a container on an IPv6 network (IPv6
// doesn't need to be disabled on the interface).
const net6Name = "testnet6"
network.CreateNoError(ctx, t, c, net6Name,
network.WithIPv6(),
network.WithIPAM("fd5c:15e3:0b62:5395::/64", "fd5c:15e3:0b62:5395::1"),
)
defer network.RemoveNoError(ctx, t, c, net6Name)
id6 := container.Run(ctx, t, c,
container.WithNetworkMode(net6Name),
container.WithCmd("ls"),
)
defer c.ContainerRemove(ctx, id6, containertypes.RemoveOptions{Force: true})
})
}
}
38 changes: 34 additions & 4 deletions libnetwork/osl/namespace_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,17 +646,47 @@ func setIPv6(nspath, iface string, enable bool) error {
value = '0'
}

if _, err := os.Stat(path); err != nil {
if curVal, err := os.ReadFile(path); err != nil {
if os.IsNotExist(err) {
log.G(context.TODO()).WithError(err).Warn("Cannot configure IPv6 forwarding on container interface. Has IPv6 been disabled in this node's kernel?")
if enable {
log.G(context.TODO()).WithError(err).Warn("Cannot enable IPv6 on container interface. Has IPv6 been disabled in this node's kernel?")
} else {
log.G(context.TODO()).WithError(err).Debug("Not disabling IPv6 on container interface. Has IPv6 been disabled in this node's kernel?")
}
return
}
errCh <- err
return
} else if len(curVal) > 0 && curVal[0] == value {
// Nothing to do, the setting is already correct.
return
}

if err = os.WriteFile(path, []byte{value, '\n'}, 0o644); err != nil {
errCh <- fmt.Errorf("failed to %s IPv6 forwarding for container's interface %s: %w", action, iface, err)
if err = os.WriteFile(path, []byte{value, '\n'}, 0o644); err != nil || os.Getenv("DOCKER_TEST_RO_DISABLE_IPV6") != "" {
logger := log.G(context.TODO()).WithFields(log.Fields{
"error": err,
"interface": iface,
})
if enable {
// The user asked for IPv6 on the interface, and we can't give it to them.
// But, in line with the IsNotExist case above, just log.
logger.Warn("Cannot enable IPv6 on container interface, continuing.")
} else if os.Getenv("DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE") == "1" {
// TODO(robmry) - remove this escape hatch for https://github.com/moby/moby/issues/47751
// If the "/proc" file exists but isn't writable, we can't disable IPv6, which is
// https://github.com/moby/moby/security/advisories/GHSA-x84c-p2g9-rqv9 ... so,
// the user is required to override the error (or configure IPv6, or disable IPv6
// by default in the OS, or make the "/proc" file writable). Once it's possible
// to enable IPv6 without having to configure IPAM etc, the env var should be
// removed. Then the user will have to explicitly enable IPv6 if it can't be
// disabled on the interface.
logger.Info("Cannot disable IPv6 on container interface but DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1, continuing.")
} else {
logger.Error("Cannot disable IPv6 on container interface. Set env var DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1 to ignore.")
errCh <- fmt.Errorf(
"failed to %s IPv6 on container's interface %s, set env var DOCKER_ALLOW_IPV6_ON_IPV4_INTERFACE=1 to ignore this error",
action, iface)
}
return
}
}()
Expand Down

0 comments on commit 4061808

Please sign in to comment.