Skip to content

fix: enable IPv6 on Docker proxy_network for native IPv6 UDP support#4

Merged
josecelano merged 7 commits into
mainfrom
2-udp-tracker-down-on-newtrackon
Mar 10, 2026
Merged

fix: enable IPv6 on Docker proxy_network for native IPv6 UDP support#4
josecelano merged 7 commits into
mainfrom
2-udp-tracker-down-on-newtrackon

Conversation

@josecelano

Copy link
Copy Markdown
Member

Summary

Fixes #2udp://udp1.torrust-tracker-demo.com:6969/announce showing as down on newTrackon after nightly Docker restart.

Two root causes were identified and fixed.


Root Cause A (Fix A — already committed)

Docker's default ip6tables: false causes Docker chain rewrites to wipe ufw's live ip6tables rules after every container restart. IPv6 UDP packets arriving after a restart were silently dropped.

Fix: server/etc/docker/daemon.json with {"ip6tables": true}.

Root Cause B (Fix B — this PR)

Docker bridge networks were IPv4-only, so the container had no IPv6 address. Docker created no ip6tables DNAT rules, leaving docker-proxy as the sole IPv6 handler. docker-proxy attempted cross-AF UDP relay (IPv6 host socket → IPv4 container backend 172.21.0.3), which silently drops all native IPv6 UDP packets.

Additionally, even with DNAT working, Docker's MASQUERADE rewrote reply source addresses to the server's primary IPv6 (2a01:4f8:1c19:620b::1) instead of the floating IPv6 (2a01:4f8:1c0c:828e::1). Clients that probed the floating IP received replies from the wrong address and timed out.

Packet flow (after fix)

Client → floating IPv6 2a01:4f8:1c0c:828e::1:6969
       → ip6tables DNAT → container fd01:db8:1::3:6969
       → container replies → ip6tables SNAT → source rewritten to 2a01:4f8:1c0c:828e::1
       → client receives reply from correct address ✅

Changes

  • docker-compose.yml: add enable_ipv6: true + subnet: fd01:db8:1::/64 to proxy_network
  • server/etc/ufw/before6.rules: add full file snapshot including SNAT rule
  • docs/docker-ipv6.md: rewrite to cover both problems with packet-flow diagram
  • docs/post-deployment.md: add Step 3 — SNAT rule for before6.rules
  • docs/post-mortems/2026-03-09-udp-ipv6-docker.md: full diagnostic chain (Steps 1–12), Root Cause B, Fix B decision
  • docs/issues/ISSUE-2-udp-tracker-down-on-newtrackon.md: acceptance criteria updated
  • server/README.md: Key Configuration Notes expanded
  • project-words.txt: new networking terms added
  • cspell.json: exclude verbatim UFW rules files from spell check

Manual server-side step required on fresh deploys

Prepend to /etc/ufw/before6.rules:

*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s fd01:db8:1::/64 -o eth0 -p udp --sport 6969 -j SNAT --to-source 2a01:4f8:1c0c:828e::1
COMMIT

Then sudo ufw reload. See docs/post-deployment.md Step 3.


Verification

Confirmed working on newTrackon 2026-03-09: both IPv6 (2a01:4f8:1c0c:828e::1) and IPv4 (116.202.177.184) reporting as up.

