179 Commits
Author SHA1 Message Date
ginuerzh 8d253dcd88 fix(logger): rotate a file log that names no rotation
A log config can name an output without naming a rotation, and that
combination used to receive a plain *os.File — nothing rotates it, ever.
One spoke on Android ran debug-level logging into exactly that shape and
grew the file to 111MB with no backups, which on a phone is not untidy so
much as fatal.

Default the rotation instead. The values match what an unconfigured
logger has always carried, so naming an output is no longer a way to ask
for an unbounded file, and a configured rotation still wins as written.
outputWriter is split out of ParseLogger so the decision — the part that
chooses whether a file log stops growing — is testable without a logger.

Signed-off-by: Step 5 Preview Free <noreply@stepfun.com>
2026-10-13 02:40:19 +00:00
ginuerzh 17909c4f95 fix(nodes): explicit reference check, trim lookup, deep-copy global definitions
IsReference no longer uses reflect.DeepEqual; ResolveConfig trims the name before registry lookup; cloneConfig deep-copies all nested fields so hop parsing never mutates shared globals. Also documents the intentional fail-fast vs warn-and-skip error semantics.
2026-10-07 01:36:45 +00:00
ginuerzh 8f6a725e7e Merge pull request #130: Global nodes registry for reusing nodes across hops 2026-10-07 01:30:30 +00:00
ginuerzh 4bbf81c9f4 feat(p2p): let a chain node ask the tunnel for a network
The tun link needs the tunnel network "ip" rather than the "udp" its dialer
would otherwise derive: "ip" is the datagram link, marked session-scoped on
the host so it ends on a lost peer session and the consumer re-dials (see
the p2p companion change). Add WithTunnelNetwork to override the derived
network and MDKeyP2PNetwork ("p2p.network") for a chain node to set it, and
have streamconn frame "ip" like "udp" so the GOST-side conn owns the 2-byte
framing on both carriers. An empty network keeps the derived value, so every
existing node is unchanged.

Gate: go build ./... and go vet ./... clean.
2026-10-06 00:36:49 +08:00
iBug c67b2ec1d2 feat: Global nodes registry for reusing nodes across hops 2026-10-02 22:23:34 +08:00
ginuerzh ec4800293d config/parsing/logger: say when a log file has no rotation
A configured output path without a rotation block is a plain file that nothing
rotates: it grows until the disk does something about it. The default logger —
the one an unconfigured program gets — always carries a rotation, so this only
happens to a hand-written config, and the parser is the only place that sees
both the path and the rotation that is missing. Reported on stderr, once, at
parse time; nothing about the behaviour changes.
2026-09-29 16:18:39 +08:00
ginuerzh 1fadb8fe38 config/parsing/logger: a log file that cannot be opened is not fatal
ParseLogger reported the failure through logger.Default(), which is nil until a
caller installs one — and this call is usually what installs it. An unwritable
path therefore dereferenced a nil logger and panicked; inside a c-shared library
that aborts the process, which on Android took an app down a second or two after
start with nothing readable left behind (an app's stderr goes nowhere, and the
tombstone carries no abort text).

Measured with Output "file" — a path, not a mode — in an app whose working
directory is "/" and not writable.

The failure goes to stderr instead, which is always there, and the default
output stays in place. The regression test forks a child process, because a
fresh one is the only place the default logger is still unset: TestMain skips
installing its logger for that child.
2026-09-29 16:03:21 +08:00
ginuerzh 1776e1a4ef p2p: rename TunnelProvider to Tunnel and OpenTunnelStream to Dial 2026-09-22 15:17:10 +08:00
ginuerzh f70b114578 plugin: manage external plugin subprocess lifecycle
Add Command []string to PluginConfig and a new x/plugin package that
spawns plugin subprocesses (deduplicated by argv) and terminates them
on Shutdown. Linux uses Pdeathsig, Windows a kill-on-close job object,
so children follow an abrupt parent exit.

