feat: add SOCKS4 and SOCKS4a proxy support (#473) - #510
Conversation
Support socks4:// and socks4a:// proxy URLs so clients can tunnel HTTP(S) through SOCKS4 proxies, with optional user ID and remote DNS via the SOCKS4a extension.
Reject NUL bytes and overlong domain names in SOCKS4a requests, align SetProxy docs with socks4/socks4a DNS behavior, and assert the public client path actually sends a domain through the SOCKS4a extension.
imroc
left a comment
There was a problem hiding this comment.
Automated review (PR Review Loop):
Scope: Adds SOCKS4 and SOCKS4a proxy support (fixes #473). Files: internal/socks/client.go, internal/socks/socks.go, internal/socks/socks4_test.go (new, 543 lines), internal/transport/option.go, transport.go, proxy_socks4_test.go (new, 237 lines).
Verification:
- Checked out the PR branch locally:
go build ./...clean,go test ./... -count=1 -shortall 22 packages pass (includinginternal/sockswith the new SOCKS4 tests). - CI passes on both Go 1.25.x and 1.26.x.
Design review:
- SOCKS4 protocol implementation (
connect4) — correct wire format:VN(4) | CD | DSTPORT(2) | DSTIP(4) | USERID | NULL [| DOMAIN | NULL]. SOCKS4a uses the invalid IP0.0.0.1and appends the domain after the userid null terminator. Matches the SOCKS4 and SOCKS4a protocol specs. - SOCKS4a domain validation — rejects empty domains, NUL-embedded strings (
validateSocks4CString), and domains >255 chars. IPv6 destinations are rejected before writing. Solid input validation. - Local DNS for socks4 — when
Socks4Ais false and the host is a domain, resolves locally to IPv4 vianet.DefaultResolver.LookupIP. Correct. - Reply parsing — accepts VN of 0 (spec) or 4 (some servers incorrectly echo 4). Pragmatic tolerance. Reply codes (90-93) mapped to descriptive strings.
- Refactor of connect5 — the SOCKS5 path was cleanly extracted from the original combined
connectmethod. Error handling was changed from named returns (ctxErr) to expliciterrreturns. No behavioral change to the SOCKS5 path.
Modified stdlib file (transport.go):
transport.gocarries Go Authors copyright but req has heavily customized it (dump, middleware, etc.). The PR adds asocks4/socks4acase branch indialConnand entries inportMap— this is req-specific customization (Go stdlib has no SOCKS4 support), not upstream net/http logic. No conflict with future upstream syncs.- The
dialConnaddition maps the proxy URL username to the SOCKS4 user ID. Password from the URL userinfo is ignored (SOCKS4 has no password field), which is the correct behavior. connectMethod.scheme()and comments updated to list the new schemes. Documentation is thorough.
Test coverage: Comprehensive — 14 tests in socks4_test.go covering IPv4 connect, SOCKS4a domain, empty userid, rejection, IPv6 rejection, NUL validation, domain too long, local resolve, DialWithConn, reply version tolerance, unexpected version, relay HTTP, reply string. 3 E2E tests in proxy_socks4_test.go covering socks4 and socks4a through the full Transport stack. No existing tests deleted or weakened.
No blockers. Approving.
Summary
Adds SOCKS4 and SOCKS4a proxy support so
reqcan tunnel HTTP(S) traffic through SOCKS4 proxies, in addition to the existing HTTP and SOCKS5 schemes.Fixes #473 #473
Changes
internal/sockswith a SOCKS4/SOCKS4a CONNECT client (IPv4-only).socks4://andsocks4a://intoTransport(default port1080).socks4: resolve domain names locally to IPv4 before the handshake.socks4a: send the domain name to the proxy via the SOCKS4a extension.SetProxy/ transport options.Usage