Skip to content

Skip SNI research mode for tunnelled flows - #744

Merged
kasnder merged 2 commits into
masterfrom
claude/trackercontrol-open-issues-r8fn0a
Aug 20, 2026
Merged

Skip SNI research mode for tunnelled flows#744
kasnder merged 2 commits into
masterfrom
claude/trackercontrol-open-issues-r8fn0a

Conversation

@kasnder

@kasnder kasnder commented Aug 20, 2026

Copy link
Copy Markdown
Member

Fixes #735 by making research mode cheap and honest under the tunnel, rather than building SNI-under-tunnel.

The problem

SNI research mode reassembles a ClientHello on the ng_session that handle_tcp creates for a 443 flow. A flow routed through WireGuard never gets one — handle_ip writes the packet to the WG bridge and returns before handle_tcp runs. So on master, with sni_enabled and WireGuard both on:

  • the reassembly guard cur != NULL && cur->tcp.checkedHostname == 0 never holds, parse_tls_header is never called, and research mode collects nothing;
  • every later segment falls into if (!defer_sni && (cur == NULL || ...)) and pays a create_packet + is_address_allowed JNI upcall and a UID lookup — per packet, forever, because the checkedHostname = 1 shortcut sits inside the cur != NULL branch those flows never reach.

The change

ip.c resolves the routing verdict before the 443 decision sites. If the flow tunnels, sni_active is cleared and the packet takes the ordinary non-research path: decide once on the SYN by IP, allowed = 1 afterwards — exactly how WireGuard behaves without research mode. That removes the per-packet upcall and stops research mode pretending to work.

  • The tunnel_uid resolution (flow cache → session table → authoritative lookup, with the fail-closed fallback) is factored into resolve_tunnel_uid() and shared with the routing fork, which reuses the early answer instead of resolving and re-storing it a second time.
  • The UID gate keeps its existing shape; a flow that just lost research mode gets its SYN UID resolved so the block decision is still attributed, as it is under WireGuard without research mode.
  • Nothing changes when WireGuard is off, or for flows that per-app routing keeps direct — those still create sessions and still collect SNI.

ActivitySettings appends a one-line note to the blocking-mode summary when sni_enabled and a configured wg_enabled are both on, so the UI stops implying extraction still applies to tunnelled traffic. One new English string; no new preference or toggle.

Not done here

Making SNI work through the tunnel needs per-flow checkedHostname + reassembly state keyed like route_flow_store, with buffer lifetime on a cache rather than a session — and a blocked verdict there still could not write_rst. That is a feature, not this bug; worth a separate issue if it is wanted.

Verification

Not built. This container has no Android SDK (android-env exits 3), so :app:compileGithubDebugJavaWithJavac, :app:assembleGithubDebug (the one that actually compiles ip.c) and :app:lintGithubDebug were not run. Draft until someone runs them — the C change is unverified by a compiler. Reviewed by hand against the surrounding code instead.


Generated by Claude Code

claude added 2 commits August 20, 2026 22:03
SNI research mode reassembles a ClientHello on the ng_session that
handle_tcp creates for a 443 flow. A flow routed through the WireGuard
tunnel never gets one: handle_ip writes the packet to the WG bridge and
returns before handle_tcp runs. So the reassembly guard always saw
cur == NULL and no SNI was ever collected, while every later segment
re-ran is_address_allowed() and a UID lookup, because the
once-per-session shortcut lives inside a cur != NULL branch those flows
never reach.

Resolve the routing verdict before the 443 decision sites and, when the
flow tunnels, take the ordinary non-research path for it: decide once on
the SYN by IP, then allowed = 1 — exactly how WireGuard behaves without
research mode. Nothing changes when WireGuard is off, or for flows that
per-app routing keeps direct, which still collect SNI as before.

The tunnel_uid resolution is factored into resolve_tunnel_uid() and
shared with the routing fork, which reuses the early answer rather than
resolving and re-storing it a second time.

Also stop the blocking-mode summary from implying research mode still
works: it now says SNI extraction does not apply to remotely routed
traffic whenever SNI and a configured remote VPN are both on.

Refs #735
- AGENTS.md's privacy-preservation principle claimed SNI parsing runs
  the same whether WireGuard is on or off, which this PR makes false.
  Update it to describe the actual behaviour (research mode collects
  nothing for tunnelled flows, surfaced in the Research summary).
- Move the SNI/WireGuard caveat from the blocking_mode summary to the
  Research (log_logcat) preference summary, since that is the control
  that actually toggles sni_enabled; blocking_mode's summary never
  mentioned SNI at all.
- resolve_tunnel_uid() now takes an optional out_uid parameter so the
  SNI-attribution fallback in handle_ip can reuse a UID the routing
  resolution already looked up, instead of paying for a third lookup
  (flow-cache miss -> session table -> procfs/Binder) on the same
  packet.
@kasnder
kasnder marked this pull request as ready for review August 20, 2026 22:35
@kasnder
kasnder merged commit 546e181 into master Aug 20, 2026
2 checks 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.

SNI research mode collects nothing while WireGuard is on, and costs a JNI upcall per packet

2 participants