Skip to content

Allow startup with no kernel support for ip6_tables#47918

Merged
thaJeztah merged 1 commit intomoby:masterfrom
robmry:allow_startup_with_no_ip6tables
Jun 8, 2024
Merged

Allow startup with no kernel support for ip6_tables#47918
thaJeztah merged 1 commit intomoby:masterfrom
robmry:allow_startup_with_no_ip6tables

Conversation

@robmry
Copy link
Contributor

@robmry robmry commented Jun 5, 2024

- What I did

Before #47747 enabled "ip6tables" by default, dockerd would start normally when:

  • the kernel had no IPv6 support, or
  • docker was running as docker-in-docker, in a container using legacy xtables, and the host didn't have kernel module 'ip6_tables' loaded.

Now, the bridge driver will try to set up its ip6tables chains and it'll fail. By not treating that as an error, the daemon will start and IPv4 will work normally.

A subsequent attempt to create an IPv6 network will fail with an error about ip6tables. At that point, the user's options are to set "ip6tables":false in daemon config. Or, in the DinD case, "modprobe ip6_tables".

- How I did it

After failing to set up the first of the IPv6 iptables chains, log the error but continue,

- How to verify it

Reproduced the problem on a Debian host with IPv6 disabled in its kernel, and on a Debian host using nftables but with CONFIG_IP6_NF_IPTABLES=m.

With this change, dockerd started normally and IPv4 networks containers worked.

In the DinD case, creating a network failed with:

