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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user