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.
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_ipwrites the packet to the tunnel and returns beforehandle_tcpruns, 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,curis alwaysNULL, so the block never executes:parse_tls_headeris never called,packetdatakeeps 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 = 1shortcut whileis_playis set, so it falls through toif (!defer_sni && (cur == NULL || cur->tcp.checkedHostname == 0))—ip.c:454.curisNULL, so the condition holds for every packet, andis_address_allowed— a JNI upcall intoServiceSinkhole, taking a read lock and building aPacketobject — runs for each one. Thecur->tcp.checkedHostname = 1guard that normally makes this once-per-session is inside anif (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 onmaster; not introduced by #730.No crash risk — the
cur != NULLguard 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_iphands the packet to WireGuard before the DNS and session machinery runs, so everything that machinery provides disappears whenever the tunnel is on.