macOS: tailcat's traffic goes through a co-located Tailscale exit node because netns is off
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 18/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 停滞
- 技術スタック
- go, macos
- 領域
- networking
調査の方向性
The netns setting is turned off in createEngine and PickBestRegion, and the issue argues it should stay on for macOS only. Start by reading how netns is set in those two functions, then check the open pull request #166 that already targets this change. Done means the macOS path binds to the physical interface and go test ./... still passes, which the issue reports on main.
索引モデルが issue の本文から書いたものです。
説明
What happens
On a Mac that runs the Tailscale app with an exit node selected, tailcat sends its own WireGuard UDP and its DERP connections into the app's tunnel. tailcat turns netns off in createEngine and PickBestRegion, so its sockets follow the route table, and with an exit node the route table sends everything to the Tailscale interface. The path that magicsock reports as direct then runs through the exit node. In our setup the server Mac is also the client's exit node, so that path leaves the server Mac and comes back to it through its router's hairpin NAT.
We use tailcat through Happy Agent, which keeps one long-lived tailcat --key=new socks --listen=127.0.0.1:0 <addr> per remote machine and sends HTTP requests through it. On that tunnel-in-tunnel path, small responses kept arriving while large ones stalled for minutes to hours at a time, on most days since 2026-11-10. Disco pings kept succeeding, so magicsock did not fall back to DERP.
Environment
- Client: macOS 27.2 (arm64), Tailscale app 1.104.5 (standalone), exit node selected, LAN access allowed.
- Server: macOS 27.2 behind a NAT router, running
tailcat serve exec. The same Mac is the client's exit node. - tailcat v0.7.0 as bundled by Happy Agent, and builds of main (e00cdde6f) for the comparison below.
During a stall (v0.7.0, 2026-11-13)
- On the client,
route -n get <server public IP>printsinterface: utun5, the Tailscale interface, and the verbose log showsnow using <server public IP>:<port> ... via=direct. - Through the long-running client, a 771-byte response arrived in 0.07 s and a 44 KB response produced no byte within 30 s (twice). A fresh
--key=newclient behaved the same. - With the exit node cleared, the 44 KB response took 0.06–0.11 s. With
TS_DEBUG_ALWAYS_USE_DERP=1and the exit node still selected, 44–49 KB took 0.3–0.8 s.
Outside a stall the same path carries megabytes, and we did not find which hop drops the large packets. tailcat's full-size packets (1340 bytes over IPv4) are larger than the Tailscale tunnel's 1280-byte MTU, while disco pings of about 100 bytes fit, so a drop that depends on packet size never makes magicsock leave the path.
With netns left on
Same client with the exit node selected, a throwaway tailcat serve on the server Mac, and a throwaway tailcat socks client that downloads 3 MB three times (2026-11-13):
| main (e00cdde6f) | netns left on for macOS | |
|---|---|---|
Interface of the client's UDP socket (nettop) |
utun5 | en0 |
| Local address of the client's DERP connection | its Tailscale IP | its Wi-Fi address |
Client's public address from STUN (v4a) |
the exit node's public IP | its own public IP |
| Path to the server | DERP | direct, and the server sees the client's own public IP |
3 MB through socks |
3.6–8.0 s | 0.5–1.8 s |
netns finds en0 here because netmon's DefaultRouteInterfaceIndex takes the Tailscale app's IPv6 default route (default fd7a:115c:a1e0:: UGcg utun5) and returns the tunnel's delegated interface. A VPN whose only unscoped default route has no gateway leaves netmon without a default interface, so netns would not bind there. tailscale/tailscale#22260 covers that case, and a build with it applied behaved like the second column.
Proposed fix
Leave netns on for macOS. On macOS a socket bound with IP_BOUND_IF still reaches loopback, so the loopback DERP map problem described in PickBestRegion does not apply there: go test ./... passes on macOS, and the root package takes the same time as on main. Other platforms keep netns off. A PR follows.
Workaround
Set TS_DEBUG_ALWAYS_USE_DERP=1 on the client, or clear the exit node.
- 主要言語
- Go
- スター
- 8.8k
- フォーク
- 404
- 平均マージ
- 3時間 48分
- マージ済み PR(30日)
- 1
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
tailscale/tailcat のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 69/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
難易度 3/5 半日 初心者へのやさしさ 30/100
-
難易度 4/5 3〜5日 初心者へのやさしさ 42/100
-
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
tailscale/tailcat の issue をすべて見る
似ている issue
-
area: interop area: sip-core v3
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
fonoster/routr#705 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
deadfin-org/deadfin#35 ·
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
-
enhancement priority: low
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
labmonkeys-space/nl6#1037 ·
メンテナーはふだん 1 日以内に返信
-
Setup Guide Go tab: say it needs Go 1.26 (the unversioned go get pulls OpenTelemetry Go v1.47.0)オープンenhancement follow-up
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
メンテナーはふだん 1 日以内に返信