Translate all Chinese comments in the relay handler package to English:
handler.go, connect.go, bind.go, forward.go, conn.go, entrypoint.go,
observe.go, metadata.go. All inline and doc comments are now in English.
Also set a persistent preference: all future code comments must be
written in English only.
- api: add fillServiceStatus helper and call it from getServiceList/getService
so status field appears in service list and detail API responses
- observeStats: unify retry pattern across all 9 handlers (http, http2, masque,
relay, router, socks4, socks5, tungo, tunnel) — buffer events on failure,
continue to skip fresh collection, clear on success; fix event-loss bug
where interim events were dropped during retry cycles
- handler/router: check WriteTo/Write return errors in associate.go and
entrypoint.go; fix DelConnector and ConnectorPool.Del using RLock instead
of Lock (write under read lock); remove unused fields t and cancel
Allow browser-based dashboards (e.g. Flutter web apps) to access the
metrics endpoint by setting Access-Control-Allow-Origin: * and handling
OPTIONS preflight requests.
The response address sent back to the tunnel client was unconditionally
set to the md5 hash of the tunnel ID. This meant the user's custom host
(e.g. "dash" from "dash:8081") was ignored — the client listened on
the hash instead.
Now the user-supplied host is used directly when:
1. A host is present in the bind address AND ingress is configured.
2. No other tunnel has already claimed that host in ingress
(conflict check via GetRule).
Fall back to the md5 hash when no host is supplied, ingress is nil, or
the host conflicts with an existing ingress rule.
Also fixes indentation in the WriteTo error handling block (extra tab
removed).
Tests:
- TestHandleBind_CustomHost: response AddrFeature uses "dash", not hash
- TestHandleBind_CustomHostConflict: conflicting host falls back to hash
- fakeIngress enhanced with ruleByHost map for conflict simulation
Move SetDefaultTLSConfig before register(cfg) in loader.go so
ParseService can access it when building handlers. Also add a
defensive nil check in socks5 serverSelector.Select() for
MethodTLS to prevent future ordering regressions.
Root cause: in selector.go, Select() tried to read ClientHello
via TLSConfig.GetConfigForClient but s.TLSConfig was nil because
register(cfg) -> ParseService -> handler Init ran before
SetDefaultTLSConfig was called from loader.go.
- Fix dead-code branch in bind.go host assignment (always use endpoint hash)
- Return descriptive error on bypass match in connect.go (was masking as success)
- Update bypass test in connect_test.go for new error behavior
- Extract entrypoint subpackage from monolithic entrypoint.go (6 files)
- Fix observeStats event-loss bug (break -> fallthrough on retry success)
- Add 47 unit tests across handler, connect, bind, metadata packages
- Add architecture doc comments to all key files
- Build and vet clean, 173 tests pass with -race
Extract checkRateLimit and observeStats into their own file (observe.go).
Fix the one-tick event-loss bug in observeStats where the retry path used
'unconditional break' instead of fallthrough, causing events collected
after a successful retry to be discarded.
Add 87 unit tests across 7 files (helpers, metadata, conn, handler,
connect, forward, bind) covering all handler modes, error paths,
observer/stats integration, and conn wrapper edge cases.
handler.go: -43 lines (unused observer import, extracted functions)
observe.go: +52 lines (checkRateLimit, observeStats with fallthrough fix)
test files: +1959 lines
- handler/http/websocket, forwarder/sniffer_ws, sniffing/sniffer_ws: nil
ro2.HTTP in WebSocket copy goroutines to avoid data races on the shared
HTTP recorder object
- handler/tunnel/bind: remove ingress rule check that incorrectly
overrode the endpoint host for non-matching ingress rules
- handler/tunnel/entrypoint: add forwarding loop detection by checking
Gost-Forwarded-Node header for the entrypoint's own node ID
Add PUT method support to the file:// handler, allowing file uploads via
curl -T. Upload is opt-in, disabled by default — enable with ?put=true
metadata flag. Includes path traversal protection.
Adds RejectUnknownSNI and ServerNames fields to TLSConfig. When enabled,
TLS handshakes with missing or non-allowlisted SNI are rejected at the
handshake level via GetConfigForClient before any certificate is sent.
Works across all TLS-based listener types (tls, mtls, ws, mws, http2,
grpc, http3) since they share the same *tls.Config from LoadServerConfig.
Priority short-circuit now checks for backup nodes before bypassing the
selector. When any node has backup:true the selector (FailFilter,
BackupFilter, strategy) runs instead, ensuring BackupFilter can separate
primary from failover nodes. Previously backup was silently ignored when
all nodes shared the same non-zero priority from matcher rule length.
Wire pStats into sub-handlers to capture real byte counts from QUIC
streams instead of the dummy listener conn. Resolve target address and
validate capsule-protocol after authentication instead of before 200
response to prevent dead tunnels and unauthenticated probing. Merge
pending observer events rather than replacing on consecutive failures.
Add idle read timeout to TCP tunnel relay. Add doc comments on exported
symbols and NewHandler.
- Add RewriteRequestBody field to HTTPNodeConfig (yaml: rewriteRequestBody)
- Add RewriteRequestBody field to HTTPNodeSettings in core chain
- Add rewriteReqBody function symmetric to rewriteRespBody in sniffer_rewrite.go
- Wire up rewriteReqBody call in httpRoundTrip before body wrapping for recording
- Add new RewriteResponseBody config field, deprecate old RewriteBody
- Parse both RewriteRequestBody and RewriteResponseBody in node config parsing
- Add 10 test cases covering nil, skip, content-type filter, and chained rewrites
- Bump core dependency to v0.4.1
- Fix: clone response header map in internal/util/sniffing/sniffer_http.go
Fix two data races in Sniffer.httpRoundTrip:
- resp.Header was assigned directly to ro.HTTP.Response.Header without
cloning, creating shared mutable state between the response and recorder.
- httpSettings.RequestHeader was set on req.Header but not propagated to
ro.HTTP.Request.Header, causing the recorder to miss configured headers.
The readTimeout field (default 15s) was being applied via xnet.Pipe to both
directions of a bidirectional proxy connection. During asymmetric transfers
(e.g. HTTP file download), the direction reading from the tunnel sees no
data after the initial request is forwarded, causing SetReadDeadline to fire
after 15s and abort the entire transfer.
Fix: add a separate idleTimeout field (default 0=disabled) to the metadata
structs in both forward/local and forward/remote handlers, and switch
xnet.Pipe to use idleTimeout instead of readTimeout. The readTimeout field
now only applies to the initial protocol sniffing/handshake phase.
Also document readTimeout vs idleTimeout semantics across all 24 locations
in the x/ module where these timeouts appear:
- readTimeout: handshake sniffing deadline (handlers), upstream response
header timeout (http.Transport), or transport-level read deadline
- idleTimeout: idle read deadline per Pipe direction (0=disabled)
- ReadTimeout on Sniffer/SnifferBuilder: upstream response header/TLS
handshake read timeout during sniffing
When rtcp listener calls Router.Bind with ":8000" (port-only address),
the relay AddrFeature.ParseFrom fails because net.SplitHostPort returns
an empty host. This leaves AType at default 0, causing AddrFeature.Encode
to return ErrBadAddrType.
Fix by normalizing port-only addresses to "0.0.0.0:<port>" in both tunnel
and relay connectors before ParseFrom. Also fix error check on
resp.WriteTo in the tunnel handler's handleBind to prevent silent
protocol desync.
- Wrap non-CONNECT upstream with traffic_wrapper and stats_wrapper (was only on CONNECT path)
- Write 503 error response on forwardRequest failure instead of silent connection close
- Decouple resp.Header from w.Header() to prevent metadata header leakage/doubling in 407 responses
- Return pipeTo signal from authenticate instead of dialing inline (matches http handler pattern)
- Add idleTimeout metadata field and pass to xnet.Pipe for CONNECT tunnels
- Add 4 tests for probe resistance host forwarding and knock bypass
Extract authenticate/probe-resistance to auth.go and roundTrip/forwarding
to proxy.go, following the handler/http/ pattern. Export sentinel errors
(ErrAuthFailed, ErrWrongConnType) for precise test assertions. Fix
flushWriter panic recovery to handle non-string/non-error panics. Default
HTTPS port to 443 when no port is specified in the Host header.
Extract Handle into handleRawForwarding, handleSniffedProtocol, SnifferBuilder,
newRecorderObject, and checkRateLimit across forward.go, sniffing.go, and
util.go. Replace redundant x509.ParseCertificate with tlsCert.Leaf (already
populated by tls.LoadX509KeyPair since Go 1.23).
Service registration (register) calls ParseService, which captures
logger.Default() into each service/handler/listener. Since
logger.SetDefault was called after register, all components captured
the initial Info-level logger, making -D/-DD ineffective for service
logging.
Extract three pure-logic components from httpHandler to eliminate I/O
coupling from auth and sniffing construction, enabling synchronous unit
tests and reducing per-request allocation in sniffAndHandle:
- Authenticator struct with AuthResult return type — auth decisions no
longer write to the connection; callers handle response I/O. Auth
tests drop net.Pipe/goroutines for direct return-value assertions.
- SnifferBuilder pre-built in Init and reused per-connection via Build(),
replacing inline sniffing.Sniffer{} construction in sniffAndHandle.
- normalizeRequest extracted to util.go with NormalizedRequest type,
collapsing 20 lines of inline URL/normalisation into a single call.
- knockMatch extracted as standalone pure function.
- clampBodySize exported as ClampBodySize for cross-package use.
- util_test.go with 22 tests covering utility functions.
- helpers_test.go consolidates shared test fakes (logger, observer, conn).
Knock previously accepted a single hostname. Now it accepts multiple
comma-separated hostnames; probe resistance is bypassed if the request
hostname matches any entry in the list. Matching is case-insensitive
and whitespace around entries is trimmed.
- Split host:port in Host header before DNS/IP validation, accept bare IPs
- Guard sniffer connection ownership with snifferHandled flag to prevent
double-close when sniffers take over the tunnel
- Close req.Body in handleProxy keep-alive loop on both error and success
paths to prevent goroutine leaks from idle HTTP transport connections
- Log all resp.Write errors (5 call sites) instead of silently discarding
- Respect resp.Close for all responses, not just ContentLength >= 0,
fixing unnecessarily closed chunked keep-alive connections
- Harden probe resistance: validate strconv.Atoi result, add 15s timeout
to web redirect HTTP client, document pr.Knock empty/set/mismatch logic
Extract newRecorderObject, selectTarget, sniffingDial, buildSniffer,
handleSniffedProtocol, and handleRawForwarding from Handle (200→87 lines).
Move nil Router guard before sniffing for early failure. Log previously
silent sniff and pipe errors at Debug level. Apply readTimeout to Pipe.
Append :0 when target addr lacks a port. Reorder metadata fields and
add doc comments on all exported symbols.
- Return clear error when Router is nil instead of panicking
- Capture and log sniffing.Sniff and xnet.Pipe errors at Debug level
- Move dial closure and Sniffer construction into HTTP/TLS switch branch
to avoid unnecessary allocation on non-HTTP/TLS traffic
- Merge ProtoHTTP and ProtoTLS cases, branch internally on proto
- Add doc comments on NewHandler and Handle
- Add comment explaining nil hop is valid (no forwarder config)
- Pass readTimeout to xnet.Pipe via WithReadTimeout
Fix bugs in the file handler: unbuffered send channel deadlock on Close,
conn leak when send drops on done, and Addr() returning nil. Add done-
priority select in send(), explicit ln.Close() in Close(), Unwrap() on
responseWriter for http.ResponseController, and nil guards.
Also add package doc, NewHandler doc, and 39 unit tests covering the
listener, responseWriter, handler lifecycle, auth, and file serving.
On Windows, icmp.ListenPacket("ip4:icmp", "0.0.0.0") does not reliably
deliver ICMP packets (golang/go#38427). Add a platform-specific
ListenPacket helper that binds to a discovered interface address and
enables SIO_RCVALL via WSAIoctl for promiscuous receive.
Fixesgo-gost/x#36
tokenTransport now delegates CloseIdleConnections to the underlying
*http.Transport so http.Client.CloseIdleConnections() works through
the wrapper. Three plugin Close() methods updated to unwrap via
HTTPClientTransport before calling CloseIdleConnections.
RoundTrip now short-circuits when caller already set Authorization,
avoiding an unnecessary req.Clone allocation on the pass-through path.
Pull node-resolution logic out of dial/dialTLS into pure resolveHTTPNode
and resolveTLSNode helpers. This separates node selection (bypass, hop,
fallback) from connection establishment, making both independently
testable. Add 21 new tests covering bypass, hop selection, nil-hop,
addr fallback, upgrade response type mismatch, websocket frames,
response body rewriting, and h2 preface validation.