Compare commits

...
Author SHA1 Message Date
beatzaplentyandClaude Sonnet 4.6 e9b225d2d6 fix(tailscale-router): add MASQUERADE to POSTROUTING, not nixos-nat-post
Check NixOS configurations / eval-hosts (pull_request) Successful in 10m31s
extraCommands runs after nixos-nat-post is deleted but before it is
re-created, so -A nixos-nat-post silently fails every time.  POSTROUTING
is a built-in chain that always exists; target it directly instead.

The -C idempotency check prevents duplicate rules on firewall reloads.
Drop networking.nat.enable -- it was only needed for the sub-chain that
turned out to be the wrong target.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-26 10:00:47 +10:00
+21 -23
View File
@@ -16,29 +16,27 @@
# Must also be approved in the Tailscale admin console (Machines → Edit route settings).
services.tailscale.extraUpFlags = [ "--advertise-routes=${vars.lanCidr}" ];
networking = {
# SNAT traffic from LAN machines going out through Tailscale so the remote
# peer sees it sourced from this router's Tailscale IP (100.x.x.x) rather
# than a raw LAN IP. Without this, Tailscale drops the forwarded packets
# because the source is not a recognised Tailscale address.
#
# networking.nat.externalInterface alone does not insert a MASQUERADE rule
# (it only does so when internalInterfaces is also set). We use
# extraCommands to add the rule into the nixos-nat-post chain that
# networking.nat.enable creates, and extraStopCommands to clean it up.
nat.enable = true;
networking.firewall = {
# Forwarded subnet-router traffic arrives on tailscale0 already
# tailscale-authenticated -- the firewall's normal per-port allow-list
# would otherwise drop it. Standard NixOS/Tailscale subnet-router guidance.
trustedInterfaces = [ "tailscale0" ];
firewall = {
# Forwarded subnet-router traffic arrives on tailscale0 already
# tailscale-authenticated -- the firewall's normal per-port allow-list
# would otherwise drop it. Standard NixOS/Tailscale subnet-router guidance.
trustedInterfaces = [ "tailscale0" ];
extraCommands = ''
iptables -t nat -A nixos-nat-post -s ${vars.lanCidr} -o tailscale0 -j MASQUERADE
'';
extraStopCommands = ''
iptables -t nat -D nixos-nat-post -s ${vars.lanCidr} -o tailscale0 -j MASQUERADE 2>/dev/null || true
'';
};
# SNAT LAN traffic going into Tailscale so the remote peer sees it as
# coming from this router's Tailscale IP rather than a raw LAN IP.
# Without this, Tailscale drops forwarded packets whose source is not a
# recognised Tailscale address.
#
# We target POSTROUTING directly (always-existing built-in chain) rather
# than nixos-nat-post: extraCommands runs after the old nixos-nat-post is
# deleted but before the new one is created, so -A nixos-nat-post silently
# fails. The -C check makes the rule idempotent across firewall reloads.
extraCommands = ''
iptables -t nat -C POSTROUTING -s ${vars.lanCidr} -o tailscale0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s ${vars.lanCidr} -o tailscale0 -j MASQUERADE
'';
extraStopCommands = ''
iptables -t nat -D POSTROUTING -s ${vars.lanCidr} -o tailscale0 -j MASQUERADE 2>/dev/null || true
'';
};
}