diff --git a/hosts/idols-aquamarine/default.nix b/hosts/idols-aquamarine/default.nix index 260c1c3f..091fd954 100644 --- a/hosts/idols-aquamarine/default.nix +++ b/hosts/idols-aquamarine/default.nix @@ -74,24 +74,6 @@ in useDHCP = false; }; - # suzi's fake-ip DNS hands out AAAA records whose IPv6 path does not work: a - # `curl -6` to a registry returns 502 or times out, while `curl -4` answers in - # 0.1 s. Clients that try the AAAA first (podman, git, pip) then hang on the - # handshake, which is why docker.io, github.com and pypi.org looked blocked - # here. These are RFC 6724's default precedences with the IPv4-mapped row - # raised, so IPv4 sorts first; glibc drops its whole built-in table as soon as - # the file lists any entry, hence spelling all of it out. - # - # The real fix belongs on that gateway: stop answering AAAA for proxied names - # (`dns.ipv6: false` in mihomo; the module README says the same). - networking.getaddrinfo.precedence = { - "::1/128" = 50; - "::/0" = 40; - "2002::/16" = 30; - "::/96" = 20; - "::ffff:0:0/96" = 100; - }; - # A `boot.kernel.sysctl` that disabled IPv6 was removed on 2026-10-14: networkd # re-enables it on the wired link for the `DHCP = "ipv6"`/`IPv6AcceptRA` settings # below, so it never applied and the AAAA attempt already fails at once (WA-034). diff --git a/modules/nixos/desktop/networking/mihomo/README.md b/modules/nixos/desktop/networking/mihomo/README.md index 4bd9104b..78306f77 100644 --- a/modules/nixos/desktop/networking/mihomo/README.md +++ b/modules/nixos/desktop/networking/mihomo/README.md @@ -62,12 +62,19 @@ Verge UI. Verge logs `Unregistered Clash configuration key "ipv6"` for the pin - still reaches the core. The gateway (`suzi`, not a host in this flake) runs the same config with a fixed set of deltas -- -`gateway-config.nu` renders it from the same `sources.yaml` and `policy.yaml`. Its output carries -the subscriptions and the private domains, so never commit it; the secret is filled in on the box, -not here. Its binary is a static release build under `/usr/local/bin`, not a package: update it by -hand, validate the live config with the new core (`mihomo -t -d /etc/mihomo -f `) before -swapping, and keep the previous binary. The release ships no checksums, so TLS plus `-v` is the -whole verification. +`gateway-config.nu` renders it from the same `sources.yaml` and `policy.yaml`. The gateway uses +nftables TProxy, disables the desktop TUN auto-route, and uses `fake-ip` DNS with `dns.ipv6: true`: +LAN clients receive stable fake A/AAAA mappings so mihomo retains the domain for routing and +sniffing. Its output carries the subscriptions and the private domains, so never commit it; the +secret is filled in on the box, not here. Its binary is a static release build under `/usr/local/bin`, +not a package: update it by hand, validate the live config with the new core (`mihomo -t -d /etc/mihomo +-f `) before swapping, and keep the previous binary. The release ships no checksums, so TLS +plus `-v` is the whole verification. + +Gateway mode contract: the tested working combinations are nftables TProxy with both `tun.enable` +and `tun.auto-route` disabled, using either `redir-host` or `fake-ip` DNS. This generator selects +`fake-ip` because it preserves more domain information. Do not combine LAN TProxy with the desktop +TUN auto-route: in this gateway topology it can recapture fake IPv6 dials and cause timeouts. ## Why generate diff --git a/modules/nixos/desktop/networking/mihomo/gateway-config.nu b/modules/nixos/desktop/networking/mihomo/gateway-config.nu index f2a61b31..c5c56547 100644 --- a/modules/nixos/desktop/networking/mihomo/gateway-config.nu +++ b/modules/nixos/desktop/networking/mihomo/gateway-config.nu @@ -36,9 +36,14 @@ def main [ | insert external-ui "ui" | insert external-ui-url $UI_URL | update secret "GATEWAY-SECRET-PLACEHOLDER" - # the base leaves the stack at upstream's default; this box has run the kernel stack for months - | upsert tun.stack "mixed" + # The gateway uses nftables TProxy for LAN traffic; its desktop-style TUN + # auto-route would capture the gateway's own DIRECT IPv6 dials again. + | upsert tun.enable false + | upsert tun.auto-route false | update tun.strict-route false + # TProxy handles fake IPv4 and IPv6 destinations when the desktop TUN + # auto-route is disabled; fake-IP preserves the domain mapping for LAN flows. + | upsert dns.enhanced-mode "fake-ip" # the desktop's resolver is loopback; a gateway serves the LAN instead | update dns.listen ":1053" # LAN clients that use DoH dial the IPs they resolved; sniff so the node dials a name diff --git a/outputs/x86_64-linux/tests/idols-aquamarine/expected.nix b/outputs/x86_64-linux/tests/idols-aquamarine/expected.nix index 86397f33..ede51a0f 100644 --- a/outputs/x86_64-linux/tests/idols-aquamarine/expected.nix +++ b/outputs/x86_64-linux/tests/idols-aquamarine/expected.nix @@ -6,7 +6,7 @@ gpuRecoveryEnabled = true; aspmDisabled = true; cStatesCapped = true; - prefersIpv4 = true; + nativeIpv6Preference = true; sourceCheckoutsPersistent = true; rootlessPodmanEnabled = true; stableKernel = true; diff --git a/outputs/x86_64-linux/tests/idols-aquamarine/expr.nix b/outputs/x86_64-linux/tests/idols-aquamarine/expr.nix index aec1735d..ad0241f7 100644 --- a/outputs/x86_64-linux/tests/idols-aquamarine/expr.nix +++ b/outputs/x86_64-linux/tests/idols-aquamarine/expr.nix @@ -25,9 +25,11 @@ in aspmDisabled = builtins.elem "pcie_aspm=off" cfg.boot.kernelParams; cStatesCapped = builtins.elem "processor.max_cstate=2" cfg.boot.kernelParams; - # suzi's fake-ip DNS returns AAAA records with no working IPv6 path, so the - # generated gai.conf has to sort IPv4 first (see ./default.nix). - prefersIpv4 = lib.hasInfix "precedence ::ffff:0:0/96 100" cfg.environment.etc."gai.conf".text; + # Aqua uses native IPv6; the gateway returns real AAAA records instead of + # requiring a host-local IPv4 precedence workaround. + nativeIpv6Preference = + !(cfg.environment.etc ? "gai.conf") + || !lib.hasInfix "precedence ::ffff:0:0/96 100" cfg.environment.etc."gai.conf".text; # The Strata checkout lives under ~/src, which has to survive the tmpfs root # like ~/codes does.