# docker network create --ipv6 --subnet fddd::/64 nnn6
Error response from daemon: Failed to Setup IP tables: Unable to enable NAT rule:  (iptables failed: ip6tables --wait -t nat -I POSTROUTING -s fddd::/64 ! -o br-eac43969bbd1 -j MASQUERADE: ip6tables v1.8.9 (legacy): can't initialize ip6tables table `nat': Table does not exist (do you need to insmod?)

After a modprobe ip6_tables on the host, reloading the daemon (rather than restarting) leads to a different error, this time because the chains aren't set up - dockerd needs a restart at that point:

# docker network create --ipv6 --subnet fddd::/64 nnn6
Error response from daemon: Failed to Setup IP tables: Unable to enable SKIP DNAT rule:  (iptables failed: ip6tables --wait -t nat -I DOCKER -i br-c185921cec34 -j RETURN: ip6tables: No chain/target/match by that name.

With ip6_tables not loaded on the host, disabling ip6tables via daemon.json worked normally.

On the host with no kernel support for IPv6, the error on trying to create an IPv6 network is unchanged - it's unrelated to ip6tables:

# docker network create --ipv6 --subnet fddd::/64 nnn6
Error response from daemon: Cannot read IPv6 setup for bridge br-f70087706f7b: open /proc/sys/net/ipv6/conf/br-f70087706f7b/disable_ipv6: no such file or directory

- Description for the changelog

n/a - this is a missing part of the change to enable ip6tables by default

@robmry robmry requested review from akerouanton and corhere June 5, 2024 20:49
@robmry robmry self-assigned this Jun 5, 2024
@robmry robmry added kind/bugfix PR's that fix bugs area/networking/ipv6 Networking area/networking/firewalling Networking labels Jun 5, 2024
@robmry robmry added this to the 27.0.0 milestone Jun 5, 2024
if version == iptables.IPv4 {
return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain %s: %v", DockerChain, err)
}
// Log this error, but don't return it - config.EnableIP6Tables is now true by default,
Copy link
Member

Choose a reason for hiding this comment

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

Can it still be disabled? If not, then it looks like we have a check above that would never have to be checked;

if version == iptables.IPv6 && !config.EnableIP6Tables {
return nil, nil, nil, nil, errors.New("cannot create new chains, ip6tables is disabled")
}

Given that that check is present, and that there's a config for it, would it make sense to disable that config if we cannot support it?

Copy link
Member

Choose a reason for hiding this comment

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

Thinking here; given that we have an option to disable the features, would it make life easier on us to disable that feature if we cannot support it, so that we don't have to perform special handling in other places 🤔

Copy link
Contributor Author

@robmry robmry Jun 6, 2024

Choose a reason for hiding this comment

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

Yes, the default has changed to enable ip6tables by default (and to make it non-experimental). But "ip6tables": false in daemon config works.

Now the default is to have IPv6 firewall rules, so that only mapped ports are exposed - I think it'd be quite bad to just give up on ip6tables if they don't work. Particularly in the DinD case, where the host is IPv6-enabled but just doesn't have the kernel module loaded, containers would silently end up with no firewall.

So, I think it's better to require that ip6tables works, or is explicitly disabled. If ip6tables is broken and IPv6 isn't used, or IPv6 isn't available on the host, behaviour will be unchanged - IPv4 will just work. With this PR, it's only when trying to use ip6tables that an error will be raised.

In the DinD case - if the host's moby is >=27.0, it'll also have ip6tables enabled and the kernel module will be loaded before the DinD starts. The issue only arises when the host has an old moby (or ip6tables explicitly disabled). That may hit people in the 27.0 release, if they pick up a 27.0-based DinD image and haven't upgraded their host.

But, the change to enabling ip6tables by default is probably going to be significant for more people. Anyone who's using IPv6 and expecting the container to be wide-open with no port mapping will have to change their config (either to disable ip6tables, or to -p the ports they want open - and maybe to opt for direct routing / no-NAT by using -o com.docker.network.bridge.gateway_mode_ipv6=routed when creating the network).

So, we have some docs work to do, and 27.0's release note will have to highlight these changes.

Copy link
Contributor Author

@robmry robmry Jun 7, 2024

Choose a reason for hiding this comment

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

In the DinD case - if the host's moby is >=27.0, it'll also have ip6tables enabled and the kernel module will be loaded before the DinD starts. The issue only arises when the host has an old moby (or ip6tables explicitly disabled). That may hit people in the 27.0 release, if they pick up a 27.0-based DinD image and haven't upgraded their host.

I can't reproduce this on Debian that's using nftables now, so I must have made a mistake when testing it the other day.

The outer docker starts normally with ip6tables enabled. But, the ip6_tables module needed by a dev container isn't loaded.

So, ip6tables works on the host, but a modprobe ip6_tables is still needed to make it work in the dev container (and, presumably, for any DinD that's using legacy iptables).

I'm wondering if we should try a modprobe ip6_tables if we fail to create ip6 chains (or, the ip link show ip6_tables trick that @tianon mentioned on the ticket, the dev container at-least doesn't currently have a modprobe) ... but I'm not sure if that'd get us into a mess with bad mixes of nftables/xtables. Perhaps it's better to expect the user to do the modprobe, or explicitly disable ip6tables for the inner-docker.

We may also need to add ip6_tables to dockerd-rootless-setuptool.sh.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

DinD on an nftables host ...

  • a container image that uses nftables doesn't need the ip6_tables module.
  • a container image that uses xtables, does need a modprobe ip6_tables
    • this doesn't affect the docker official image, it's always nftables-based alpine.
    • it does affect our dev container (which has no modprobe, but ip link show ip6_tables magically works).

DinD on an iptables host ...

  • ip6_tables does need to be loaded
  • it will be already, if the outer docker has ip6tables enabled (it's the new docker, or it's explicitly enabled)
  • otherwise, it'll need to be modprobe'd
    • so, the official image's dockerd-entrypoint.sh will need an update for moby 27.0 (cc @tianon)

(rootless seems to be ok as it's on the host - I've not looked at dind-rootless, but guess it needs the same treatment as dind.)

So - we're probably ok ... there may be other DinD cases like our dev container where a modprobe is needed, we'll have to make sure that's documented.

Comment on lines 50 to 52
if version == iptables.IPv4 {
return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain %s: %v", DockerChain, err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is policy. It doesn't belong in low-level code. Return an error which could be asserted on using errors.As or errors.Is, and lift the decision on how to handle failure into the caller func (*driver) configure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As there's nothing really special about setting up the first of the chains, if any of them fail none will be left intact, I just moved all the logic up into driver.configure.

Before "ip6tables" was enabled by default, dockerd would start normally
when:
- the kernel had no IPv6 support, or
- docker is running as docker-in-docker, and the host doesn't have kernel
  module 'ip6_tables' loaded.

Now, the bridge driver will try to set up its ip6tables chains and it'll
fail. By not treating that as an error, the daemon will start and IPv4
will work normally.

A subsequent attempt to create an IPv6 network will fail with an error
about ip6tables. At that point, the user's options are:
- set "ip6tables":false in daemon config
- in the DinD case, "modprobe ip6_tables" on the host, or start dockerd
  on the host with ip6tables enabled (causing the kernel module load).

Signed-off-by: Rob Murray <[email protected]>
@robmry robmry force-pushed the allow_startup_with_no_ip6tables branch from 7491fe9 to 837b3f9 Compare June 7, 2024 16:43
Copy link
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

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.

dockerd now requires ip6tables to startup

4 participants