Skip to content

fix(awg): auto-enable Docker IPv6 so dual-stack instances actually work - #103

Open
leonidorlov-hash wants to merge 3 commits into
PRVTPRO:mainfrom
leonidorlov-hash:fix/docker-ipv6
Open

fix(awg): auto-enable Docker IPv6 so dual-stack instances actually work#103
leonidorlov-hash wants to merge 3 commits into
PRVTPRO:mainfrom
leonidorlov-hash:fix/docker-ipv6

Conversation

@leonidorlov-hash

@leonidorlov-hash leonidorlov-hash commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Problem

The panel already writes dual-stack configs (fd42:8:1::/64 server address, fd42:8:1::N/128 peer addresses, NAT66 rules), but on a stock Docker install the daemon runs IPv4-only. Containers then have no IPv6 route out:

ping6 2606:4700:4700::1111 -> sendto: Network unreachable

Result: clients receive fd42:... addresses in their configs, but IPv6 traffic silently doesn't work — the tunnel is de facto IPv4-only. Reproduced on Debian 13 and Ubuntu 20.04 with default Docker settings.

Fix — three layers, all required

1. Enable IPv6 + NAT66 in the Docker daemon (prepare_host(), runs before every AWG instance install)

When the host has a global IPv6 address, the panel now enforces four keys in /etc/docker/daemon.json (merged, never overwriting other settings; backed up to daemon.json.bak.awp):

  • "ipv6": true + "fixed-cidr-v6": "fd00:42::/64" — containers get v6 addressing and a default route
  • "experimental": true + "ip6tables": true — without these Docker programs no NAT66 MASQUERADE rule for the ULA subnet, so v6 packets leave the host with an unroutable fd00:42:: source and blackhole

Docker is restarted only when the file actually changed. Keys are enforced unconditionally, so hosts that already have "ipv6": true but lack ip6tables still get fixed on the next install. IPv4-only hosts are untouched.

2. Enable IPv6 inside the container netns at creation time (docker run)

Even with an IPv6-enabled daemon, containers are created with net.ipv6.conf.all.disable_ipv6 = 1, and that flag is fixed at network-namespace creation — it cannot be changed afterwards:

[#] ip -4 address add 10.8.1.1/24 dev awg0     # OK
[#] ip -6 address add fd42:8:1::1/64 dev awg0  # RTNETLINK answers: Permission denied
[#] ip link delete dev awg0                    # awg-quick rolls back: awg0 is gone

Worse, awg-quick's rollback deletes the whole interface, so a failed IPv6 address add takes the working IPv4 tunnel down with it — the instance looks installed but passes no traffic at all.

So host IPv6 is detected before docker run and --sysctl net.ipv6.conf.all.disable_ipv6=0 + net.ipv6.conf.default.disable_ipv6=0 are passed at container creation. On IPv4-only hosts the sysctls are omitted, behaviour unchanged.

Testing

Verified on live servers (Debian 13), layer by layer:

  • Stock daemon: containers had no inet6 on eth0, ping6 failed.
  • After layer 1 only: ip -6 address add failed with Permission denied and awg-quick deleted awg0 — tunnel fully dead.
  • After layers 1+2 without ip6tables: interface up, v6 address and default route present, but ping6 100% loss — no NAT66 MASQUERADE on the host.
  • After all layers: disable_ipv6 = 0, fd00:42:: addressing works, host has MASQUERADE ... fd00:42::/64, ping6 succeeds, clients get working IPv6 through the tunnel (test-ipv6.com 10/10).

…l IPv6

Dual-stack instances silently ended up IPv4-only: the panel writes v6
addresses into configs and NAT66 rules, but the Docker daemon defaults
to IPv4, so containers had no v6 route out (ping6 -> Network unreachable,
clients got no IPv6 despite fd42:: addresses in their configs).

prepare_host now checks for a global IPv6 address on the host, and if
/etc/docker/daemon.json lacks ipv6 support: backs it up (daemon.json.bak.awp),
merges in {"ipv6": true, "fixed-cidr-v6": "fd00:42::/64"} (python3 merge,
fallback to a fresh file when python3 is missing), and restarts docker.
Runs once; skipped on IPv4-only hosts and when already enabled.
Even with an IPv6-enabled daemon, containers are created with
net.ipv6.conf.*.disable_ipv6=1, and the flag is fixed at netns creation.
awg-quick then fails on 'ip -6 address add' (RTNETLINK: Permission
denied) and rolls back by deleting awg0 entirely, killing IPv4 too.
Detect host IPv6 before docker run and pass the sysctls at creation.
Without ip6tables Docker creates no MASQUERADE rule for the
fixed-cidr-v6 ULA subnet, so container v6 traffic leaves with an
unroutable address. Keys are now enforced unconditionally (docker
restarted only on change) so hosts that already have ipv6:true still
pick up the missing ip6tables/experimental keys.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant