beefcake: NAT tailscale0 so exit-node traffic gets masqueraded #540

Merged
lytedev merged 3 commits from beefcake-exit-node-nat into main 2026-05-23 20:30:11 -05:00
Owner

Summary

With beefcake serving as a Tailscale exit node, clients' traffic was egressing eno1 with the tailnet source IP unchanged (e.g. 100.64.0.9 from the Zenfone), so internet replies had no return path — conntrack stayed [UNREPLIED] on every flow and pages like lobste.rs would just hang from the phone.

Root cause: beefcake runs tailscaled with NetfilterMode=0 (deliberate — keeps it from stomping on the k3s / flannel / netavark rules also installed on this host), which means Tailscale doesn't install its own MASQUERADE rule for exit-node traffic. The existing networking.nat config only marks ve-+ (nixos-containers) traffic for masquerading, not tailscale0.

Fix: add tailscale0 to networking.nat.internalInterfaces. NixOS's existing nixos-nat-pre chain then marks tailscale-arriving packets with 0x1, and the nixos-nat-post MASQUERADE ... out eno1 mark match 0x1 rule rewrites the source to beefcake's LAN IP.

Test plan

  • Diagnosis confirmed: conntrack on beefcake showed SYN_SENT src=100.64.0.9 dst=151.101.65.140 [UNREPLIED] for every flow from the phone.
  • Ephemeral test of the same rule (added live via iptables -t nat -A nixos-nat-pre -i tailscale0 -j MARK --set-xmark 0x1) — lobste.rs and other sites load immediately from the phone with beefcake as exit node.
  • Deploy beefcake (nixos-rebuild switch) so the rule persists across reboots.
  • Re-verify after deploy: iptables -t nat -L nixos-nat-pre -v -n shows the tailscale0 -> MARK 0x1 rule.

Security posture

  • No new opened ports.
  • No change to tailscale ACL — only tag:admindevice nodes (which already have *:* covering autogroup:internet) can actually use the exit node; group:family/group:friends lack any rule permitting autogroup:internet:* so they couldn't route through it even if they tried.
  • The masquerade only fires on outbound eno1 traffic that was originally marked from tailscale0 — doesn't affect any inbound or LAN-internal flows.
## Summary With beefcake serving as a Tailscale exit node, clients' traffic was egressing `eno1` with the **tailnet source IP unchanged** (e.g. `100.64.0.9` from the Zenfone), so internet replies had no return path — conntrack stayed `[UNREPLIED]` on every flow and pages like lobste.rs would just hang from the phone. Root cause: beefcake runs `tailscaled` with `NetfilterMode=0` (deliberate — keeps it from stomping on the k3s / flannel / netavark rules also installed on this host), which means Tailscale doesn't install its own `MASQUERADE` rule for exit-node traffic. The existing `networking.nat` config only marks `ve-+` (nixos-containers) traffic for masquerading, not `tailscale0`. Fix: add `tailscale0` to `networking.nat.internalInterfaces`. NixOS's existing `nixos-nat-pre` chain then marks tailscale-arriving packets with `0x1`, and the `nixos-nat-post` `MASQUERADE ... out eno1 mark match 0x1` rule rewrites the source to beefcake's LAN IP. ## Test plan - [x] **Diagnosis confirmed**: conntrack on beefcake showed `SYN_SENT src=100.64.0.9 dst=151.101.65.140 [UNREPLIED]` for every flow from the phone. - [x] **Ephemeral test of the same rule** (added live via `iptables -t nat -A nixos-nat-pre -i tailscale0 -j MARK --set-xmark 0x1`) — lobste.rs and other sites load immediately from the phone with beefcake as exit node. - [ ] Deploy beefcake (`nixos-rebuild switch`) so the rule persists across reboots. - [ ] Re-verify after deploy: `iptables -t nat -L nixos-nat-pre -v -n` shows the `tailscale0 -> MARK 0x1` rule. ## Security posture - No new opened ports. - No change to tailscale ACL — only `tag:admindevice` nodes (which already have `*:*` covering `autogroup:internet`) can actually use the exit node; `group:family`/`group:friends` lack any rule permitting `autogroup:internet:*` so they couldn't route through it even if they tried. - The masquerade only fires on outbound `eno1` traffic that was originally marked from `tailscale0` — doesn't affect any inbound or LAN-internal flows.
beefcake: NAT tailscale0 so exit-node traffic gets masqueraded
Some checks failed
/ check-format (push) Failing after 8s
/ build (push) Has been cancelled
54df63aeb8
NetfilterMode=0 on beefcake's tailscaled (deliberate, to avoid
stomping on k3s/flannel/netavark rules), so tailscale doesn't install
its own MASQUERADE rule for exit-node traffic. Without this, clients
using beefcake as exit node had their packets egress eno1 with the
source IP unchanged (e.g. 100.64.0.9), so internet replies had no
return path and conntrack stayed [UNREPLIED].

Adding tailscale0 to networking.nat.internalInterfaces makes the
existing nixos-nat-pre/post mark+MASQUERADE chain cover it, alongside
the ve-+ containers it already handles.
beefcake: nixfmt internalInterfaces list
All checks were successful
/ check-format (push) Successful in 8s
/ build (push) Successful in 5m42s
10fce701c7
lytedev force-pushed beefcake-exit-node-nat from 10fce701c7
All checks were successful
/ check-format (push) Successful in 8s
/ build (push) Successful in 5m42s
to a3d55b3093
All checks were successful
/ check-format (push) Successful in 9s
/ build (push) Successful in 5m49s
2026-05-23 20:10:10 -05:00
Compare
lytedev changed target branch from main to push-rpwpxukqzqqn 2026-05-23 20:10:59 -05:00
lytedev changed target branch from push-rpwpxukqzqqn to main 2026-05-23 20:22:11 -05:00
chore: prefer details in commit messages, not PR descriptions
All checks were successful
/ check-format (push) Successful in 7s
/ build (push) Successful in 7s
d8e2cb1009
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lytedev/nix!540
No description provided.