Skip to content

Add --allow-socks server flag and --proxy socks5 client support (v0.6.0) - #37

Merged
guyte149 merged 1 commit into
masterfrom
feat/allow-socks-and-socks5-proxy
May 3, 2026
Merged

Add --allow-socks server flag and --proxy socks5 client support (v0.6.0)#37
guyte149 merged 1 commit into
masterfrom
feat/allow-socks-and-socks5-proxy

Conversation

@guyte149

@guyte149 guyte149 commented May 3, 2026

Copy link
Copy Markdown
Owner

Summary

Two new flags from the README roadmap, both gated behind explicit opt-in for safety:

  • --allow-socks (server) — default-deny gate for reverse-SOCKS5 (R:socks / R:port:socks). Without it the server now refuses reverse-SOCKS at the control-plane handshake instead of silently exposing its network. Mirrors --allow-reverse semantics. Forward socks decomposes into per-target dynamic TCP/UDP on the wire and is intentionally not gated by this flag — see the new "Security & access control" roadmap section in the README for the full ACL story.

  • --proxy socks5://[user:pass@]host:port (client) — routes the QUIC connection through a SOCKS5 proxy via UDP ASSOCIATE (RFC 1928 §4). New rusnel::common::proxy module with:

    • ProxyConfig URL parser (socks5://, socks:// alias, bare host:port, optional user:pass@ per RFC 1929).
    • SOCKS5 control-plane handshake (no-auth + user/pass).
    • Socks5UdpSocket adapter implementing quinn::AsyncUdpSocket, wrapping every outbound QUIC datagram in RSV/FRAG/ATYP/DST.ADDR/DST.PORT framing and unwrapping every inbound one. The TCP control connection is held open inside the socket for the lifetime of the relay (RFC 1928 §6).
    • Client-side: bypasses Happy Eyeballs and the cross-attempt endpoint pool when proxied (the proxy owns routing); each reconnect opens a fresh UDP association.

HTTP CONNECT is intentionally not supported in this release — it cannot carry UDP/QUIC. The path that would unlock HTTP/SOCKS-CONNECT proxies is the WebSocket-fallback transport tracked separately in the README roadmap.

Test plan

  • 12 new unit tests in src/common/proxy.rs (URL parser corner cases, SOCKS5 UDP wrap/unwrap roundtrips, fragment/ATYP rejection paths).
  • New tests/proxy.rs integration test stands up an in-process SOCKS5 UDP relay and asserts a real Rusnel client + server pair completes the QUIC handshake through it and round-trips bytes over a tunneled TCP echo.
  • All existing test suites still pass (cargo test --all).
  • cargo fmt --all -- --check clean.
  • cargo clippy --all -- -D warnings clean.
  • Version bumped to 0.6.0; CHANGELOG updated.
  • README CLI help blocks updated; --proxy README TODO bullet checked.

Notes for downstream embedders

  • ServerConfig gains pub allow_socks: bool (default-deny — set to true if you previously relied on reverse-SOCKS without the flag).
  • ClientConfig gains pub proxy: Option<ProxyConfig> (None = direct connect, existing behaviour).
  • server_receive_remote_request gains an allow_socks: bool parameter alongside allow_reverse. Wire format unchanged.

Made with Cursor

Two new flags closing out items from the README roadmap:

- `--allow-socks` on the server gates reverse-SOCKS5 (`R:socks`) at the
  control-plane handshake, default-deny. Mirrors the existing
  `--allow-reverse` semantics so a client can no longer make the server
  spin up a SOCKS listener exposing the server's network unless the
  operator explicitly opts in. Forward `socks` decomposes into per-target
  dynamic TCP/UDP on the wire and is intentionally not gated by this flag
  — see the new "Security & access control" roadmap section for the full
  ACL story.

- `--proxy socks5://[user:pass@]host:port` on the client routes the QUIC
  connection through a SOCKS5 proxy via UDP ASSOCIATE (RFC 1928 §4). New
  `rusnel::common::proxy` module ships a `ProxyConfig` parser, the SOCKS5
  control-plane handshake (no-auth + RFC 1929 user/pass), and a
  `Socks5UdpSocket` adapter implementing `quinn::AsyncUdpSocket` that
  wraps every outbound QUIC datagram in the SOCKS5 UDP framing and
  unwraps every inbound one. The TCP control connection is held open
  inside the socket for the lifetime of the relay (RFC 1928 §6); each
  reconnect opens a fresh association. The client bypasses Happy
  Eyeballs and the cross-attempt endpoint pool when proxied (the proxy
  owns routing). HTTP CONNECT is intentionally not supported here
  because it cannot carry UDP — that requires the WebSocket fallback
  transport tracked separately.

Tests: 12 new unit tests (URL parser, SOCKS5 UDP wrap/unwrap, fragment
and ATYP rejection paths) plus tests/proxy.rs — an integration test
that stands up an in-process SOCKS5 UDP relay and asserts a real
Rusnel client/server pair completes the QUIC handshake through it and
round-trips bytes over a tunneled TCP echo.

Co-authored-by: Cursor <cursoragent@cursor.com>
@guyte149
guyte149 merged commit 1df2039 into master May 3, 2026
1 check passed
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