Files
alksocks/docs/architecture/vendor.md
T
glm-5.3-flash 5b18d4d292 review: vendor extraction — gate opens; F-1 panic arm fixed
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.
2026-09-22 11:01:55 +00:00

11 KiB
Raw Blame History


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:

  1. 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's sock_addr as the reply#1 carrier; the reply#2 writer using the published new_reply).
  2. new_reply publication — the reply encoder becomes public (a reply(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 socks4 module is out entirely (upstream's separate default-off socks4 cargo feature, which POC #4 never compiled; out of RFC 1928 scope). SocksError's ReplySocks4Error variant (also #[cfg(feature = "socks4")] upstream) goes with it.
  • The legacy Config<A>/Authentication auth layer is out (ungated upstream, but consumed only by the cut legacy/net region — Socks5Server/Socks5Socket/the run_* handlers; the typestate's own accept_no_auth/accept_password_auth are the crate's in-band auth surface). With it go async-trait (its four upstream uses are all on this layer), the example auth impls (SimpleUserPassword/DenyAuthentication/AcceptAuthentication), and SocksError's AuthenticationRejected-consuming legacy plumbing that only they exercise.
  • Upstream log calls in the extracted region are dropped or re-pointed at the crate's tracing.
  • Upstream anyhow error wrapping (Context / Other(#[from] anyhow::Error)) is dropped — the crate's error surface is thiserror-typed; the vendored SocksError/SocksServerError stay 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 upstream unreachable!() arm for codes outside the RFC table maps unassigned codes (0x09–0xFF, RFC 1928 §6) to GeneralFailure instead — the value comes off the wire (Socks5Stream::read_request_reply feeds the raw reply byte there), so a misbehaving peer surfaces as a typed error, never a panic. (client.rs's use_password_auth unreachable!() and server.rs's reply_error unwrap were already translated by their tasks.)
  • The client Config derives Default instead of upstream's manual impl (upstream's manual impl only spelled the cut connect_timeout field), and request_header writes with write_all where upstream wrote with write (a partial write of a request header would corrupt the conversation; byte-identical on a healthy stream — clippy::unused_io_amount is 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-socks5 stays 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 + the new_reply finding), 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)