Pipe: remove context.WithCancel that caused premature ctx.Done() before the second pipeHalf could return its error. Replace with a completed counter that drains both errors even on cancellation. Transport: increase error channel buffer from 1 to 2 to prevent goroutine leak when both CopyBuffer goroutines complete.
6.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this directory.
Build & Verify
cd x && go build ./... # verify compilation
cd x && go vet ./... # static analysis
cd x && go build ./... && go vet ./... # both
There are no tests in this module. Build + vet is the verification path.
Architecture
x/ is the implementation layer for the GOST proxy framework. Every interface defined in core/ is implemented here. The module ties the whole system together: it provides ~25 handler implementations, ~25 listener implementations, ~20 dialer implementations, ~15 connector implementations, plus config parsing, registry management, and all cross-cutting concerns.
Component pattern (applies to every handler, listener, dialer, connector)
Every component follows this exact pattern:
// 1. init() self-registration via blank import
func init() {
registry.HandlerRegistry().Register("http", NewHandler)
}
// 2. Private struct holding options + parsed metadata
type httpHandler struct {
md metadata
options handler.Options
}
// 3. Functional options constructor
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &httpHandler{options: options}
}
// 4. Init extracts typed values from generic metadata
func (h *httpHandler) Init(md md.Metadata) error {
return h.parseMetadata(md)
}
Each component package also defines a private metadata struct and a parseMetadata(md) method that uses mdutil.GetBool/GetInt/GetString/GetDuration(md, keys...) to extract configuration. Metadata keys are lowercased and multiple fallback keys are tried (e.g., "observePeriod", "observer.period", "observer.observePeriod").
Config parsing and service composition
config/parsing/service/parse.go is the critical wiring function. ParseService(cfg) does:
- Defaults listener type to
"tcp", handler type to"auto" - Sets up TLS config, network namespace, authentication, admission, bypass
- Looks up the listener and handler factories from their registries, calls them with composed options
- Creates a
service.Service(which runs the accept loop callinghandler.Handleper connection) - Wraps the listener in order: proxyproto → metrics → stats → admission → traffic limiter → connection limiter
Each config sub-package (config/parsing/{auth,bypass,admission,chain,hop,limiter,...}/) has a parse.go that converts config structs into registry lookups and constructed objects.
Metadata system (metadata/)
metadata.NewMetadata(map[string]any) wraps a raw config map. All keys are lowercased on lookup. The mdutil package (metadata/util/) provides typed accessors used everywhere:
mdutil.GetBool(md, keys...),GetInt,GetFloat,GetString,GetStringsmdutil.GetDuration(md, keys...)— int values treated as seconds; strings parsed withtime.ParseDurationmdutil.GetStringMap(md, keys...),GetStringMapString
Metadata key constants are defined in config/parsing/parse.go (e.g., MDKeyProxyProtocol, MDKeySoMark, MDKeyInterface, MDKeyEnableStats).
Internal packages (internal/)
Do not import from external modules. Go's internal/ restriction enforces this. Key internal packages:
| Package | Purpose |
|---|---|
internal/ctx/ |
Context values for session state during requests |
internal/io/ |
Custom IO readers/writers |
internal/loader/ |
Loader, Lister, Mapper interfaces for hot-reloadable components |
internal/matcher/ |
Pattern matching utilities |
internal/net/ |
Network utilities (resolver, dialer, proxyproto, HTTP helpers, UDP listener) |
internal/plugin/ |
External plugin process management |
internal/util/sniffing/ |
Protocol sniffing (TLS, HTTP, WebSocket) |
internal/util/stats/ |
Connection stats tracking |
internal/util/tls/ |
TLS config loading, cert management |
internal/util/ws/ |
WebSocket utilities |
Context propagation (ctx/)
x/ctx/value.go provides typed context key/value helpers used to pass per-request data through the handler chain:
ContextWithSid/SidFromContext— Session IDContextWithSrcAddr/SrcAddrFromContext— Source addressContextWithDstAddr/DstAddrFromContext— Destination addressContextWithClientID/ClientIDFromContext— Client ID (for hash-based load balancing)ContextWithHash/HashFromContext— Hash source for selector
Hot-reloadable components
Auth, bypass, admission, hosts, ingress, limiter, recorder, router, and SD components all support periodic reload from file/redis/http sources. The pattern:
- Options include
fileLoader,redisLoader,httpLoaderplus aperiodduration - A background goroutine runs
periodReload(ctx)callingloader.Load()on each tick - The component swaps its internal data structure (e.g.,
ipMatcher,hostMatcher) atomically
Listener wrapper ordering
Listeners wrap net.Listener in a fixed order. Getting this wrong breaks metrics/admission/limiting:
proxyproto → metrics → stats → admission → traffic_limiter → conn_limiter → (protocol-specific)
Registry
All registries are in x/registry/registry.go — a single file with ~25 typed global singletons backed by sync.Map. Each is accessed via an exported function (e.g., registry.HandlerRegistry(), registry.ListenerRegistry()). Registration returns ErrDup on name collision; empty names are silently ignored.
Selectors (selector/)
Load-balancing strategies for hops and chain groups:
| Strategy | Behavior |
|---|---|
RoundRobinStrategy |
Atomic counter, modulo selection |
RandomStrategy |
Weighted random using RandomWeighted |
FIFOStrategy |
Always first element (stickiness) |
HashStrategy |
CRC32 of client ID or hash context value |
Filters (FailFilter, BackupFilter) are applied before the strategy, reducing the candidate pool.
API (api/)
Uses Gin framework. Registers CRUD endpoints under /config for all component types. Supports basic auth when an auther is configured. Swagger docs at /docs.
Metrics (metrics/)
Prometheus-style metrics following _total, _seconds naming conventions. Constants for all metric names (e.g., MetricServiceRequestsCounter, MetricNodeConnectDurationObserver). Enable(bool) toggles globally. When disabled, GetCounter/Gauge/Observer return noop implementations.