tailscale / tailscale/tailscale
iOS: prefer one CGNAT route when safe
- Dominant language
- Go
- Stars
- 36.5k
- Forks
- 3.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 123
Description
## What is the issue?
The iOS client does not automatically prefer one `100.64.0.0/10` route for Tailscale peers, even though macOS and Android now do so when no non-Tailscale interface uses CGNAT space.
On an iPhone and a Wi-Fi-only iPad in a tailnet with multiple peers, Mullvad exit nodes, and a `10.7.0.1/32` subnet route, the Network Extension exposed many per-peer `100.x.y.z/32` routes. Route updates and route-sensitive endpoint discovery were intermittently slow or unreliable. In the worst cases, ordinary connectivity through the Tailscale tunnel stalled until the VPN was toggled.
I applied the existing control-plane override to the affected iOS nodes:
```json
{"target":[""],"attr":["one-cgnat?v=true"]}
```
After reconnecting, the same stock iOS client installed one `100.64.0.0/10` route while preserving the independent `10.7.0.1/32` subnet route. Across repeated application and VPN reconnects:
- route-sensitive discovery completed substantially faster;
- the tunnel no longer became completely unavailable during the test;
- ordinary Internet connectivity no longer wedged after a failed application connection;
- a few application-level retries remained, attributable to that application's separate 100 ms TCP probe deadline.
This suggests iOS benefits from the same automatic OneCGNAT behavior introduced for Android in #19652. The existing safety check should remain unchanged: if `netmon.Monitor.HasCGNATInterface` finds a non-Tailscale interface using RFC 6598 space, the client should retain fine-grained peer routes. An explicit `OneCGNAT` control knob should continue to take precedence.
Expected behavior: iOS automatically prefers the single CGNAT route when the existing safety check permits it, matching macOS and Android.
## Steps to reproduce
1. Connect an iOS or iPadOS device to a tailnet with enough peers or Mullvad nodes to produce many peer routes. The test tailnet also advertises an unrelated `10.7.0.1/32` subnet route.
2. Without a `one-cgnat` node attribute, inspect the Network Extension routes and observe multiple Tailscale peer `/32` routes.
3. Exercise a latency-sensitive TCP connection through the subnet route while reconnecting the client or changing the peer set.
4. Apply `one-cgnat?v=true` to the iOS node and reconnect Tailscale.
5. Observe that the peer routes are coalesced to `100.64.0.0/10`, `10.7.0.1/32` remains present, and repeated connection attempts are substantially more stable.
## Are there any recent changes that introduced the issue?
No known regression. Android gained automatic OneCGNAT selection in #19652, while iOS remains excluded from the automatic platform selection in `shouldUseOneCGNATRoute`.
## OS
iOS, iPadOS
## OS version
iOS/iPadOS 27.0 (24A5430a)
## Tailscale version
Stock iOS client. The behavior was controlled through the tailnet `one-cgnat?v=true` node attribute without modifying the app.
## Other software
The route and TCP behavior was exercised with SideStore/minimuxer over a Tailscale subnet route. SideStore is only the route-sensitive reproducer; the proposed behavior is in Tailscale's platform route selection.
## Bug report
`BUG-81f563556f63f27f689049d7cb2c14a60b8d229100a8150878ce86ef4423123d-20260903110558Z-9209458106207042`
Contributor guide
Research direction
Start at the shouldUseOneCGNATRoute entry point and inspect how netmon.Monitor.HasCGNATInterface and the explicit OneCGNAT control knob affect platform selection. Compare the automatic Android behavior introduced in #19652, then exercise the iOS route scenarios described in the issue. Done means iOS selects one 100.64.0.0/10 route when safe, preserves unrelated subnet routes, and retains fine-grained routes when the safety check fails.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, ios
- Domain
- mobile-dev, networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100