Add SOCKS5 UDP ASSOCIATE; shard UDP session map (v0.5.0) - #36
Merged
Conversation
Adds UDP-over-SOCKS5 (RFC 1928 §6 CMD=0x03) for both forward (`socks`) and reverse (`R:socks`) tunnels. The SOCKS server binds a UDP relay socket, parses the SOCKS5 UDP datagram header on every received packet, and ferries the inner payload through a per (source, target) QUIC bi-stream that reuses the existing length-prefixed UDP framing. Replies are re-wrapped with the SOCKS5 UDP header and sent back to the original UDP source. Per-session lifetime is tied to the TCP control connection plus a 60s idle timeout. IPv4, IPv6, and domain targets all work; fragmented datagrams (FRAG ≠ 0) are rejected. While extending the SOCKS5 handshake also accepted IPv6 ATYP=0x04 for TCP CONNECT — previously it returned "address type not supported". UDP forward client's per-source session table swapped from `Arc<Mutex<HashMap<SocketAddr, _>>>` to `Arc<DashMap<SocketAddr, _>>` so the receive loop's per-datagram lookup doesn't serialize on a single mutex under high pps from many sources. New integration tests cover forward + reverse UDP-over-SOCKS5 round trips against a localhost UDP echo target. Unit tests cover the SOCKS5 UDP header codec. Benchmarks regenerated (`./benchmark/run.sh`). Loopback iperf and chisel-bench results are essentially unchanged from the prior PNGs — this PR is a feature/scalability release, not a throughput release. The `read_chunk` single-copy bullet was tried and reverted: on loopback quinn returns small per-frame chunks and the per-chunk syscall overhead regressed throughput by ~40 % vs the existing 256 KB BufReader+copy_buf path. Bullet stays open in the README with that rationale. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UDP ASSOCIATE, CMD=0x03) for both forward (socks) and reverse (R:socks) dynamic tunnels. The SOCKS server binds a UDP relay socket, parses the per-datagram SOCKS5 UDP header on each received packet, and ferries the inner payload through a per (source, target) QUIC bi-stream that reuses the existing length-prefixed UDP framing. Replies are re-wrapped with the SOCKS5 UDP header and sent back to the original UDP source. Lifetime is tied to the TCP control connection + a 60 s idle timeout. IPv4, IPv6, and domain targets all work; fragmented datagrams (FRAG ≠ 0) are rejected, same as virtually every other SOCKS5 implementation.Arc<Mutex<HashMap<SocketAddr, _>>>→Arc<DashMap<SocketAddr, _>>so the UDP receive loop's per-datagram lookup doesn't serialize on a single global mutex under high pps from many sources. (Effect not visible in iperf since iperf is TCP — would require a UDP fan-in benchmark we don't have today.)RecvStream::read_chunk. Replacing the QUIC-sideBufReader+copy_bufwithread_chunk+write_allregressed loopback iperf throughput from 763 MB/s to 458 MB/s (-40 %): on loopback quinn returns chunks at QUIC-frame granularity, and the per-chunk syscall overhead overwhelmed the saved memcpy. Reverted; README bullet stays open with the rationale and a "revisit when quinn exposes a vectoredBytes-returning read API" note.Net effect on loopback iperf bench (after revert): essentially flat vs. previous release — ~780 MB/s Rusnel vs ~377 MB/s Chisel, p99 0.46 ms vs 2.00 ms. PNGs in
benchmark/{iperf,chisel-bench}/results/loopback/regenerated.Wire format
Unchanged from 0.4.0 — 0.4.0 ↔ 0.5.0 peers interop.
HostPortnow derivesHash(needed to key aDashMap) andRemoteRequest::dynamic_udp(target)is added as the UDP analog ofdynamic_tcpfor the SOCKS5 UDP relay.Test plan
cargo fmt --all -- --checkcargo clippy --all -- -D warningscargo test --all— 41 tests pass, including 2 new integration tests:test_socks5_udp_associate_forward— forwardsocks+ IPv4 UDP echotest_socks5_udp_associate_reverse— reverseR:socks+ IPv4 UDP echoNETEM_PROFILES=loopback ./benchmark/run.sh— loopback iperf + chisel-bench refreshed; numbers within run-to-run variance of prior PNGsFiles
src/common/socks.rs— UDP ASSOCIATE handler, SOCKS5 UDP header codec, IPv6 ATYP supportsrc/common/remote.rs—dynamic_udp,HashonHostPortsrc/common/udp.rs—Arc<Mutex<HashMap>>→Arc<DashMap>on the per-source session table;read_datagram/write_datagrammadepub(crate)for reuse fromsocks.rstests/common/mod.rs—socks5_udp_associate,socks5_udp_wrap_ipv4,socks5_udp_unwrap_ipv4helperstests/tunnels.rs— forward + reverse UDP-over-SOCKS5 integration teststests/edge_cases.rs— retarget the "unsupported ATYP" assertion now that 0x04 (IPv6) worksCargo.{toml,lock}—dashmap = "6.1.0", version bumped to 0.5.0CHANGELOG.md,README.md— release notes; TODO list updatedbenchmark/{iperf,chisel-bench}/results/loopback/*.png+chisel-bench/results/wan/*.png— regeneratedMade with Cursor