The extraction review against vendor.md's cut table, ADR-011
§Amendment, and AGENTS.md conventions. All five file diffs vs the
fast-socks5 v1.0.0 reference checkout read hunk-by-hunk and
categorized (cut / carried changes / adjustments / provenance /
tests); wasm, clippy, fmt, doc all green; RFC 1928 spot-checks
(reply form, method order, UDP zero fallback, domain cap, FRAG
split) independently verified against the RFC text.
F-1 (critical, resolved here): ReplyError::from_u8 kept upstream's
unreachable!("ReplyError code unsupported.") arm while being
wire-reachable — Socks5Stream::read_request_reply feeds the raw
reply byte from the wire into it, and RFC 1928 §6 marks 0x09-0xFF
unassigned, so a misbehaving peer would panic the consumer task.
Unassigned codes now map to GeneralFailure (the RFC's catch-all),
pinned by a new test, and the panic-translation adjustment is
recorded in vendor.md's adjustment list (with the Config-Default
and write_all notes) in the same change.
Findings F-2..F-4 (dead commented code dropped with its region;
Cargo.toml feature-layout comments; the preserved resolve_dns error
string) reviewed and accepted. Review task file carries the full
record; the gate to the downstream task groups is open.
Verified: cargo test (38 + 32), cargo test --all-features, clippy
--all-targets and --target wasm32-unknown-unknown (-D warnings),
cargo check --target wasm32-unknown-unknown, cargo fmt --check,
cargo doc --no-deps.
11 KiB
status: draft
last_updated: 2026-09-22 (extraction review F-1: ReplyError::from_u8's
panic arm mapped to GeneralFailure, panic-translation + Config/
write_all adjustments recorded — see the adjustment list; the
extraction amendment ADR-011 §Amendment: extraction not wholesale
copy, the net gate dissolves, two carried changes; Phase 1 initial
draft 2026-09-20)
alksocks — The Inline Vendor (fast-socks5 subset)
The crate owns its RFC 1928 protocol code in-tree: the inline
vendor of fast-socks5's T-generic subset ([ADR-011](decisions/
011-inline-vendor.md); OQ-SK-04's resolution). This document is the
vendor's scope, the carried changes, and the maintenance posture.
The protocol base decision is ADR-002; the feature gating is
ADR-009.
The Mechanism: Extraction, Not Wholesale Copy (ADR-011 §Amendment)
The vendor is an extraction: copy only the T-generic
inventory from fast-socks5 v1.0.0 into src/vendor/; the kernel-net
handlers are not copied — they stay in /workspace/fast-socks5
(the read-only reference checkout). Concretely: POC #4's cfg map
(its five-file inventory of what is kernel-net-gated) is the base of
the extraction recipe — copy what POC #4 left ungated, minus the
two recorded exclusions (the socks4 module and the legacy
Config/Authentication layer — see the cut table). The extraction
contains zero kernel-net code, so it is wasm-clean by construction;
there is no in-tree net-shaped feature gate (that part of POC #4's
change dissolves — ADR-011 §Amendment).
What is Extracted
From fast-socks5 v1.0.0 (MIT, third-party upstream —
/workspace/fast-socks5 is the read-only reference checkout; the
vendor point is noted in the extracted code):
| Piece | Why it rides | Notes |
|---|---|---|
The server typestate (Socks5ServerProtocol<T, states::*>, negotiate/auth/command-read/reply, accept_no_auth/finish_auth/accept_password_auth) |
the producer's conversation driver (ADR-002) | T-generic |
The auth traits (AuthMethod<T>/AuthMethodSuccessState<T>) |
the negotiation machinery (the crate uses accept_no_auth in-band; the traits stay for fidelity) |
T-generic |
transfer + wait_on_tcp |
the typestate's own two-pump seam (the crate's data plane is pump_bidi; transfer is the interception-point shape) |
T-generic |
The codecs: new_udp_header/parse_udp_request, TargetAddr/to_be_bytes/read_address, ReplyError/SocksError/SocksServerError |
the RFC wire vocabulary everywhere (front doors, associate frames, reply#2 reader) | pure; new_reply published in-tree (carried change) |
The client types: Socks5Stream<S>::use_stream + the AsyncRead/AsyncWrite impls |
the consumer half's stream session (ADR-002; POC #1's fit) — the first-party associate session in consumer.rs was informed by this shape |
T-generic |
| The non-net test modules (where extractable as unit tests) | RFC edge-case coverage rides in-tree | adjusted to the crate's test layout |
What is NOT Extracted (the cut — POC #4's gated inventory + two recipe additions)
These stay in /workspace/fast-socks5 (the differential-testing
reference); the cut table below records the boundary, so a future
re-vendor knows the cut. Rows 1–5 are POC #4's five-file cfg map
(what POC #4 gated is what is not extracted); the socks4 row and the
legacy Config/Authentication layer are this recipe's two
additions — the socks4 module is out of RFC 1928 scope, and the
legacy auth layer was left ungated by POC #4 (it compiles without
net) but no extraction consumer reaches it. None of it is copied:
| Upstream file | Region NOT in src/vendor/ |
|---|---|
src/server.rs |
run_tcp_proxy, run_udp_proxy/run_udp_proxy_custom, udp_bind_random_port, the legacy Socks5Server/Incoming/Socks5Socket (struct + impls), DnsResolveHelper trait + impl, handle_udp_*/transfer_udp, the net-only test module — plus the legacy Config<A> + Authentication trait + impls (SimpleUserPassword/DenyAuthentication/AcceptAuthentication, authenticate_callback), which POC #4 left ungated but which only this cut region consumes (rows 1–5 are POC #4's gate map; the Config/Authentication layer and the socks4 row are this recipe's two additions) |
src/client.rs |
Socks5Datagram (struct + impl), the Socks5Stream<TcpStream> convenience impl (connect/connect_with_password/connect_raw) |
src/util/stream.rs |
tcp_connect* fns (the read_exact!/ready! macros + ConnectError stay) |
src/util/target_addr.rs |
the resolve_dns method (the codec stays) |
src/lib.rs |
the net-heavy test module (consts/errors/UDP-header codec stay) |
src/socks4/ |
the whole module (upstream's separate default-off socks4 cargo feature; out of RFC 1928 scope) |
Why none of it is needed in-tree (ADR-011 §Amendment): no crate
consumer routes through the kernel-net region — the producer drives
the typestate directly (the ASSOCIATE path is the POC #2
typestate-driving shape; run_udp_proxy_custom is unusable on
channels regardless), the consumer sessions use use_stream plus
the first-party associate session, the doors are pure codecs +
first-party bridges, and the local backends are first-party tokio
code (ADR-008). The "producer-side local backend" is a bridge onto
the channels-served service (POC #1's door shape), not a standalone
vanilla server. If a future need surfaces (e.g. a standalone
vanilla-server example), the fix is re-vendor that piece then, as an
additive decision.
The Carried Changes (ADR-011, re-read under extraction)
The extraction's diff vs upstream v1.0.0 is deliberate and bounded — two changes touch extracted code; the other two of the original four dissolve with the kernel-net region:
- BIND — absent upstream (every handler refuses
Socks5Command::TCPBind). The crate drives the typestate itself for the BIND shape (ADR-007); the vendor carries the shape in-tree (reply_success'ssock_addras the reply#1 carrier; the reply#2 writer using the publishednew_reply). new_replypublication — the reply encoder becomes public (areply(code, addr)-shaped constructor) so the BIND reply#2 writer doesn't re-encode the 10/22-byte reply form.
Dissolved: the net feature gate (no net code in-tree) and the
run_udp_proxy_custom bind seam (the handler isn't extracted).
Plus the provenance notes: the vendor point (v1.0.0) and the license headers (MIT; preserve per-file headers or a bundled notice — ADR-011's license obligation).
Plus the extraction-time adjustments (not carried changes — mechanical edits to the copied code, ADR-011 §Amendment):
- The
socks4module is out entirely (upstream's separate default-offsocks4cargo feature, which POC #4 never compiled; out of RFC 1928 scope).SocksError'sReplySocks4Errorvariant (also#[cfg(feature = "socks4")]upstream) goes with it. - The legacy
Config<A>/Authenticationauth layer is out (ungated upstream, but consumed only by the cut legacy/net region —Socks5Server/Socks5Socket/therun_*handlers; the typestate's ownaccept_no_auth/accept_password_authare the crate's in-band auth surface). With it goasync-trait(its four upstream uses are all on this layer), the example auth impls (SimpleUserPassword/DenyAuthentication/AcceptAuthentication), andSocksError'sAuthenticationRejected-consuming legacy plumbing that only they exercise. - Upstream
logcalls in the extracted region are dropped or re-pointed at the crate'stracing. - Upstream
anyhowerror wrapping (Context/Other(#[from] anyhow::Error)) is dropped — the crate's error surface isthiserror-typed; the vendoredSocksError/SocksServerErrorstay the wire-level vocabulary. - The panic paths in the copied region are translated (AGENTS.md
convention 2 — no panics in library code; the extraction
post-dates the client task's summary, corrected by the extraction
review's F-1):
ReplyError::from_u8's upstreamunreachable!()arm for codes outside the RFC table maps unassigned codes (0x09–0xFF, RFC 1928 §6) toGeneralFailureinstead — the value comes off the wire (Socks5Stream::read_request_replyfeeds the raw reply byte there), so a misbehaving peer surfaces as a typed error, never a panic. (client.rs'suse_password_authunreachable!()andserver.rs'sreply_errorunwrapwere already translated by their tasks.) - The client
ConfigderivesDefaultinstead of upstream's manual impl (upstream's manual impl only spelled the cutconnect_timeoutfield), andrequest_headerwrites withwrite_allwhere upstream wrote withwrite(a partial write of a request header would corrupt the conversation; byte-identical on a healthy stream —clippy::unused_io_amountis deny-by-default). Both are recorded here so a future re-vendor re-applies them.
Maintenance Posture
- The vendor is first-class code, not a patch stack: the carried changes live in-tree and co-evolve with the wrapper; the extracted subset's RFC fidelity is the crate's own test suite's job.
- Differential testing:
/workspace/fast-socks5stays the reference for RFC edge cases (reply-code mapping, domain addressing, fragmentation). The crate's test suite covers the edge cases the extracted code must keep passing; when in doubt, diff behavior against the reference checkout. The kernel-net region lives there too — the reference checkout is the only place the full upstream surface exists. - Upstream posture: fast-socks5 is third-party; nothing waits on it. Offering a net-gate PR upstream is optional courtesy (POC #4: small enough to offer as-is) and unrelated to this crate's build — the extraction doesn't need it. A future upstream release may be re-based against deliberately, never absorbed silently (the extraction cut + carried changes would need re-verification).
- No second codec, ever: the vendored codecs are the only RFC wire vocabulary in the crate (ADR-011's rationale; the alknet anti-prior-art record is in phase-0 §Anti-prior-art).
Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| 002 | Protocol base | the explicit typestate API only |
| 009 | Feature gates | local, composed; the default crate is wasm-clean with no net feature (ADR-011 §Amendment) |
| 011 | The inline vendor | the extraction in-tree; two carried changes (BIND, new_reply); v1.0.0 noted |
Open Questions
Open questions are tracked in open-questions.md. None currently blocking this document.
References
- OQ-SK-04 (the resolved posture), OQ-SK-08 (BIND +
new_reply), OQ-SK-03 (the bind seam) - POC #4 (
poc-connect-wasm-findings.md— the measured gate), POC #5 (poc-bind-findings.md— the BIND driving + thenew_replyfinding), POC #2 (the typestate-driving shape for ASSOCIATE) /workspace/fast-socks5(v1.0.0, MIT — the reference checkout);docs/research/iroh-socks5-eval.md(why not the alternative base)