docs: phase-0 — UDP phase model vs alktty-style demux; fork-vs-reimplement collapses onto OQ-SK-04

- OQ-SK-03: RFC 1928 control/data never interleave (fast-socks5
  wait_on_tcp treats post-reply control bytes as garbage), so the
  alktty 5-stream demux solves a problem SOCKS5 doesn't have; phase
  model (pass-through CONNECT, len-prefixed datagrams post-reply)
  subsumes it; the demux's real advantage is an additive in-band
  stream-type vocabulary — noted as the honest one-way-door fork
- OQ-SK-04: no structural fork needed for the channels surface
  (router.rs interception shape covers CONNECT; run_udp_proxy_custom
  + reply_success + the pure UDP codec cover ASSOCIATE); the
  fork/wrap/reimplement decision is driven entirely by the wasm
  feature-gating outcome; full fork documented as least attractive
This commit is contained in:
2026-09-12 09:46:05 +00:00
parent 4c641ea9c0
commit 216a5e965e
+67 -9
View File
@@ -361,11 +361,43 @@ the datagram stage must ride a channel. Sub-questions:
sentinel-reply shape is fine and no virtualized relay address is
ever invented.
Hunch: wrapper-aware client halves ride the same-channel
length-prefixed-datagram shape; vanilla clients get UDP ASSOCIATE only
via the optional local backend. But this is the crate's largest
unknown — a POC candidate (OQ-SK-07 #2), decided by an ADR before the
first consumer (the datagram framing is wire-stable once published).
**Phase model vs alktty-style demux (2026-09-12 discussion).** An
alternative shape surfaced: alktty's logical demux (input/output/error/
control sub-streams inside one channel, type byte per chunk, up to the
u8 = 255 stream-type limit) applied to the SOCKS5 channel — a `data`
stream and a `udp-relay` stream type would carry the two phases
structurally. Analysis against RFC 1928's actual structure: **RFC 1928
is one command per connection, and control and data never interleave in
either direction.** CONNECT's control conversation ends at the reply,
after which the channel is a pure byte stream (alktunnels' 0 B
pass-through conclusion); ASSOCIATE's control stream goes silent after
the reply — fast-socks5's own `wait_on_tcp` (`src/server.rs:1195`)
treats any post-reply control byte as protocol garbage
(`UnexpectedUdpControlGarbage`). So the per-chunk type byte demuxes a
problem SOCKS5 doesn't have; a **phase model** (raw RFC conversation
until the reply, then a mode switch synchronized by the reply itself —
pass-through for CONNECT, `[len: u16 BE]` datagrams for ASSOCIATE)
subsumes it with no type byte and no sentinel address: wrapper-aware
clients know "post-reply = datagram mode," the same way they'd know
"relay stream = datagram mode" under the demux. What the demux genuinely
buys that the phase model does not: an **additive in-band vocabulary**
new stream types extend the wire additively (no format break), whereas
adding a post-reply phase later is a wire break. alktunnels accepted the
opposite ("no in-band control path, ever — the escape hatch is a new
ALPN, a wire change is not"); the demux reintroduces that vocabulary at
2 B/chunk (type byte + length prefix) on every datagram. The one-way-door
cost is real either way: demux bakes the vocabulary in before it has a
user; phase model keeps 0 B overhead on CONNECT and 2 B on ASSOCIATE
datagrams. Hunch: phase model (simpler, RFC-shaped); the extensibility
argument is the honest case for the demux if Phase 1 wants the
vocabulary. Decide via ADR with the first consumer in sight.
Hunch (datagram stage, unchanged): wrapper-aware client halves ride the
same-channel length-prefixed-datagram shape; vanilla clients get UDP
ASSOCIATE only via the optional local backend. But this is the crate's
largest unknown — a POC candidate (OQ-SK-07 #2), decided by an ADR
before the first consumer (the datagram framing is wire-stable once
published).
### OQ-SK-04: fast-socks5 wasm posture (blocks the wasm-clean invariant)
@@ -396,6 +428,30 @@ Option A upstream if confirmed. This OQ gates the wasm verification
command in AGENTS.md (the expected-failure note is already written
there).
**Fork vs reimplement vs wrap — collapses onto this OQ (2026-09-12
discussion).** The wrap surface was re-checked and **no structural fork
is needed** for the channels path: CONNECT rides the interception points
as-is — `read_command` → own dial → `reply_success``transfer` (the
`examples/router.rs` shape), with the producer's `BiStream` as `T` on
both sides of `transfer`. UDP is ours either way: `run_udp_proxy_custom`
takes a custom `transfer` closure for the relay half, `reply_success`
accepts any `SocketAddr` (the sentinel reply), and
`new_udp_header`/`parse_udp_request` are public, pure, and reusable.
The one hardwired piece — `run_udp_proxy`'s peer relay socket
(`udp_bind_random_port`, socket2-based, called inside the default
handler before `run_udp_proxy_custom` even runs) — is in the *default*
handler, not the protocol: the wrapper's custom closure never needs it.
So the fork-vs-reimplement decision is not about the channels surface
at all; it is driven by the wasm question above: **wrap (with the
upstream `net` feature-gate ask) if the gating lands cleanly;
reimplement if the gating turns out structural.** The reimplementation
is genuinely small — RFC 1928 over `tokio::io` generics is the
alktunnels-codec scale of code — and fast-socks5 would remain the
differential-test oracle for RFC edge cases (reply-code mapping,
domain addressing, fragmentation) either way. A full fork (divergent
copy we publish) is the least attractive branch: it duplicates the
maintenance without buying anything the two options above don't.
### OQ-SK-05: Target policy and egress scoping
SOCKS5 is arbitrary-egress by nature — the open gate plus the target
@@ -536,11 +592,13 @@ Candidate reading for the research specialist (to be expanded):
dialer hunch); decide in Phase 1 against the spec
- [ ] OQ-SK-02 (auth mapping) — posture drafted (identity seam
primary, in-band auth for the local backend); decide in Phase 1
- [ ] OQ-SK-03 (UDP ASSOCIATE) — the shape space written; resolve via
research + POC #2, ADR before the first consumer
- [ ] OQ-SK-03 (UDP ASSOCIATE) — the shape space written (including
the phase-model-vs-alktty-demux analysis); resolve via research
+ POC #2, ADR before the first consumer
- [ ] OQ-SK-04 (fast-socks5 wasm) — verify empirically (POC #4);
likely an upstream feature-gating ask; file early per AGENTS.md
convention 17
the fork-vs-reimplement-vs-wrap decision collapses onto this
OQ; likely an upstream feature-gating ask; file early per
AGENTS.md convention 17
- [ ] OQ-SK-05 (target policy) — folded into the OQ-SK-01 decision;
scope-gate convention pinned in Phase 1
- [ ] OQ-SK-06 (noq client) — research pass (trait-shape comparison,