docs: correct fast-socks5 posture — third-party upstream, inline vendor decided

The ownership premise ('we own fast-socks5') was wrong — it is
third-party upstream and has not been forked. The corrected posture
resolves OQ-SK-04: inline vendor. The wrapper's needs exceed the wasm
gate regardless (BIND absent upstream — POC #5 proved the wrapper
drives it; the run_udp_proxy_custom bind seam; private new_reply), so
carrying the T-generic subset (~120 cfg lines, POC #4-verified) pays
for itself independent of the adapter story.

AGENTS.md:
- convention 17 rewritten: 'Upstream: alkcall is ours to shape;
  fast-socks5 is inlined' — the ownership split stated plainly, the
  inline vendor + its four carried changes named, the courtesy PR
  kept optional/never-waited-on, the vendor point (v1.0.0) noted
- convention 4: wasm posture note updated to the resolved vendor
- prior-art entry: fast-socks5 marked third-party/NOT ours

phase-0.md:
- OQ-SK-04 RESOLVED (2026-09-20): resolution note headers the OQ (the
  two settling facts: wrong ownership premise; needs exceed the gate);
  decision tree retained as reasoning trail with both legs annotated
  (Case 2's premise falsified by the BIND need); the root question
  reframed as retrospective; checklist item checked
- §Prior art fast-socks5: header + run_udp_proxy_custom caveat
  reworded from 'fileable ask' to in-tree change
- settled-foundations wasm caveat resolved; survey + checklist
  prior-art entries marked; 'What Phase 0 could NOT answer' wasm item
  struck as resolved; convergence Layering bullet now enumerates the
  four in-tree carried changes; upstream-asks bullet: fast-socks5
  items are no longer asks

poc-connect-wasm-findings.md / poc-bind-findings.md /
iroh-socks5-eval.md: 'we own it' ask sections corrected to
in-tree-vendor changes (dangling fragment from an earlier edit also
fixed); ledger item 3 annotated

Verified: fast-socks5 upstream still 1.0.0, no net feature, BIND
refused (lib.rs:384-386/server.rs:813-816) — the needs-are-real facts
stand. Docs-only change; no build surface affected.
This commit is contained in:
glm-5.3-flash committed 2026-09-20 09:59:12 +00:00
1 parent cb553ea033
commit cced3b2b85
5 files changed
+170 -90

No files matched your search

+3 -2
View File
@@ -230,7 +230,7 @@ the exact question OQ-SK-03 poses. Its answers:
| Client session genericity | `Socks5Stream::use_stream` (any `T`) | none (local TCP front door only) |
| wasm posture | fork verified minimal (POC #4) | moot — iroh + tokio-full are native-only |
| Tests | unit + examples; router/custom-auth examples | strong integration suite over real iroh infra |
| License | MIT (we own upstream) | MIT OR Apache-2.0 |
| License | MIT (third-party upstream; inline-vendored — OQ-SK-04) | MIT OR Apache-2.0 |
| LOC | ~4k | ~1.3k |
## Verdict
@@ -258,7 +258,8 @@ though fast-socks5 already covers them): MIT OR Apache-2.0 dual license,
copyright Matthew Geddes; keep LICENSE-MIT/LICENSE-APACHE entries and a
provenance note in any vendored file header.
Also worth filing upstream (courtesy, we own fast-socks5, but
Also worth noting (posture corrected 2026-09-20: fast-socks5 is
third-party upstream, so nothing is "filed" there as of right;
iroh-socks5 is upstream's own crate): nothing blocking. Optionally a
repository link on the crates.io entry would help discoverability — the
repo exists at `github.com/mattgeddes/iroh-socks5` but `repository` is
+109 -48
View File
@@ -1,9 +1,8 @@
---
status: draft
last_updated: 2026-09-20 (the graduation landed upstream: alkcall 0.8.0
— reply projection, flavor-form discovery, ADR-051 relay; alktunnels
0.2.0 — ADR-007 direct op + ADR-008 bind-first/forwarded, superseding
the BIND ask ledger's shape; ask-ledger statuses updated)
last_updated: 2026-09-20 (fast-socks5 posture corrected + resolved: we
do NOT own it — inline vendor decided; the graduation landed upstream:
alkcall 0.8.0, alktunnels 0.2.0 superseding the BIND ask ledger's shape)
---
# alksocks — Phase 0 (Exploration)
@@ -177,8 +176,10 @@ not start from zero. It inherits:
confined to feature-gated backend modules, injected at the assembly
layer (alktty `TtyBackend` / alktunnels no-trait precedent).
- **The wasm-clean default crate** — protocol-only code should compile
to `wasm32-unknown-unknown` (alktty/alktunnels precedent). Caveat:
`fast-socks5` itself is not yet known to be wasm-clean (OQ-SK-04).
to `wasm32-unknown-unknown` (alktty/alktunnels precedent). Caveat
resolved: `fast-socks5` itself is not wasm-clean as published
(OQ-SK-04) — the inline vendor (resolved 2026-09-20) carries the
net-gate so the default-crate invariant holds.
## Prior art
@@ -215,10 +216,12 @@ load-bearing conclusions this crate inherits:
the channels 8-byte header. UDP ASSOCIATE's datagram stage is the
exception (OQ-SK-03).
### fast-socks5 — the implementation to wrap
### fast-socks5 — the implementation to wrap (inline-vendor decision)
`/workspace/fast-socks5` (v1.0.0, MIT, we own upstream). Read the source
before designing against it. Key surface points, verified:
`/workspace/fast-socks5` (v1.0.0, MIT — **third-party upstream, not
ours; posture corrected 2026-09-20, OQ-SK-04 resolved to the inline
vendor — see the OQ**). Read the source before designing against it.
Key surface points, verified:
- **Explicit typestate server API** (`src/server.rs`):
`Socks5ServerProtocol<T, states::Opened|Authenticated|CommandRead>`,
@@ -248,9 +251,10 @@ before designing against it. Key surface points, verified:
the reply port from that kernel socket; the wrapper can customize
the relay loop (`transfer`) and the reply *IP* only. The channels
path therefore cannot use this handler: it drives the typestate
directly (`reply_success(sentinel_addr)` + own datagram stage). The
unconditional bind is a fileable upstream ask (we own fast-socks5):
make the bind optional or accept a caller-supplied socket.
directly (`reply_success(sentinel_addr)` + own datagram stage).
Under the inline vendor this is a change we make in-tree, not an
upstream ask: make the bind optional or accept a caller-supplied
socket.
- `transfer(inbound, outbound)` — plain two-pump copy; a channels
`BiStream` is a legal `T` on either side.
- **UDP support** — `new_udp_header(target)` / `parse_udp_request(buf)`
@@ -671,6 +675,20 @@ composed story. The datagram-framing ADR (the one-way door) is Phase
### OQ-SK-04: fast-socks5 wasm posture (blocks the wasm-clean invariant)
**RESOLVED 2026-09-20: inline vendor (Case 1, decided).** Two facts
settled it: (1) the ownership premise was wrong — we do *not* own
fast-socks5 (third-party upstream), so the "file asks / PR" legs of
the old decision tree lose their cheap path; and (2) the wrapper's
needs go beyond the wasm gate regardless — BIND is absent upstream
(POC #5 proved the wrapper must drive it itself), and the two seam
fixes (`run_udp_proxy_custom`'s unconditional bind; private
`new_reply`) were only "asks" under the ownership assumption. The
change set (net-gate + BIND + bind seam + `new_reply`) is exactly the
shape POC #4 measured as minimal (~120 cfg lines + additive additions
in the `T`-generic region), so the vendor pays for itself without
leaning on the adapter story. Mechanics in the Convergence §Layering
bullet; the decision record below is retained for the reasoning trail.
`fast-socks5` uses `tokio::net` (`TcpListener`, `TcpStream`,
`UdpSocket`) and `socket2` unconditionally (`Cargo.toml`: tokio features
`io-util, net, time, macros`; deps `socket2 = "0.5.8"`); `wasm32-
@@ -680,7 +698,9 @@ depending on the crate at all may break `cargo check --target
wasm32-unknown-unknown` at the dependency-graph level.
**The decision tree (2026-09-12 clarification — two main forks, and the
preference between them is conditional):**
preference between them was conditional). Retained as the reasoning
trail; both legs were written under the wrong ownership premise ("we
own upstream") — the resolved posture is the header note above:**
- **Case 1: wasm matters for this crate → fork-first.** Make the
minimal fork and use it — offer it upstream as a PR (merge if they
@@ -697,17 +717,26 @@ preference between them is conditional):**
checkout for differential testing of RFC edge cases (reply-code
mapping, domain addressing, fragmentation) and the PR source; if
upstream accepts the PR, the fork shrinks to a plain dependency.
(The vendoring shape is what was adopted 2026-09-20 — the PR is
optional courtesy, and "shrinks to a plain dependency" is moot since
the vendor also carries BIND + the seam fixes upstream lacks.)
- **Case 2: wasm doesn't matter for this crate → plain dependency, no
fork.** Ride the `router.rs` native path exactly as-is (the
alkhttp precedent — a crate in this suite can ship native-only).
This is strictly better *if its premise holds*: no fork burden, no
vendoring, upstream stays upstream. The alkhttp contrast is
instructive — alkhttp's substance (axum/hyper/reqwest) is
socket-native, so nobody misses wasm there.
socket-native, so nobody misses wasm there. (Superseded: Case 2's
premise — a wrap-only relationship — was falsified by the BIND +
seam needs; see the resolution note above.)
**The root question is therefore: does wasm make sense for alksocks?**
Not "how do we get wasm" — that is Case 1's solved problem. Analysis
for the decision:
**The root question was therefore: does wasm make sense for alksocks?**
(Retrospective note, 2026-09-20: the analysis below was written when
that was open; the ownership correction resolved the OQ ahead of the
adapter story — the vendor is justified by BIND + the seam fixes
alone. The adapter-story input below is retained as the
still-relevant product question for how much wasm surface to
exercise, not as a gate.) Analysis that was on file for the decision:
- **What wasm would serve:** the protocol layer (components 1+2 of the
three-component split, §Vision) in a sandboxed adapter — the
@@ -732,7 +761,7 @@ for the decision:
only an argument once Case 1 is chosen; it is not itself the use
case. The use case is a planned sandboxed SOCKS consumer.
**Decision inputs (what actually settles Case 1 vs Case 2):**
**Decision inputs (what was set to settle Case 1 vs Case 2):**
1. **Is there a planned sandboxed (wasm) consumer of the SOCKS5
service?** If the downstream TS/Python adapter story includes a
@@ -745,11 +774,22 @@ for the decision:
gains weight — reimplementing the protocol to serve a wasm story
nobody has yet defeats the purpose.
Both inputs point the same way when aligned: fork-first in Case 1 is
Both inputs pointed the same way when aligned: fork-first in Case 1 is
the preferred branch *because* wasm is wanted there — not as a general
preference for forks over upstream asks. If the adapter story never
materializes, Case 2 (plain dep, router path) is the obvious choice,
and no one should carry a fork for an invariant's sake.
materializes, Case 2 (plain dep, router path) was framed as the
obvious choice — and no one should carry a fork for an invariant's
sake. (The ownership correction below is what changed the balance.)
**The resolution (2026-09-20), restated:** the ownership correction
made "wait on upstream" legs of both cases unavailable, which pushes
every fast-socks5 change (wasm gate, BIND, bind seam, `new_reply`)
into code we carry — and once that set is carried, a plain-dep
Case 2 would mean wrapping a crate we can't change against needs it
doesn't serve (no BIND). The vendor is the only posture that fits all
four needs; POC #4 established the carry is small. Case 2's fallback
framing (alkhttp precedent) remains the record of what *would* have
been chosen had the crate been native-only with no BIND need.
### OQ-SK-05: Target policy and egress scoping
@@ -774,8 +814,8 @@ dissolving most of this OQ for that path):
- Domain-form targets (RFC 1928 ATYP 0x03): resolve producer-side
(fast-socks5's `dns_resolve` config), refuse, or pass through to the
dialer unresolved? (DNS-on-the-producer is the SSH `-D` semantic;
wasm producers may not have a resolver at all — another OQ-SK-04
interaction.)
wasm producers may not have a resolver at all — an interaction with
the vendored crate's wasm posture, OQ-SK-04 resolved 2026-09-20.)
**Virtual address space / identity-scoped subnets (added 2026-09-14,
post-POC #2).** A proposal surfaced during the findings review: give
@@ -1183,7 +1223,9 @@ Candidate reading for the research specialist (to be expanded):
(`Socks5Stream`/`Socks5Datagram`, `use_stream`), `src/lib.rs`
(`new_udp_header`/`parse_udp_request`, `ReplyError`),
`examples/router.rs` (conditional interception), `examples/
custom_auth_server.rs`.
custom_auth_server.rs`. (Third-party upstream; the inline vendor —
OQ-SK-04, resolved 2026-09-20 — vendors the `T`-generic subset into
this crate; this checkout is the differential-testing reference.)
- iroh-socks5 — `crates.io/crates/iroh-socks5` v0.5.0, repo
`github.com/mattgeddes/iroh-socks5` (crates.io has no repository
link; the published source matches). Evaluated in
@@ -1234,10 +1276,13 @@ Candidate reading for the research specialist (to be expanded):
- [ ] Vision + guiding principles captured (this doc, §Vision —
including the three-component split)
- [ ] Prior-art pass complete: fast-socks5 surface verified (§Prior
- [x] Prior-art pass complete: fast-socks5 surface verified (§Prior
art), alktunnels/alknet/noq lineage mapped, anti-prior-art list
written; iroh-socks5 evaluated (structural sibling, not the base;
OQ-SK-03 prior art — `docs/research/iroh-socks5-eval.md`)
OQ-SK-03 prior art — `docs/research/iroh-socks5-eval.md`);
**ownership premise corrected 2026-09-20** (fast-socks5 is
third-party upstream; the posture is the inline vendor, OQ-SK-04
resolved)
- [x] OQ-SK-01 (dial policy) — researched, half-answered (injected
dialer hunch); **POC #1 resolved the shape empirically** (dial
callback at the command-read interception point, boxed halves, no
@@ -1259,24 +1304,27 @@ Candidate reading for the research specialist (to be expanded):
POC-validated end to end, findings in
`poc-udp-associate-findings.md`; the datagram framing is
wire-stable once published — the Phase 1 ADR decision remains)**
- [ ] OQ-SK-04 (fast-socks5 wasm) — the decision tree is written
- [x] OQ-SK-04 (fast-socks5 wasm) — the decision tree was written
(Case 1: wasm wanted → fork-first; Case 2: wasm unwanted →
plain dep, router path); **POC #4 verified the fork is genuinely
minimal** (~120 lines of cfg gating; wasm-clean subset is
functionally complete); the root question ("does wasm make
sense for alksocks?") now reduces to the adapter-story input
functionally complete); **resolved 2026-09-20: inline vendor**
(the ownership premise was corrected — we don't own fast-socks5;
BIND + the seam fixes must be carried regardless, so the vendor
pays for itself independent of the adapter story — see the OQ's
resolution note)
- [ ] OQ-SK-05 (target policy) — folded into the OQ-SK-01 decision;
scope-gate convention pinned in Phase 1; the 2026-09-14
virtual-subnet proposal (dial-callback translation, no wire
change) recorded for Phase 1
- [ ] OQ-SK-08 (BIND) — new (2026-09-14): the gap + use case +
- [x] OQ-SK-08 (BIND) — new (2026-09-14): the gap + use case +
accept-side mechanism written; **POC #5 run 2026-09-15 —
passed** (the phase model extends end to end; both accept
variants implemented; the scope gate works; the ask ledger is
pinned); **the ask ledger landed 2026-09-18/20 — superseded in
shape by the bind-first/forwarded graduation (see OQ-SK-08's
landed note)**; the Phase 1 ADR (ship in base crate? default
accept variant?) has empirical ground under it
landed note)**; the remaining Phase 1 ADR (ship in base crate?
default accept variant?) has empirical ground under it
- [ ] OQ-SK-09 (channel capacity) — new (2026-09-14): the proxy-
workload analysis written (wire change rejected; config is the
mechanism; in-channel port multiplexing considered and
@@ -1349,21 +1397,30 @@ binds except the two explicit, optional, feature-gated assembly shapes
- **Layering: the alktty/alktunnels precedents** — substrate-agnostic
protocol modules at the crate core; feature-gated backend modules
(local binds) never imported by the protocol layer; producer +
consumer + params modules mirroring POC #1/#2's structure; the
fork posture for fast-socks5 is vendoring the `T`-generic subset
into this crate gated behind a `net`-shaped feature (POC #4:
~120 cfg lines, wasm-clean subset functionally complete).
consumer + params modules mirroring POC #1/#2's structure. **The
fast-socks5 posture is the inline vendor (resolved 2026-09-20,
OQ-SK-04)**: vendor the `T`-generic subset into this crate (the
codecs, typestate machinery, client `use_stream` path), gated
behind a default-on `net`-shaped feature (POC #4: ~120 cfg lines,
wasm-clean subset functionally complete) — and carry the four
wrapper needs in the same vendor: BIND (absent upstream; POC #5's
typestate-driven shape), the `run_udp_proxy_custom` bind seam, the
`new_reply` publication, and the wasm gate. `/workspace/fast-socks5`
stays the differential-testing reference; note the vendor point
(v1.0.0) in the vendored code.
- **Open for Phase 1 ADRs** (inputs collected, decisions pending):
the demux-vs-phase extensibility trade (evidence now favors the
phase model — no in-band control vocabulary was needed across both
POCs); the params-format/ALPN naming ADRs (wire-stable once
published, no consumers exist yet); `SOCKS5_OPEN_SCOPE` convention
(must distinguish BIND from CONNECT/ASSOCIATE — OQ-SK-08's security
note); the door's credential→identity mapping shape; the wasm
decision (Case 1 vs Case 2) once the adapter story is confirmed;
note); the door's credential→identity mapping shape;
BIND's base-crate inclusion + default accept variant (OQ-SK-08);
the proxy-workload channel-cap recommendation (OQ-SK-09); the
virtual-subnet translation-layer shape (OQ-SK-05 addendum).
(The wasm decision itself is settled — OQ-SK-04 resolved to the
inline vendor 2026-09-20; only the wasm-consumer adapter story
remains a product input, and it no longer gates the vendor.)
- **Upstream asks — landed (the first-real-consumer sweep paid off).**
The graduation (`alktunnels docs/research/tunnels-graduation.md`,
alkcall review 008) took every ask at once, in one-version bumps:
@@ -1380,22 +1437,26 @@ binds except the two explicit, optional, feature-gated assembly shapes
egress), the bind-first establisher + forwarded op (dissolving
both BIND asks in shape — see OQ-SK-08's landed note),
`TunnelListener`, and the `local` bind-first substrate half.
- **fast-socks5 (still open, courtesy)**: the `net` feature gate
(POC #4's ~120-line change), the `run_udp_proxy_custom`
unconditional-bind seam, and the `new_reply` publication (POC #5
re-encoded the reply wire form by hand). The BIND ledger never
had a fast-socks5 ask — the typestate fit with zero changes.
No asks remain blocking for this crate; the fast-socks5 items are
the courtesy tail (we own the upstream; POC #4's fork posture
covers them if wasm picks Case 1).
- **fast-socks5 — posture changed (2026-09-20): the items are no
longer asks.** The ownership premise was wrong (third-party
upstream, not ours), and the wrapper's needs exceed the wasm gate
(BIND absent upstream; the bind seam; private `new_reply`). All
four changes move into the inline vendor (OQ-SK-04 resolved —
see the Layering bullet); `/workspace/fast-socks5` remains the
reference checkout, and offering the net-gate upstream as a
courtesy PR stays optional.
### What Phase 0 could NOT answer (handed to Phase 1)
- The datagram-framing ADR (the one-way door) — now has empirical
ground: framing validated, LRU/dedup semantics pinned, open cost
measured. Decide with the first consumer in sight.
- The wasm root question (OQ-SK-04) — mechanism de-risked; the
adapter-story input is a product decision, not a research one.
- ~~The wasm root question (OQ-SK-04)~~ **resolved 2026-09-20** (the
inline vendor) — the mechanism is de-risked (POC #4) and the
posture no longer leans on the adapter story (the vendor pays for
itself via BIND + the seam fixes); the sandboxed-wasm-consumer
adapter story remains a product input for later, but nothing waits
on it.
- Target-policy shape (OQ-SK-05) — the dialer-refuses shape is
evidence-backed; the declarative-allowlist variant stays a Phase 1
choice. (The 2026-09-14 virtual-subnet proposal is recorded there —
+6 -4
View File
@@ -183,7 +183,8 @@ end-to-end (the same ownership lesson as POC #2's egress, mirrored).
writer hand-rolls the 10-byte reply (VER, REP, RSV, ATYP, addr,
port — 10 bytes v4 / 22 v6). Ledger ask #3 (fast-socks5, courtesy):
publish `new_reply` (or a `reply(code, addr)` constructor) so
embedders don't re-encode replies.
embedders don't re-encode replies. (2026-09-20: rides the inline
vendor — see §ledger statuses.)
- `ToTargetAddr::to_target_addr()` (not `From`) is the
`SocketAddr → TargetAddr` conversion — `TargetAddr::from` does not
exist (the POC hit this; the wrapper's accept layer will too).
@@ -227,9 +228,10 @@ both asks are **superseded in shape**, not implemented as posed:
3. **(fast-socks5, courtesy)** `new_reply` is private — publish it
(or a constructor) so embedders don't re-encode the reply wire
form. Also still open from POC #4: the `net` feature gate and the
`run_udp_proxy_custom` unconditional-bind seam. (Unchanged — the
courtesy tail; no fast-socks5 release has landed these as of
2026-09-20.)
`run_udp_proxy_custom` unconditional-bind seam. (Posture note,
2026-09-20: fast-socks5 is third-party upstream and these are no
longer asks — all three move into the inline vendor, OQ-SK-04
resolved.)
The POC's evidence stands as the record of what was validated against
alktunnels 0.1.0; the Phase 1 shape composes variant 2 against 0.2.0
+20 -7
View File
@@ -167,9 +167,18 @@ Remaining for Case 1: the fork posture is vendoring *into this crate*
(per phase-0: vendor the `T`-generic subset, gate the rest), keeping
`/workspace/fast-socks5` as the differential reference; the upstream
PR (feature-gate `net` behind a default-on feature) is small enough
to offer as-is.
to offer as-is. **(2026-09-20: resolved — the inline vendor is the
decision; the PR is an optional courtesy, never a wait. See phase-0
OQ-SK-04.)**
### Follow-on asks (upstream, we own it)
### Follow-on changes (posture corrected 2026-09-20: not upstream asks)
**Ownership correction:** fast-socks5 is third-party upstream — we do
not own it, and it has not been forked. These are therefore not asks;
they are changes the inline vendor carries in-tree (OQ-SK-04, resolved
2026-09-20). The wasm-gate change below is POC #4's; the other two
were recorded as "asks" under the wrong ownership premise and move
into the vendor with it.
1. **Feature-gate kernel-net behind a default-on `net` feature**
(POC #4's change, ~120 lines of `#[cfg]`).
@@ -178,7 +187,8 @@ to offer as-is.
customizable seam covers the relay closure and reply IP only; the
channels path must drive the typestate directly. Make the
`udp_bind_random_port` step optional / accept a caller-supplied
socket.
socket. (Both #2 and the POC #5 courtesy item — publish `new_reply`
— ride the vendor; see `poc-bind-findings.md` §ledger statuses.)
## OQ status after these POCs
@@ -192,8 +202,11 @@ to offer as-is.
propagation is a wiring requirement the producer harness performs.
In-band SOCKS auth was not exercised (NoAuth only); the RFC-facing
username/password path for the local backend remains Phase 1.
- **OQ-SK-04 (wasm)**: fork-minimality verified; the decision now
reduces purely to "does wasm make sense for alksocks" (the adapter
story), with the mechanism de-risked.
- **OQ-SK-04 (wasm)**: fork-minimality verified; the decision reduced
to "does wasm make sense for alksocks" (the adapter story), with
the mechanism de-risked. **(2026-09-20: resolved — inline vendor,
on the corrected ownership premise + the BIND/seam needs. See
phase-0 OQ-SK-04.)**
- **OQ-SK-07**: #1 and #4 complete; #2 (UDP ASSOCIATE) and #3 (noq)
remain.
remain. (Both resolved later: #2 passed 2026-09-14, #3 dropped with
the scope decision.)