Skip to content

Add optional TCP MSS clamp for tethering PMTUD failures (#478) - #603

Merged
kasnder merged 2 commits into
masterfrom
claude/fix-478-pmtud-mss-clamp
Jul 11, 2026
Merged

Add optional TCP MSS clamp for tethering PMTUD failures (#478)#603
kasnder merged 2 commits into
masterfrom
claude/fix-478-pmtud-mss-clamp

Conversation

@kasnder

@kasnder kasnder commented Jul 11, 2026

Copy link
Copy Markdown
Member

Provides an opt-in workaround for the tethering stalls reported in #478.

Problem

TrackerControl's TUN uses an MTU of 10000, and the native TCP proxy can therefore use an MSS near 9960. When oversized packets are written back through the TUN toward a tethered client, the smaller downstream path may return ICMP "fragmentation needed" / "packet too big" messages. The native ICMP handler does not currently process those PMTUD messages.

A fixed global clamp would reduce TUN aggregation for every TCP connection, increasing native reads, allocations, checksums, and writes even for users who do not encounter the tethering problem.

Change

Adds an advanced, default-off Tethering compatibility mode setting.

When enabled:

  • the MSS stored for a new TCP session is capped at 1360 bytes;
  • injected SYN/SYN-ACK packets advertise an MSS no larger than 1360 bytes.

The preference is read once when the VPN starts and stored in the native context. Native code checks it only during TCP SYN/session setup; there is no preference lookup or additional branch for established data packets.

The existing MSS option write is also corrected to use network byte order with htons(). That correctness fix applies regardless of whether compatibility mode is enabled.

The global TUN MTU remains unchanged.

Tradeoffs

1360 is a conservative heuristic, not a complete PMTUD implementation. Compatibility mode may reduce throughput and increase CPU/battery use because it produces more native socket reads and TUN writes. Keeping it opt-in limits that cost to affected users and enables direct A/B testing.

Validation

  • :app:externalNativeBuildGithubDebug -x :app:wgbridgeBuild: BUILD SUCCESSFUL for arm64-v8a, armeabi-v7a, x86, and x86_64.
  • XML validation and git diff --check: passed.
  • Full :app:assembleGithubDebug reached and compiled the changed native code, but the repository currently fails resource linking on unrelated Ukrainian blocking-mode string references.

Required device testing

  1. Reproduce TrackerControl can get a corrupted state where some websites becomes unreachable on tethering #478 with a tethered client and confirm large TLS/download flows complete with compatibility mode enabled.
  2. Confirm the same reproduction still occurs with the setting disabled.
  3. Check normal phone traffic, IPv4/IPv6, and WireGuard on/off.
  4. Compare throughput and battery/CPU behavior before considering enabling the workaround by default.

kasnder and others added 2 commits July 11, 2026 02:26
The native tunnel advertises an oversized tun MTU (get_mtu() == 10000),
so the MSS derived for the SYN/SYN-ACK it injects is ~9960. Combined with
handle_icmp() dropping every non-echo ICMP - including ICMPv4 type 3 code 4
("fragmentation needed") and ICMPv6 type 2 ("packet too big") - a peer that
is not re-originated over a real kernel socket (notably a tethered client)
never learns to shrink its segments, so large flows black-hole while small
ones and the phone's own apps work.

Clamp the advertised/used MSS to a conservative TCP_MSS_CLAMP (1360 bytes)
so every segment on the tun path fits typical constrained upstream paths
(cellular / WireGuard) without relying on PMTUD ICMP:

- netguard.h: add TCP_MSS_CLAMP with rationale.
- tcp.c (handle_tcp): clamp the effective MSS stored in the session (used to
  size downstream segmentation into the tun) to min(mss, 1360), covering both
  the oversized-tun default and any peer-advertised value.
- tcp.c (write_tcp): clamp the MSS advertised in the injected SYN/SYN-ACK, and
  write it with htons(). The option value is read back with ntohs() in
  handle_tcp, so it must be network byte order; the previous host-order write
  was a latent byte-order bug that would also have defeated the clamp.

The global tun MTU (get_mtu/setMtu) is deliberately left unchanged. Passing
PMTUD ICMP through (translating type 3 code 4 / type 2 back into the tun) is a
possible follow-up but is out of scope here. MSS clamping is a heuristic; 1360
is chosen conservatively and needs on-device tethering validation.

Native lib build-checked: :app:externalNativeBuildGithubDebug BUILD SUCCESSFUL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kasnder kasnder changed the title Clamp injected TCP MSS to fix PMTUD black-hole over tethering (#478) Add optional TCP MSS clamp for tethering PMTUD failures (#478) Jul 11, 2026
@kasnder
kasnder marked this pull request as ready for review July 11, 2026 01:33
@kasnder
kasnder merged commit e9fa2b4 into master Jul 11, 2026
1 check passed
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.

1 participant