- 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)
Add package-level architecture documentation describing two modes
(hop-based forwarding and direct proxy), two-tier dialing strategy
in forwardSerial, recorderConn per-packet logging with direction
convention, and metadata parsing details. No code changes.
- conn.go: Write() now writes first then records b[:n] (not len(b)),
matching the Read() pattern and preventing phantom data on failures.
- handler.go: add nil Router check before h.options.Router.Dial()
in Handle's non-hop path to return a clear error instead of panicking.
- handler.go: guard h.options.Router against nil in forwardSerial;
restructure if/else so ReadTimeout is applied in both the direct
OpenPort path and when the Router has no chain configured.