docs: capture tun2proxy udpgw prior art in phase 0 findings

Documents the previously-undocumented UDP gateway prior art discussed
with the alknet-channels POC agent (/workspace/tun2proxy/src/udpgw.rs):
per-datagram length framing over a stream (LEN|FLAGS|CONN_ID|[SOCKS5
addr]|DATA), SOCKS5 ATYP addressing in-band, CONN_ID flow multiplexing,
keepalive/ERR flag packets, MTU cap, idle expiry. Folds implications
into OQ-TN-01 (addressing fork: per-channel vs per-datagram), OQ-TN-02
(framing mechanics production-proven; remaining fork documented),
OQ-TN-07 (single-ALPN option strengthened), OQ-TN-09 (flag-packet
vocabulary maps to establishment/error frame question), OQ-TN-10 (UDP
POC scope narrowed to channels-layer fit), and the survey checklist.
Also records the transport story: TCP vs QUIC is the alkcall layer's
concern; the tunnel crate is transport-agnostic.
This commit is contained in:
2026-09-05 20:07:15 +00:00
parent c2d5cbbbd9
commit 7e387ac05f
+107 -14
View File
@@ -19,6 +19,13 @@ channel structure, alktunnels generalizes the tunnel handler shape to
arbitrary bidirectional tunnels in the `ssh -L` / `ssh -D` sense — TCP, UDP,
unix sockets, and other stream or datagram substrates.
The 2026-09-05 first revision adds the tun2proxy UDP gateway prior art
(`OQ-TN-01`, `OQ-TN-02`, `OQ-TN-07`, `OQ-TN-10`) and the transport story
clarification (TCP vs QUIC at the channels layer, invisible to this crate —
§What is already settled). This prior art was discussed with the POC agent
around the alknet-channels POC but never documented; it is captured here so
it survives into the spec.
## What is already settled
The foundation is POC-validated and ADR-pinned; this crate is not starting
@@ -50,6 +57,65 @@ from zero. It inherits:
- **The relay story** — tunnels traverse alkcall hub relays
transparently via byte-for-byte data-channel forwarding with ID
rewrite (alkcall ADR-042). No tunnel-specific relay work.
- **The transport story** — the underlying channels transport is TCP or
QUIC (QUIC preferred) but that is the alkcall layer's concern, not
this crate's: the tunnel protocol sees a `BiStream` and is
transport-agnostic like the sibling crates. UDP tunneling (below)
rides the same chunk stream; the stream-vs-datagram question is
about what the tunnel protocol frames *inside* the channel, not
about the transport.
## Prior art: tun2proxy UDP gateway (`OQ-TN-01`, `OQ-TN-02`, `OQ-TN-07`, `OQ-TN-10`)
`/workspace/tun2proxy/src/udpgw.rs` implements a UDP gateway over a TCP
stream — structurally the same problem this crate faces for UDP tunnels
over channels data channels. Discussed with the alknet-channels POC
agent as the example of "UDP over a stream substrate," but never
documented. Key mechanics, all of which generalize:
- **Per-datagram length framing over the stream** — the packet format is
`LEN(u16 BE) | FLAGS(u8) | CONN_ID(u16) | [SOCKS5 address] | DATA`
(`udpgw.rs:82-88`). Boundary preservation is re-added by the protocol,
not by the substrate: exactly the "length-prefix each datagram inside
the channel" half-answer in OQ-TN-02, proven in production.
- **SOCKS5 address format travels per data packet** — `ATYP`
(0x01 IPv4 / 0x03 domain / 0x04 IPv6) + variable address + port
(`udpgw.rs:68-76`). This is concrete prior art for OQ-TN-01's
addressing: a scheme-tagged addressing encoding with v4/v6/domain
coverage already standardized. Note the asymmetry with TCP tunnels:
for UDP, the remote endpoint is per-datagram, not per-channel.
- **One stream carries many UDP flows** — `CONN_ID(u16)` multiplexes
associations over a single gateway connection (`udpgw.rs:66`),
with `keepalive` (0x01) and `error` (0x20) flag packets as the only
non-data frame types (`udpgw.rs:21-26`). This is the "one channel =
one association, per-endpoint multiplexing inside" half-answer in
OQ-TN-02, with the refinement that the per-endpoint multiplexing key
(`CONN_ID`) is protocol-level, allocated by the client, u16.
- **Flow lifecycle is packet-level** — `udp_timeout` idle expiry,
`keepalive_time` heartbeats on idle connections
(`UDPGW_KEEPALIVE_TIME = 30s`, `udpgw.rs:16`), and an MTU cap
(`parse_udp_response` rejects `data.len() > udp_mtu`,
`udpgw.rs:527`). Also `UDPGW_MAX_CONNECTIONS = 5` pooled gateway
connections *above* the packet layer — a throughput choice, not a
protocol requirement; channels gives us N channels already.
- **Implications for alktunnels:**
- UDP boundary preservation over the chunk stream is validated
prior art, not speculation — raises confidence in the OQ-TN-02
half-answer considerably.
- The frame-type set (DATA/KEEPALIVE/ERR) is a useful minimal
vocabulary — it maps onto OQ-TN-09's establishment/error frame
question (tun2proxy uses flag packets, alktty uses typed control
chunks; both are self-contained frames inside the data stream).
- Per-datagram addressing (SOCKS5-style) vs per-channel addressing
(fixed target at open) is a real fork for the params design: TCP
tunnels fix the target at open; UDP associations may either fix
one endpoint at open or carry per-datagram addresses like udpgw.
This interaction is unresolved and feeds OQ-TN-01 + OQ-TN-07.
- NAT/keepalive concerns partially disappear on channels: the
underlying transport (QUIC/TCP) handles connection keepalive, and
channel liveness is the channels layer's concern. The tunnel
protocol likely needs only flow-level idle expiry, not
transport-level keepalive packets — TBD in the spec.
## Open Questions
@@ -87,7 +153,11 @@ Considerations:
**Status:** open — research needed. Half-answer (hunch): a scheme-tagged
JSON object rather than a URL-ish string, so params stay typed and
extensible; exact shape TBD.
extensible; exact shape TBD. Strengthened 2026-09-05: the tun2proxy UDP
gateway (§Prior art) validates SOCKS5 ATYP addressing (v4/v6/domain) as
in-band prior art, and surfaces a fork — for UDP, remote addressing is
per-datagram (SOCKS5-style) rather than fixed-at-open like TCP. The
params design must account for both modes.
### OQ-TN-02: Datagram substrates (UDP) — boundary preservation
@@ -111,12 +181,16 @@ re-chunked arbitrarily)?
"association" carries many remote endpoints — does one tunnel channel
carry one endpoint or many, and how are per-endpoint replies routed?
**Status:** open — research needed (survey SSH/russh/SOCKS5/tun2proxy
approaches; likely a targeted POC if boundary preservation is chosen).
Half-answer (hunch): length-prefix each datagram inside the channel
(boundary-preserving), and one channel = one association with per-endpoint
multiplexing inside, mirroring SOCKS5 UDP — but this is exactly the kind
of guess that needs survey + POC before it becomes an ADR.
**Status:** open — survey mostly resolved by tun2proxy prior art
(§Prior art): per-datagram length framing over the stream is
production-proven (`LEN | FLAGS | CONN_ID | [addr] | DATA`), one
stream carries many UDP flows via a protocol-level `CONN_ID`, and
flow lifecycle (idle timeout + keepalive) is packet-level. Remaining:
whether alktunnels fixes the UDP endpoint at open (per-channel, TCP-
like) or carries per-datagram addresses (udpgw-like), and whether a
u16 conn-id vocabulary is right for channels (vs the channel ID
itself doing the demux and one channel per UDP flow). A targeted POC
(OQ-TN-10 #1) is likely still +EV for the chosen shape.
### OQ-TN-03: Direction semantics (`-L` / `-R` / dynamic)
@@ -237,7 +311,13 @@ consumer — ALPN strings are wire-stable once published.
substrate enum).
**Status:** open — needs the OQ-TN-02 outcome first (if datagrams need
different framing, option B gets stronger).
different framing, option B gets stronger). Note from the tun2proxy
prior art (§Prior art): udpgw runs its packet framing over a plain TCP
stream — one framing covers both the stream and datagram cases there.
If alktunnels follows the same shape (datagram framing self-describing
inside the channel), option A (single `alk/tunnel` ALPN) stays viable
even with UDP support; option B remains cleaner if the datagram
channel needs structurally different framing from the first chunk on.
### OQ-TN-08: Access control and ownership scope
@@ -292,7 +372,11 @@ Phase 0 may need (in rough priority order, per the SDD process's
1. **UDP tunnel POC** — boundary-preserving length framing over a
channels channel, per-endpoint multiplexing inside one association,
backpressure behavior. Derisks OQ-TN-02 (and OQ-TN-07's option B).
backpressure behavior. Partially derisked by the tun2proxy prior
art (§Prior art) — the POC now mainly validates *channels-layer*
fit: chunk-size vs datagram-size interaction, MTU cap against the
channels bounded buffers, idle-expiry mapping, and the chosen
endpoint-addressing shape. Derisks OQ-TN-02 (and OQ-TN-07's option B).
2. **Reverse-flow POC**`-R`-style: the accept side listens, the far
side carries. Derisks OQ-TN-03's advertisement/lifecycle shape.
3. **Unix socket + stdio bridge POC** — cheap; validates "substrate
@@ -315,8 +399,12 @@ Candidate reading for the research specialist (to be expanded):
- SOCKS5 (RFC 1928): addressing (ATYP), UDP ASSOCIATE framing,
per-endpoint multiplexing — the closest standardized "arbitrary
tunnel + UDP" model.
- tun2proxy (`/workspace/tun2proxy`): UDP-over-TCP tunnel framing in
production; also handles DNS over tunnel.
- tun2proxy (`/workspace/tun2proxy`, `src/udpgw.rs`): UDP gateway over
TCP — per-datagram length framing, SOCKS5 per-datagram addressing,
CONN_ID flow multiplexing, keepalive/ERR flag packets, MTU cap,
idle expiry. Analyzed in §Prior art. Its `socks.rs` /
`proxy_handler.rs` are also relevant for the `-D` (dynamic/SOCKS)
composition question (OQ-TN-03).
- quinn-proxy-poc (`/workspace/quinn-proxy-poc`) and iroh
(`/workspace/iroh`): datagram-native transports; how they model
per-endpoint flows.
@@ -331,10 +419,15 @@ Candidate reading for the research specialist (to be expanded):
## Convergence checklist (what Phase 0 must produce)
- [ ] Survey notes: SSH/SOCKS5/tun2proxy addressing + UDP framing
(OQ-TN-01, OQ-TN-02)
- [ ] Recommendation: addressing format sketch (OQ-TN-01)
(OQ-TN-01, OQ-TN-02) — tun2proxy UDP gateway done (§Prior art);
SSH/SOCKS5 addressing survey still open
- [ ] Recommendation: addressing format sketch (OQ-TN-01) — SOCKS5
ATYP validated as in-band encoding prior art; per-channel vs
per-datagram fork unresolved
- [ ] Recommendation: datagram strategy (OQ-TN-02) + ALPN strategy
dependent on it (OQ-TN-07)
dependent on it (OQ-TN-07) — framing mechanics de-risked by
tun2proxy prior art; endpoint-at-open vs per-datagram addressing
fork remains
- [ ] Recommendation: direction model (-L/-R/-D) (OQ-TN-03) + API
surface sketch satisfying no-forced-binding (OQ-TN-04)
- [ ] Decision input: backend trait vs no-trait (OQ-TN-05)