The read deadline set for ClientHello sniffing was reset via defer, so it
remained on the client connection until Handle returned, i.e. for the whole
session. xnet.Pipe is called without a read timeout for TLS/HTTP, so nothing
refreshed it and any connection was torn down after readTimeout (15s default)
regardless of traffic. Clear the deadline immediately after sniffing, matching
the relay/router/tunnel/socks4 handlers.
Regression test asserts the deadline is zero when the upstream dial fires.
Fixes#904
The cache-hit path (serveCachedResponse) recorded on the same HandlerRecorderObject
as the calling handler's deferred Record(), producing two records per request.
Copy ro at HandleHTTP entry so the sniffer's internal recording stays isolated
from the caller's defer. Also zero ro.Time when the sniffer takes over, so the
handler's defer Record() skips the now-redundant connection-level record.
Fixes 10 files across sniffer and all 8 handlers that support sniffed HTTP forwarding.
Implements the cache.Cache interface with in-memory backend (TTL,
maxSize/maxBytes bounds, oldest/LRU eviction, background cleanup).
Adds httpcache layer for HTTP response policy (per-status TTL,
serve-stale, method/body filtering). Wires into config parser,
loader, registry, and all four sniffing handlers (forward/local,
forward/remote, sni, http) so HTTP responses are cached before
dialing upstream.
Bumps core dependency to v0.6.0 for the new cache.Cache interface.
- Extract newRecorderObject, checkRateLimit, sniffingDial, and
handleSniffedProtocol from the Handle method into dedicated files
(sniffing.go, util.go) to reduce Handle from ~100 to ~65 lines.
- Introduce SnifferBuilder: populated once during Init, reused via
Build() per connection. Avoids reconstructing a 9-field sniffing.Sniffer
literal on every inbound connection.
- Hoist router nil check from the lazy dial closure into Handle so
misconfigured handlers fail immediately instead of silently dropping
unrecognised traffic with no error.
- Add nil-addr guard in checkRateLimit (missing from the old inline
version — would panic if the remote address were somehow nil).
- Add comprehensive unit tests (44 tests across 5 test files) covering
handler creation, Init, metadata parsing (defaults, custom, MITM,
error paths), protocol dispatch (HTTP, TLS, unknown, empty), rate
limiting, recorder objects, dial plumbing, and SnifferBuilder.
- Add package-level godoc explaining the handler scope, limitations
vs tcp/forward, and the connection processing flow.
- Fix metadata comment: s/SnifferBuilder/sniffing.Sniffer/.
- serial/handler.go: capture xnet.Pipe return value in Handle() and forwardSerial()
- serial/handler.go: log Warnf when chain dial returns (nil,nil) before falling back
- sni/handler.go: use defer for read deadline reset to prevent stale deadline
- sni/handler.go: unrecognized protocol returns nil (clean close) instead of error
- sni/metadata.go: update readTimeout comment to reflect dual usage (deadline + SnifferBuilder)
- Set read deadline before/after sniffing.Sniff to prevent goroutine
leak from slow clients (matches redirect/tcp and ss patterns)
- Log sniff errors at Debug level instead of silently discarding them
- Add nil Router check in dial closure to prevent NPE panic