tailscale / tailscale/tailscale

iOS: prefer one CGNAT route when safe

Open
#21,125 0 comments 0 reactions 0 assignees View on GitHub
fr OS-ios
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.