Wire Spawn into every config/parsing plugin constructor.
2026-09-07 17:40:20 +08:00
ginuerzh fe9d9c9574 add p2p plugin-backed tunnel dialer for chain nodes
Any dialer can now declare its base transport to run through a tunnel
opened by a named p2p plugin component: a top-level p2ps: section
registers TunnelProviders (gRPC plugin), and chain node metadata
"p2p: <name>" wraps the node's dialer so it dials the plugin's
OpenTunnel endpoint instead of the network directly. The inner dialer
keeps its protocol (tls/ws/mux) and Handshake, which the wrapper
forwards so it still triggers at Transport.Handshake.

- x/p2p: tunnelDialer (fail-closed, per-dial tunnel with idempotent
  CloseTunnel on conn close), SupportedDialer whitelist (tcp only;
  fail closed for unverified inner dialers), gRPC TunnelProvider
- config: P2PConfig, parsing, loader registration (reload-safe via
  unregister+Close), mergeConfig wiring
- dialer wrapper registered before Init so the chain inits once
- pin github.com/go-gost/plugin v0.6.0 (p2p proto); dependency bumps
  are a consequence of the new import
2026-09-05 21:25:13 +08:00
ginuerzh 53269265e1 refactor(config): swap yaml.v3 encoder for goccy/go-yaml
gopkg.in/yaml.v3 is archived/unmaintained upstream. Its only use in x is
Config.Write()'s YAML encoding, which is replaced with the already-present
goccy/go-yaml (compiled in via gin) — no new dependency. goccy output is
wire-equivalent (same keys/structure/omitempty); viper reads it back
identically, confirmed by the config round-trip tests. yaml.v3 drops to an
indirect dep (retained only by the inspector module in the workspace).
2026-09-02 18:00:43 +08:00
ginuerzh 2299c43bb7 forwarder: HTTP header rewrite (regex + plugin) and hostPattern wiring
Implement the header counterpart of the existing body rewrite:

- rewriteHeaderBlock applies rewrite rules to an http.Header in place.
  Regex mode matches header names case-insensitively (per HTTP spec) and
  rewrites each value independently; an empty replacement drops the value,
  and a fully-empty header is deleted. Plugin mode serializes the header
  block (http.Header.Write + trailing CRLF, since ReadMIMEHeader needs the
  blank line), sends it to a core/rewriter.Rewriter plugin, and parses the
  result back with clear+copy (h is a map; reassignment would not escape).
- rewrite_metadata.go consolidates the hand-rolled plugin metadata maps
  (baseMetadata + three SSE inline maps) into rewriteMeta() with common
  sid/direction/uri fields and a new kind (body|header), additive for
  existing plugins.
- sniffer_http.go wires rewriteReqHeader before the request is written
  upstream (and refreshes the recorder header clone) and rewriteRespHeader
  after static response-header overrides, before the recorder clone.
- hostPattern: derive req.Host from the request path via regex submatch
  expansion (Host = $1-style template), for Host-routed mirror origins.
- parse: parseHeaderRewrites gates on the config string (regexp.Compile("")
  yields a non-nil match-all, so gating on the compiled regexp would turn
  an empty name into a silent no-op rule).

Fixes found by e2e: reqHeaderRewrites was assigned after its guard, so the
request header rewrite never ran; tests cover the header round-trip,
plugin gating, metadata kind, and hostPattern expansion.
2026-08-29 08:37:44 +08:00
ginuerzh 93bd2ea760 fix(hop): parenthesize host OR and normalize wildcard in filter->matcher rule
filterToMatcherRule produced `Host(x) || Host(.x) && Proto(...) && ...`,
but the matcher DSL (vulcand/predicate) binds && tighter than ||, so the
apex Host(x) branch bypassed the protocol/path conjuncts — a request to
the apex with a non-matching protocol/path matched where the legacy
filter (AND of host/protocol/path) rejected it.

Also normalize `*.example.com` to `.example.com` before the leading-dot
branch: legacy ParseNode did this so both forms matched apex+subdomains;
without it, `*.example.com` reached Host() as a wildcard matching only
subdomains, dropping apex coverage that `.example.com` still had.