Remaining open criterion: fix survives a nightly Docker Compose restart (to verify after tonight's backup cron run).

Refs: #2

Docker's default ip6tables: false causes its chain rewrites to wipe
ufw's live ip6tables rules after every container restart. This silently
dropped all IPv6 UDP port 6969 traffic, causing the UDP tracker to show
as down on newTrackon after the nightly backup restart.

Fix: add /etc/docker/daemon.json with ip6tables: true. Docker now manages
ip6tables rules for all published ports automatically, mirroring its IPv4
behaviour. The fix is systemic — no per-port or per-container configuration
needed, and it covers future UDP trackers on any port.

Verified on 2026-03-09:
- ufw6-user-input rule for udp/6969 survives docker compose restart
- udp://udp1.torrust-tracker-demo.com:6969/announce back up on newTrackon

Refs: #2
- Add docs/docker-ipv6.md: operations reference explaining why
  ip6tables: true is required, its scope, what it does not cover
  (floating IP routing), and server application + verification steps
- Add docs/post-mortems/2026-03-09-udp-ipv6-docker.md: full
  investigation log with commands, outputs, root cause analysis and
  fix decision rationale
- Update docs/issues/ISSUE-2-udp-tracker-down-on-newtrackon.md:
  add Fix Applied section, tick off completed acceptance criteria
- Update server/README.md: add Key Configuration Notes section
  cross-referencing daemon.json and netplan floating IP config

Refs: #2
The deployer handles basic provisioning but does not cover floating IP
routing or Docker IPv6 ip6tables configuration. Add docs/post-deployment.md
as the canonical checklist of manual steps required after running the
deployer, with a section for each concern:

1. Floating IP routing (netplan policy routing — required per floating IP)
2. Docker IPv6 for UDP trackers (daemon.json ip6tables: true — required
   only for IPv6 UDP announces)

- Add deployer callout to docs/docker-ipv6.md pointing to the checklist
- Add post-deployment link to README.md

Refs: #2
Root Cause B: Docker bridge networks were IPv4-only, so no container IPv6
address existed. Docker created no ip6tables DNAT rules, leaving docker-proxy
as sole handler. docker-proxy attempted cross-AF UDP relay (IPv6 host socket
to IPv4 container backend 172.21.0.3), which silently drops all native IPv6
UDP packets.

Changes:
- docker-compose.yml: add enable_ipv6: true + subnet fd01:db8:1::/64 to
  proxy_network so the container receives a ULA IPv6 address and Docker
  generates ip6tables DNAT rules, bypassing docker-proxy entirely
- docs/post-mortems/2026-03-09-udp-ipv6-docker.md: add Steps 9-12
  (docker logs grep, ps aux smoking gun), Root Cause B section, Fix B
  decision rationale
- docs/docker-ipv6.md: rewrite to cover both problems (chain wipe and
  docker-proxy cross-AF) and both fixes (daemon.json and enable_ipv6 + SNAT)
- docs/post-deployment.md: add Step 3 with exact SNAT rule for before6.rules
  to override Docker MASQUERADE and pin UDP 6969 replies to the floating IPv6
  2a01:4f8:1c0c:828e::1
- docs/issues/ISSUE-2-udp-tracker-down-on-newtrackon.md: tick off newTrackon
  acceptance criterion (confirmed 2026-03-09), update survival criterion
- server/README.md: expand Key Configuration Notes with daemon.json,
  enable_ipv6, and SNAT subsections
- project-words.txt: add SNAT, SNATs, POSTROUTING, PREROUTING, userland,
  ulnp, UNCONN, misrouted, ADDRTYPE, pkts

Fix verified: newTrackon reports tracker working for both IPv6 and IPv4
(2a01:4f8:1c0c:828e::1 and 116.202.177.184) as of 2026-03-09.

A manual server-side step is still required on fresh deploys: prepend a
*nat POSTROUTING SNAT rule to /etc/ufw/before6.rules and run sudo ufw reload.
See docs/post-deployment.md Step 3 for the exact lines.
Add the full /etc/ufw/before6.rules file to server/etc/ufw/ so the
repo captures the complete server UFW configuration, including the
custom *nat POSTROUTING SNAT rule added as part of Fix B.

Also exclude the three UFW rules files from cspell (verbatim system
files with non-prose content like icmpv6 type names) and update
docs/infrastructure.md and docs/post-deployment.md to reference the
new file.
@josecelano josecelano self-assigned this Mar 9, 2026
@josecelano

Copy link
Copy Markdown
Member Author
image

@josecelano josecelano requested a review from da2ce7 March 9, 2026 20:03
@josecelano

Copy link
Copy Markdown
Member Author

ACK 3b331cd

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.

fix: UDP tracker down on newTrackon after nightly restart

1 participant