Skip to content

tailcat: keep netns on for macOS so a co-located VPN is bypassed - #166

Open
chphch wants to merge 1 commit into
tailscale:mainfrom
chphch:fix/netns-exit-node
Open

chphch wants to merge 1 commit into
tailscale:mainfrom
chphch:fix/netns-exit-node

Conversation

@chphch

@chphch chphch commented Nov 13, 2026

Copy link
Copy Markdown

Fixes #165

tailcat turned netns off on every platform, so its sockets followed the system route table. On a Mac running the Tailscale app with an exit node selected, that table sends tailcat's own WireGuard UDP and DERP connections into the app's tunnel, and large responses over the resulting tunnel-in-tunnel path stalled for minutes to hours. #165 has the details and measurements.

This leaves netns on for macOS, where it binds sockets to the default physical interface as tailscaled does. Loopback UDP still works from sockets bound that way on macOS, so the loopback DERP map concern that PickBestRegion documented applies to Linux only. Other platforms keep netns off.

Testing:

  • New TestNetNSPolicy starts a Server and checks the netns policy. On macOS it fails on main (netns binds sockets = false; want true) and passes with this change.
  • go test ./... passes on macOS 27.2 (arm64) and Ubuntu 22.04 (amd64). On macOS the root package takes the same time as on main, about 4.6 s.
  • go vet for linux, darwin and windows; release builds for windows/amd64 and darwin/arm64; a GOOS=js build of the root package.
  • On a MacBook with the Tailscale app's exit node selected, a tailcat socks client built from main kept its UDP socket and DERP connection on utun5 and stayed on DERP. Built from this branch, it put them on en0 and got a direct path, and the server saw the client's own public IP. 3 MB downloads took 3.6–8.0 s with main and 0.5–1.8 s with this branch.

🤖 Generated with Claude Code

tailcat turned netns off on every platform, so its sockets followed
the system route table. On a Mac running the Tailscale app with an
exit node selected, that table sends tailcat's own WireGuard UDP and
DERP connections into the app's tunnel, and tailcat's direct path then
runs inside it.

On macOS netns binds sockets to the default physical interface, as
tailscaled does, and loopback UDP still works from sockets bound that
way, so leave it on there. Other platforms keep it off, as
configureNetNS explains.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Fixes tailscale#165

Signed-off-by: Hyunwoo Jung <chaosphch@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

This branch has not been deployed

No deployments
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.

macOS: tailcat's traffic goes through a co-located Tailscale exit node because netns is off

1 participant