- Parenthesize the host OR when protocol/path conjuncts follow.
- Strip leading `*` + ensure leading dot, mirroring legacy normalization.
- Update TestFilterToMatcherRule expectations; add dot/star combined cases.
- Add TestFilterToMatcherRule_Precedence: runtime guard that apex+wrong
  proto/path is rejected and apex/subdomain+valid is accepted, for both
  `.example.com` and `*.example.com` forms.
2026-08-26 21:02:50 +08:00
ginuerzh 6ad7269644 refactor(hop): normalize deprecated node filter into matcher at parse time
Fold the deprecated per-node filter (host/protocol/path) into an equivalent
matcher DSL rule during config parsing, so hop.Select keeps a single
filtering path (routing.Matcher). Legacy filter configs stay accepted.

- filterToMatcherRule() maps host->Host(), protocol->Proto(), path->PathPrefix();
  a leading-dot host emits `Host(x) || Host(.x)` to preserve the legacy
  apex+subdomain coverage. Converted rules get priority=-1 to keep the
  always-through-selector behavior of the legacy path.
- Remove isEligible/checkHost/checkProtocol/checkPath from x/hop.
  core/ chain.NodeFilterSettings is left untouched (dead but harmless).
- Migrate hop tests to matchers; add TestFilterToMatcherRule.
2026-08-26 20:26:26 +08:00
ginuerzh faeede7881 feat(forwarder): delete JSON body fields via json-: rewrite match
Add a json-: match prefix to http.rewriteRequestBody/rewriteResponseBody
that deletes the matched JSON field (sjson.DeleteBytes) instead of
replacing it, keeping the existing value-regex conditional semantics.
2026-08-25 21:54:27 +08:00
ginuerzh 0132cefaec fix(api): save config back to the loaded file path (gost/x#123)
POST /config without an explicit path= always wrote gost.yaml/gost.json to
the process CWD, silently leaving the config loaded via -C untouched. Track
the last file read by ReadFile and default the save target to it, deriving
the output format from the file extension so a JSON config is saved back as
JSON. Falls back to the CWD default when no config file was loaded.
2026-08-21 21:43:17 +08:00
ginuerzh 64ea5184f8 fix(hop): drop marked-failed nodes before priority selection
http.failCodes (and probe) marking had no effect when the failing node
had a strictly higher matcher priority: the priority short-circuit
returned the top node directly, and FailFilter no-ops on a single-node
tier (len(vs) <= 1 guard), so the marked node was always re-selected
and failover never happened.

Pre-filter nodes marked failed (same Count >= maxFails && within
failTimeout criteria as FailFilter) out of the candidate pool before
priority selection. Single-node hops keep their lone node (safety net),
and an all-failed pool returns nil as before.

Wired maxFails/failTimeout from the hop selector config into the hop
via FailFilterSettingsOption.
2026-08-18 23:15:59 +08:00
ginuerzhandgithub 0e0eab0c52 fix(cmd): restore unix socket port-forward semantics
Commit b3b5986 crammed the URL path into the listen/connect address and
disabled forward mode for path-based protocols, breaking the
`unix://a.sock/b.sock` (listen on a, forward to b) port-forward form and
producing an invalid `a.sockb.sock` address.

Distinguish the two URL forms by host presence:
- three-slash (unix:///abs.sock, empty host) → path is the absolute socket
  address, no forward target;
- two-slash (unix://a.sock/b.sock) → host is the socket and the path is a
  forward target (service) or ignored (node).

Add regression tests for both forms.

Co-Authored-By: Claude (claude-opus-4-8[1m])
2026-08-17 22:51:00 +08:00
Dean Chen 98e9651361 cmd: keep absolute unix socket paths for path-based schemes (#121)
Path-based address selection used the dialer/listener type after registry
fallback, so unix remapped to tcp skipped EscapedPath and dropped
addrs like /var/run/docker.sock from three-slash URLs.

Decide path-based protocols from the original scheme components.

Signed-off-by: Dean Chen <862469039@qq.com>
2026-08-17 22:29:17 +08:00
ginuerzh 3157436140 x: add Redis backend for cache
Adds a RedisCache config block and redisCache implementation for the
generic cache.Cache interface, enabling distributed HTTP response
caching across gost instances.

go-redis/redis/v8 was already a dependency (used by recorder and
internal loader) — no new imports needed.
2026-07-29 22:39:19 +08:00
ginuerzh 3cd4fad4bf x: close forwarder hop probe goroutines on service shutdown
Same leak class as the previous chainGroup fix: a forwarder built from
a hopGroup or inline nodes starts probe goroutines at parse time, but
nothing closed them — every SIGHUP reload leaked them permanently.

- parseForwarder returns an io.Closer for service-owned hops (hop
  group or inline nodes); registry-owned named hops return nil since
  the registry closes them on unregister
- ParseService registers the closer via ClosersOption and closes it
  when construction fails partway
- hopGroup.Close no longer closes the underlying hops: they come from
  the HopRegistry and are shared with other referents; closing them
  here would kill shared state
2026-07-28 23:45:40 +08:00
ginuerzh 50f4a6b096 x: close chainGroup probe goroutines on service shutdown
chainGroup.Close() existed but was never called: the group is wrapped
in a chain.Router held by the listener/handler, and service.Close()
only closed the handler, observer, and listener. Every SIGHUP reload
leaked the old config's probe goroutines permanently.

Wire the close path end to end:
- Router.Close() closes its chain when it implements io.Closer
- service ClosersOption registers io.Closers called on shutdown
- ParseService passes both routers via ClosersOption, and closes
  them on construction failure so probes started early don't leak
2026-07-28 23:14:27 +08:00
ginuerzhandClaude 30ba6d64a5 x: add matcher + probe to chainGroup with independent entry marker
chainGroup now selects chains in two stages: a matcher filter (by host/network)
builds an eligible pool, then the group selector applies FailFilter + strategy.
Each entry carries its own failure marker so chain-level probe marking is
independent of node-level failCodes markers (probe Reset() can't undo a node
failCodes Mark()).

Config gains ChainGroupEntry (chain/matcher/probe) replacing the old []string,
with a mapstructure decode hook preserving backward-compatible "chains: [a, b]"
string lists and time.Duration string parsing. Probe goroutines run per-entry
and mark the entry on failure.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 21:20:34 +08:00
ginuerzh 72db96d4cd x: add hopGroup for conditional hop-level selection with probe/failCodes marker separation
hopGroup wraps named hops with per-entry matcher + probe, implementing
hop.Hop for drop-in compatibility with the existing Forwarder.Forward().

Core design: hopEntry.marker (probe, hop-level) is independent of
node.Marker() (failCodes, node-level) so probe Reset() cannot undo a
failCodes Mark() — the two mechanisms no longer conflict on the single
chain.Node marker.

- hop/group.go: HopEntry + hopGroup + probe infrastructure
- config/config.go: ForwardHopGroupConfig, ForwardHopConfig types
- config/parsing/service/parse.go: parseHopGroup, HopGroup > Hop > Name > Nodes precedence
- config/parsing/node/parse.go: export parseProbeConfig → ParseProbeConfig
2026-07-27 22:08:34 +08:00
ginuerzh 3474501784 x: add generic cache component and HTTP response caching
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.
2026-07-26 17:26:00 +08:00
ginuerzh 431fe759dc x: matcher.bodySize defaults to DefaultMatcherBodySize (1MB) when unset
bodySize=0 now uses DefaultMatcherBodySize (1MB) instead of disabling
body reading, so BodyRegexp/BodyJSON matchers work out of the box.
MaxMatcherBodySize raised to 10MB. bodySize=-1 opt-out removed
(no real use case).
2026-07-25 21:46:48 +08:00
ginuerzh edd6e5f3f2 x: add failCodes support for HTTP status-based node fail marking 2026-07-25 14:36:46 +08:00
ginuerzh 8602043e8d x: add BodyJSON matcher and json: rewrite prefix
- routing: add BodyJSON(path, regexp) matcher using gjson for JSON path
  field lookup instead of regex against raw body bytes
- config: detect 'json:<path>[=regex]' prefix in Match field for
  rewriteRequestBody/rewriteResponseBody, using gjson query + sjson set
- forwarder: add shouldApply() helper for rewrite type matching; JSON
  rewrites match only application/json content type
2026-07-24 12:28:08 +08:00
ginuerzh f5127f010c fix(forwarder): pass Probe config to node in parseForwarder
ForwardNodeConfig has a Probe field but parseForwarder was missing it
when converting to NodeConfig, silently dropping probe configuration
for forwarder nodes. cmd probe works regardless (no transport needed),
tcp/http probes are limited to the default tcp dialer (plain TCP).
2026-07-16 23:15:16 +08:00
ginuerzh f59eac64d9 probe: add cmd probe type with cross-platform shell support
- Add CmdProber to x/internal/probe/cmd.go with shellCmd() for Windows/Unix
- Add cmd probe dispatch in x/chain/node.go (early-return, no transport needed)
- Add Command field to config.ProbeConfig and parseProbeConfig mapping
- Add 5 CmdProber unit tests (success, failure, timeout, not-found, pipeline)
- Add 2 chain integration tests (cmd probe + cmd disabled)
2026-07-16 20:27:21 +08:00
ginuerzh 46fc054ad5 x: implement node-level liveness probe
- x/chain/node.go: StartNodeProbe goroutine — periodic TCP/HTTP probing
- x/internal/probe/probe.go: HTTPProber with status code validation
- x/selector/latency.go: LatencyFilter, LowestLatencyStrategy (generic)
- x/config: ProbeConfig, MaxLatency, parseProbeConfig, lowestlatency strategy
- x/hop/hop.go: Close() cascade to node.Close()
- x/chain/chain.go: Chain.Close() cascade to hop.Close()

Tests: 4 latency selector tests, 4 node probe tests, 4 HTTP prober tests,
1 hop failover integration test.
2026-07-16 20:27:21 +08:00
ginuerzh 7b5a92817f feat(routing): add BodyRegexp matcher for HTTP request body matching
Lets a node match routing rules against a size-capped prefix of the
HTTP request body. The forwarder HTTP sniffer reads the prefix before
node selection when any node in the hop opts in via matcher.bodySize
(capped at MaxMatcherBodySize = 1MB), restores the stream, and passes
the prefix through hop.Select to the matcher.

- routing: BodyRegexp predicate; Body field on routing.Request
- chain/hop: plumb Body through SelectOptions
- config: matcher.bodySize on NodeFilterConfig
- sniffer_http: aggregate max body size across hop nodes, read+restore
- registry: NodeList wrapper forwards Nodes() so body-size is visible
  for named hops
- bump go-gost/core to v0.5.3
2026-07-03 17:22:10 +08:00
ginuerzh 7f36d10605 add MaxChunkSize for bounded buffering of chunked non-streaming bodies
- HTTPBodyRewriteConfig gains maxChunkSize field (YAML: maxChunkSize)
- newRewriteBody replaces the unconditional skip of chunked non-streaming
  bodies with bounded buffering: reads up to maxChunkSize+1 bytes,
  rewrites if fits, passthrough if overflows
- Default maxChunkSize is 1MB when no rule sets one
- Compressed chunked bodies: decompress → rewrite → recompress when fits
- rewriteRespBody/rewriteReqBody clear Transfer-Encoding only when
  Content-Length is set (fixes latent stale chunked header bug)
2026-06-30 11:02:48 +08:00
ginuerzh 4c086685b1 feat(forwarder): add sid metadata to non-streaming body rewrite paths
Pass session ID metadata to plugin Rewriters in non-streaming rewrite
paths (plain and compressed) for consistency with the SSE streaming path.
Also restore SSE lifecycle metadata (start/end phase emission, event index
tracking) and fix a missing Rewriters config merge in parser.

The non-streaming paths now carry sid metadata via apply(), while the SSE
path provides sid, sse_phase (start/event/end), and event_index for each
stream event.
2026-06-28 18:42:58 +08:00
ginuerzh 1b07475bf3 feat(forwarder): support plugin-based rewriter in HTTP body rewrite rules
Each rewriteBody/rewriteResponseBody/rewriteRequestBody rule can now
optionally reference a named rewriter plugin (HTTP/gRPC backend) via
the rewriter field. When set, body rewriting delegates to the plugin
rather than applying pattern/replacement, while content-type filtering
still applies.

Coincident fixes:
- rewriter/plugin/grpc: return nil when conn is nil (avoid nil-ptr panic)
- rewriter/plugin/http: drain response body in defer to prevent leaks
- config/parsing/rewriter: pass TimeoutOption to gRPC plugin
- config/parsing/service: warn when referenced rewriter is not registered
2026-06-27 22:52:17 +08:00
ginuerzh 3b25e0317f feat(rewriter): implement plugin-based Rewriter module with gRPC and HTTP backends
Add the first implementation of core/rewriter.Rewriter as a plugin-only
component following the bypass/admission pattern (single-value, no
RewriterObject). Includes:

- plugin/rewriter/proto/: protobuf definition with Rewrite RPC returning
  transformed data in the reply
- x/rewriter/plugin/: gRPC and HTTP plugin clients
- x/registry/rewriter.go: hot-reload-safe RewriterRegistry with wrapper
- x/config/config.go: RewriterConfig, Config.Rewriters, ServiceConfig.Rewriter
- x/config/parsing/rewriter/parse.go: config parser (plugin backends only)
- core/handler/option.go: Rewriter field + RewriterOption on handler.Options
- x/config/loader/loader.go: rewriter registration during startup
- x/config/parsing/service/parse.go: inject rewriter into handler options
2026-06-27 21:44:13 +08:00
ginuerzh 143438a30e feat(auth): add skipauth metadata option to bypass auth for whitelisted client IPs
Introduces WhitelistedAuthenticator that wraps an auth.Authenticator to skip
authentication when the client IP matches configured CIDR ranges or exact IPs.
The wrapping happens at service-parse time, transparently covering all handler
types (HTTP, SOCKS4/5, relay, http2, SSH).

Usage:
  gost -L 'user:pass@:5999?skipauth=192.168.0.0/24,172.22.22.22/32'
2026-06-27 14:33:40 +08:00
ginuerzh cd97e5e746 fix(hop): skip priority short-circuit when multiple nodes share the same priority
When two or more nodes have an identical matcher rule they receive
identical default priority (rule string length). The priority shortcut
assumed the top-priority node was the single authoritative choice and
returned it directly, bypassing the selector (FailFilter, BackupFilter,
strategy) — so weight, round-robin, and hash were silently ignored.

Add a third condition: the top priority must be strictly greater than
the second-highest. When multiple nodes share the same highest priority
the selector applies normally for load balancing.

Add doc comments on NodeMatcherConfig.Priority explaining the three
semantic ranges (0=auto, negative=always-selector, positive=shortcut).
2026-06-26 22:53:17 +08:00
ginuerzh ffa7ceaed7 feat(tls): persist auto-generated CA certificate to $HOME/.gost/
Previously BuildDefaultTLSConfig generated a new random self-signed
certificate in memory on every run, making it impossible for users to
extract and trust the certificate in their system trust store.

Now when no explicit cert files are configured, GOST:
1. First checks cert.pem / key.pem in CWD (backward compatible)
2. Then checks auto-ca-cert.pem / auto-ca-key.pem under $HOME/.gost/
3. If none found, generates a new ECDSA P-256 CA certificate, persists
   it to $HOME/.gost/, and uses it

The persisted cert has IsCA=true so tools recognize it as a root CA.
Falls back to in-memory-only when disk writes fail.

Closes go-gost/gost#876
2026-06-23 20:29:57 +08:00
ginuerzh 2e16457467 fix(config): warn when TLS options are set on a plaintext listener (#108)
Log a warning in ParseService when certFile/keyFile/caFile are configured for a listener type that never terminates TLS (tcp, udp, ws, mws, redirect, tproxy, rtcp, rudp, unix, runix, serial, stdio), so the misconfiguration is no longer silent. Uses a conservative plaintext blocklist; TLS-keyed off raw config strings so normal plaintext services stay quiet.

Refs go-gost/gost#579.
2026-06-22 14:29:41 +08:00
ginuerzh 9ccf402ed1 feat(listener): add stdio listener for SSH ProxyCommand support
The stdio listener wraps os.Stdin/os.Stdout as a net.Listener that accepts
exactly one connection. It enables GOST to be used as an SSH ProxyCommand,
where the parent process (SSH) pipes the transport byte stream through the
child process's standard I/O.

Usage: gost -L stdio://example.com:22 -F http://proxy:8080

The URL host:port is treated as the forwarding target and converted to a
forward node in buildServiceConfig. The standard forward handler then
routes traffic through the configured chain to the destination.

Fixes go-gost/gost#433
2026-06-21 15:39:52 +08:00
ginuerzh d4d823df45 fix(recorder): forward per-service recorder metadata to plugin recorders (#412)
User-configured metadata on recorder objects (recorders[].metadata in YAML)
was silently discarded — MetadataRecordOption existed but was never called
anywhere in the codebase, causing plugin (HTTP/gRPC) recorders to always
receive null metadata.

Fix: wrap recorders that carry config metadata in MetadataRecorder, which
auto-appends the metadata to every Record() call. This covers all paths
(handler, router, sniffer, service) with zero call-site changes.
2026-06-21 13:25:27 +08:00
ginuerzh e583f5b538 feat(parser): support HTTP(S) URL config sources with -C flag
Add remote config fetching capability to the -C CLI flag, enabling
centralized fleet management of gost nodes.

- readConfig() now detects http:// and https:// URLs and fetches
  them via HTTP GET with a 30s timeout and shared client pool
- Format auto-detection from Content-Type header (using mime
  parsing) with fallback to URL path extension (.yaml/.json)
- 10 MiB response size limit to prevent memory exhaustion
- URL userinfo stripped from error messages for safety
- Fix ReadFile to force viper config type from file extension,
  preventing format detection ambiguity
- Fix viper config search path fallback: skip cfg.Load() when
  -C flags are explicitly provided, avoiding unwanted merges
- Tests: URL fetch (YAML/JSON), non-200, oversize, detectFormat,
  isHTTPURL, sanitizeURL, and combined file+URL Parse scenario
2026-06-20 20:11:43 +08:00
ginuerzh 9c585e2de8 feat(parser): support multiple -C config files, merged left-to-right
Allow passing -C multiple times to load and merge several configuration
files in order. Slice fields are appended; scalar fields (TLS, Log, API,
Metrics, Profiling) are overridden by later files — the same semantics as
the existing mergeConfig() used for CLI+config merging.

- Change Args.CfgFile string to Args.CfgFiles []string
- Extract readConfig() helper from Parse() for single-source loading
- Loop over CfgFiles in Parse(), merging each into the accumulator
- Add Quotas to mergeConfig (was missing from the slice list)
- Add TestParse_MultiFile integration test for two-file merge

Closes go-gost/gost#150
2026-06-20 18:56:08 +08:00
Usishchev Yury b23553d37a feat: add quota limiter and quota API (#107) 2026-06-20 14:44:42 +08:00
ginuerzh ba3e1cbe09 fix(config/loader): close old services before binding new ports on reload (issue #754)
Services are the only component group that binds a port during parsing
(ParseService calls listener.Init). The generic registerGroup "parse-all-
then-swap" sequence bound every new listener while the old services were
still listening, causing EADDRINUSE on SIGHUP reload — a regression from
82e7e50.

Close and unregister all old services first (unregisterAll relies on
registry.Unregister closing io.Closer values, which frees each service's
port), then parse, bind, and register the new ones. This restores the
pre-82e7e50 close-old-before-bind ordering for the services group.
2026-06-14 01:40:50 +08:00
Yuan Tong 8b0806bad4 feat(listener/runix): support remote UDS forwarding (#102) 2026-06-13 13:39:14 +08:00
Denis Galeev 9f610fd163 feat service: introduce labels for logs/recorder (#104) 2026-06-13 13:05:14 +08:00
Kebin Liu eaeac2763e Bypass with network (#101)
* Fix logger init before use it

* Support specific network protocol validation in bypass
2026-06-13 12:36:09 +08:00
Kebin Liu 69acf08560 Fix logger init before use it (#100) 2026-06-05 20:41:50 +08:00