Skip to content

Support Shadowsocks outbound proxy hops - #2157

Open
missdeer wants to merge 2 commits into
shadowsocks:masterfrom
missdeer:feature/ss-outbound-hop
Open

Support Shadowsocks outbound proxy hops#2157
missdeer wants to merge 2 commits into
shadowsocks:masterfrom
missdeer:feature/ss-outbound-hop

Conversation

@missdeer

Copy link
Copy Markdown
Contributor

Summary

This PR adds TCP-only Shadowsocks (ss://) hops to the existing outbound_proxy chain used by sslocal and ssserver.

It allows an outbound connection to traverse an encrypted Shadowsocks edge hop before reaching the normal Shadowsocks server or another supported proxy hop. Existing SOCKS5, HTTP, and HTTPS outbound-proxy configurations continue to use the same configuration forms and connection paths.

Motivation

The existing outbound chain can hide the final Shadowsocks server behind SOCKS5/HTTP CONNECT proxies, but it cannot use a Shadowsocks server itself as an encrypted intermediate hop. This is useful for deployments where:

  • the first reachable edge must use Shadowsocks encryption or a SIP003 transport plugin;
  • the final landing server should not be dialled directly from the client;
  • the entire route should remain in one sslocal or ssserver process instead of requiring an additional local forwarding process.

What changed

Configuration and URL compatibility

  • OutboundProxy is now an enum with separate plain-proxy and Shadowsocks-hop variants.
  • SOCKS5, HTTP, and HTTPS retain their existing host, port, and optional authentication representation.
  • SIP002 ss:// URLs are parsed through ServerConfig, so cipher, password, remarks, and SIP003 plugin parameters use the existing Shadowsocks parser.
  • to_url() supports round-tripping Shadowsocks hops.
  • Debug output redacts Shadowsocks passwords.
  • Existing single-string and string-array outbound_proxy configuration formats remain supported.
  • A sanitized end-to-end example is provided in examples/chain-ss.json5.

TCP chain composition

  • OutboundProxyKind gains an Ss variant backed by an Arc<ServerConfig>.
  • The chain builder receives the shared Shadowsocks context and wraps the already-established transport in ProxyClientStream for an ss:// hop.
  • OutboundProxyStream gains a boxed Shadowsocks variant. Boxing breaks the recursive stream type while preserving static async I/O dispatch.
  • The same outbound client is used by both local and server TCP relay paths.
  • SOCKS5, HTTP, HTTPS, and Shadowsocks layers can compose without changing the existing first-hop dialer abstraction.

SIP003 plugin lifecycle

  • A SIP003 plugin is supported on the first Shadowsocks outbound hop.
  • The plugin is started once in client mode, and its loopback listener becomes the first physical address dialled by the chain.
  • The plugin handle is retained by the outbound client, so the child process follows the client's lifetime and is terminated through the existing Plugin drop behavior.
  • Plugin readiness probes are serialized to prevent concurrent connections from creating repeated three-second waits.
  • A failed readiness probe is cached for one second and then retried. This avoids a retry storm while still allowing a slow-starting plugin to recover.
  • Plugins on non-first Shadowsocks hops return Unsupported; supporting those requires per-connection rendezvous plumbing and is intentionally outside this PR.

UDP behavior

Shadowsocks outbound hops are TCP-only in this PR.

  • A chain containing an ss:// hop reports that it does not support UDP.
  • Direct OutboundProxyDatagram association returns io::ErrorKind::Unsupported with a specific diagnostic.
  • Local and server UDP relay paths reject traffic when the configured chain contains a Shadowsocks hop.
  • UDP therefore fails closed instead of silently bypassing the chain and exposing a destination or upstream address through a direct datagram path.
  • The legacy behavior for other non-SOCKS5 hop types is unchanged.

Diagnostics and documentation

  • Startup warnings identify potentially recursive Shadowsocks-hop/server configurations.
  • Startup warnings explain that UDP is rejected for chains containing a Shadowsocks hop.
  • README configuration and CLI documentation now list ss://, first-hop plugin support, TCP-only behavior, and the new example.

Compatibility and limitations

  • Existing SOCKS5/HTTP/HTTPS outbound proxy URLs and JSON configuration remain compatible.
  • No new dependencies are introduced.
  • Shadowsocks outbound hops support TCP only.
  • Only the first Shadowsocks hop may contain a SIP003 plugin.
  • UDP chaining through Shadowsocks is not implemented; it requires a separate datagram design with per-hop encapsulation and NAT state.
  • Static cycle detection is diagnostic only and cannot detect every DNS-based or dynamically resolved loop.

Tests

The new integration suite covers:

  • TCP echo through two encrypted Shadowsocks outbound hops;
  • a real mock SIP003 child process on the first hop;
  • throttled plugin-start failure followed by recovery after a delayed plugin start;
  • rejection of a plugin on a non-first Shadowsocks hop;
  • rejection of UDP association through a chain containing a Shadowsocks hop;
  • Send + Sync compatibility of the cached outbound client;
  • SIP002 URL round-tripping, plugin parameters, and example configuration parsing.

Local validation completed successfully:

  • cargo test -p shadowsocks-service --test outbound_ss --no-default-features --features aead-cipher -- --nocapture (6 passed, 2 ignored helper entry points)
  • cargo check --features full-extra
  • cargo build --features full on Windows with NASM 3.01
  • cargo clippy --features "full-extra local-flow-stat utility-url-outline" -- -A clippy::absurd_extreme_comparisons
  • git diff --check

GitHub Actions completed successfully on the pushed branch:

  • Build & Test, including Linux, macOS, and Windows matrices
  • Build MSRV
  • Clippy Check
  • Cargo Deny Check

Security considerations

  • Example credentials and endpoints are placeholders.
  • Shadowsocks passwords are redacted from debug formatting.
  • Unsupported UDP traffic fails closed for ss:// chains instead of bypassing the configured route.

Add TCP-only encrypted hop chaining with first-hop SIP003 plugins and fail closed for unsupported UDP traffic.
@missdeer
missdeer marked this pull request as ready for review July 20, 2026 05:06
@missdeer missdeer changed the title [codex] support Shadowsocks outbound proxy hops Support Shadowsocks outbound proxy hops Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant