From 5dd1ab5217b01108ead52810b7ff101b6943de7c Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Mon, 17 Aug 2026 17:11:17 -0700 Subject: [PATCH] Stop resting the iptables case on a kernel config I cannot verify MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 3 argued the macOS rationale is stale: that Docker Desktop no longer builds from linuxkit/linuxkit and has enabled nft_fib_ipv4 since 4.35. I could not confirm or refute that from a Linux host — searching turned up no version matrix either way. But the decision does not depend on it, and the comment should not have implied it did. `xt_CONNMARK`, which the iptables path needs, was present in every kernel config examined. `nft_fib_ipv4`, which the nft path needs, was absent from the config read here and may be present in current Docker Desktop. That asymmetry is the actual argument: nftables' viability varies by Docker Desktop version in a way nobody here can pin down, iptables' requirement did not vary anywhere it was checked. If nft_fib_ipv4 is present this costs 1.6 MB and nothing else; if it is absent it is the difference between a working full tunnel and none. Rewritten to say that, and to say plainly what is verified versus assumed — this is the third round in which the previous round's central premise did not survive, and a confidently-worded paragraph is what the next round inherits. Also from review: - CLAUDE.md still said "`iptables` is deliberately absent", the opposite of what this PR now does, contradicting the Dockerfile and both other docs. - The Dockerfile referenced "the pia-vpn skill", which does not exist on this branch — the third forward reference of that kind, now gone. - "full tunnels work on native Linux, Mac and WSL2 6.6" was unconditional and contradicted ten lines later by the `DNS =` concession, which stops them on every platform. Reordered so the DNS hurdle is named as the first one. - The WSL2 gap was written as a permanent platform limitation. It is a stale install: `wsl --update` moves the host to a current kernel that has the symbol. That remedy was missing from the user-facing doc. - The migration probe label carried an internal comma, which `joinFeatures` renders into a comma-joined list. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 3 ++- HOW-TO-USE.md | 9 +++++---- app/src-tauri/src/docker/migration.rs | 2 +- container/Dockerfile | 27 +++++++++++++++++---------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3baccd7..251f2c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -315,7 +315,8 @@ container is created once by a very long function where a dropped capability is - **The tooling is baked, not installed at runtime.** `iproute2` and `wireguard-tools` are in `container/Dockerfile` because a runtime install lands in the writable layer and is lost on base-image migration — leaving a project holding the capability with nothing able to exercise it, - and no error that points at why. `iptables` is deliberately absent; see the Dockerfile comment. + and no error that points at why. `iptables` is included and `nftables` deliberately is not; see + the Dockerfile comment for why that way round. - **Anything built on this fails open.** The network namespace is rebuilt on every start and no service manager runs inside, so a tunnel never survives stop/start or recreation — while leftover `/run` state makes it look as though it did. Note the two different mechanisms: `/run` is in the diff --git a/HOW-TO-USE.md b/HOW-TO-USE.md index 38514a8..85f53b6 100644 --- a/HOW-TO-USE.md +++ b/HOW-TO-USE.md @@ -537,10 +537,11 @@ Things worth knowing: at `resolvconf: command not found` and deletes the interface again. This happens before any routing, so it takes **split tunnels down too**. Set the resolver another way instead, or drive `wg` and `ip route` directly rather than going through `wg-quick`. -- **`wg-quick` full tunnels also need `xt_CONNMARK` from the host kernel.** Native Linux, Docker - Desktop for Mac and WSL2 kernels from 6.6 have it; older WSL2 kernels do not, and a container - cannot load one. There the answer is again to add the routes yourself with `ip route`, which - needs no firewall backend on any platform. +- **`wg-quick` full tunnels additionally need `xt_CONNMARK` from the host kernel.** WSL2 kernels + before 6.6 do not have it and a container cannot load one — on Windows, `wsl --update` moves you + to a current kernel, which does. Failing that, add the routes yourself with `ip route`, which + needs no firewall backend on any platform. Note this is the *second* hurdle: clear the `DNS =` + one above first, or you will not reach this. > This setting can only be changed when the container is stopped. Capabilities and devices are > fixed when a container is created, so toggling it recreates the container on the next start. diff --git a/app/src-tauri/src/docker/migration.rs b/app/src-tauri/src/docker/migration.rs index 1b0b580..a08776a 100644 --- a/app/src-tauri/src/docker/migration.rs +++ b/app/src-tauri/src/docker/migration.rs @@ -135,7 +135,7 @@ pub const FEATURE_PROBES: &[(&str, &str)] = &[ ("/usr/local/bin/triple-c-task-runner", "Scheduled task runner"), ("/usr/local/bin/triple-c-sso-refresh", "AWS SSO auto-refresh"), ("/opt/mission-control", "Mission Control (Flight Control)"), - ("/usr/bin/wg", "VPN tooling (WireGuard, for the VPN Support toggle)"), + ("/usr/bin/wg", "VPN tooling for the VPN Support toggle (WireGuard)"), ]; /// Headroom demanded on Docker's storage backend on top of the measured diff --git a/container/Dockerfile b/container/Dockerfile index 86aea02..b94b76c 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -87,18 +87,25 @@ RUN for i in 1 2 3 4 5; do \ # Error: Could not process rule: No such file or directory # ^^^^^^^^^^^^^^ needs nft_fib_ipv4 # -# That matters because of how the two hosts we ship to are configured. From -# LinuxKit's kernel config — Docker Desktop for Mac, identical on both arches: +# The choice therefore turns on which kernel symbol each path needs, and the two +# are not equally safe to bet on. `xt_CONNMARK` (iptables) was present in every +# kernel config examined — LinuxKit's for both arches, and WSL2's from 6.6. +# `nft_fib_ipv4` (nftables) was absent from the LinuxKit config read here, and a +# later review argued Docker Desktop has since enabled it and no longer builds +# from that config at all. That may well be true; it could not be settled from a +# Linux host, and it is the point: nftables' viability varies by Docker Desktop +# version in a way nobody here can pin down, while iptables' requirement did not +# vary anywhere it was checked. # -# CONFIG_NETFILTER_XT_CONNMARK=y <- the iptables path works -# # CONFIG_NFT_FIB_IPV4 is not set <- the nft path does not +# So `iptables` is chosen for being robust to that uncertainty rather than for +# beating nftables on any particular host. If nft_fib_ipv4 is present, wg-quick +# never reaches the iptables path and this costs 1.6 MB and nothing else; if it +# is absent, this is the difference between a working full tunnel and none. # -# So shipping `nftables` would forfeit the platform it was meant to fix. With -# `iptables`, full tunnels work on native Linux, on Docker Desktop for Mac, and -# on WSL2 kernels from 6.6 (which added xt_CONNMARK as a module). Only WSL2 -# older than that is left out, and nothing installable here changes it — the way -# out there is to add the routes with `ip route` instead of using `wg-quick`, -# which is what the pia-vpn skill does on every platform. +# The residual gap is WSL2 before 6.6, which has neither symbol. Nothing +# installable in the container changes that — but `wsl --update` does, and moves +# the host to a far newer kernel. Add the routes with `ip route` in the meantime; +# that needs no firewall backend on any platform. # # ## What this still does not fix #