Skip to content

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

Description

@kasnder

Summary

SNI research mode (sni_enabled) parses no SNI at all while remote VPN (WireGuard) egress is on, and pays a JNI upcall on every single packet of every 443 flow for the privilege.

Root cause

Both symptoms come from the same fact: a WireGuard-tunnelled flow never creates an ng_session. handle_ip writes the packet to the tunnel and returns before handle_tcp runs, so the session table stays empty for those flows.

No SNI is collected. The reassembly block is guarded by if (cur != NULL && cur->tcp.checkedHostname == 0)ip.c:393. With WireGuard on, cur is always NULL, so the block never executes: parse_tls_header is never called, packetdata keeps the flags string, and the hostname is never seen. The opt-in research mode quietly produces nothing.

And it costs an upcall per packet. A non-SYN 443 segment does not take the allowed = 1 shortcut while is_play is set, so it falls through to if (!defer_sni && (cur == NULL || cur->tcp.checkedHostname == 0))ip.c:454. cur is NULL, so the condition holds for every packet, and is_address_allowed — a JNI upcall into ServiceSinkhole, taking a read lock and building a Packet object — runs for each one. The cur->tcp.checkedHostname = 1 guard that normally makes this once-per-session is inside an if (cur != NULL), so it never engages.

Without WireGuard the session exists, the flag gets set, and the decision is made once per session as intended. The guard is doing its job everywhere except the path where it is needed most.

Scope

Opt-in (sni_enabled) and WireGuard enabled. Present on master; not introduced by #730.

No crash risk — the cur != NULL guard on the reassembly block holds. The cost is a wasted upcall per packet and a research mode that reports nothing.

Direction

Depends on how the shared root cause is resolved. If tunnelled flows gain lightweight session state, both symptoms fall out for free. Short of that, the per-packet upcall can at least be bounded by remembering the decision per flow rather than per session — #730 adds exactly such a per-flow cache for the routing verdict (policy.c, route_flow_lookup), and the same shape would work here.

Root cause is shared with the two sibling issues linked below: handle_ip hands the packet to WireGuard before the DNS and session machinery runs, so everything that machinery provides disappears whenever the tunnel is on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions