17 tests pass (7 codec + 10 integration). The producer/consumer
shape, ADR-049 establishment integration, codec decision, and typed
establishment errors all validated over alkcall 0.5.0 channels.
Key findings folded into the ledger:
- OQ-TN-05 resolved for stream substrates: no backend trait —
pump_halves generic over boxed halves (TCP into_split, UDP
UdpHalf adapter); remaining thread is only the hub re-produce
composition question
- OQ-TN-06 convergence test satisfied: producer pump and consumer
pump are the same shape modulo channel side; extraction decidable
in Phase 1
- New finding for the alkcall ledger: OpenHandler JoinHandle
semantics are load-bearing and undocumented — a handler that
returns before the data plane starts tears the channel down at
birth (the POC found this by hanging; the pump must be awaited
inline). Plus: Establishment should carry the channel plan
(the reserved extension point) to kill the establisher handoff
side-channel
- UdpHalf truncation semantics flagged for the Phase 1 ADR
(recommend fail-loud over silent truncation)
POC crate: /workspace/alktunnels-udp-poc (db224cc).
Structural rationale: alktty's 5-byte header is a sub-demux key for
five logical streams sharing one BiStream; a tunnel has exactly one
data stream per direction, so no type byte. TCP needs no length
prefix (channels already length-prefixes every chunk). UDP needs
boundary re-framing only, and u16 suffices (UDP max payload 65507).
Wire overhead table: tunnel TCP = 8 B/chunk total (channels header
only), tunnel UDP = 10 B/datagram, vs alktty's 13 B. udpgw
calibration: 5 B + per-datagram address.
Trade-offs accepted: no in-band control path for TCP channels ever
(post-ADR-049 this is clean; escape hatch = new ALPN, not a wire
change); sentinel collision does not exist (UDP len=0 = empty
datagram, legal; EOF is the channels-level sentinel on the BiStream
at a different layer); future multi-endpoint UDP gateway framing
lives inside datagram payloads (udpgw precedent).
Residual for the Phase 1 codec ADR: empty-datagram semantics; u16 BE
over varint (leaned, MTU-bounded anyway).
Research specialist survey (docs/research/ssh-socks5-survey.md):
SSH forwarding model (direct-tcpip/forwarded-tcpip payloads, open-
failure reason codes, tcpip-forward registration lifecycle), SOCKS5
(ATYP, CONNECT, UDP ASSOCIATE), error-vocabulary comparison table,
endpoint-at-open vs per-datagram analysis, and an explicit anti-
prior-art list (SSH window updates, packet-size negotiation, dual
channel numbering, SOCKS5 FRAG/auth/BND semantics, udpgw
KEEPALIVE/pooling — all mechanisms channels already owns or the
substrate change obsoletes).
Key corrections to phase-0 findings:
- OQ-TN-02 category error fixed: SSH has NO UDP forwarding at all;
the earlier '-D UDP associate over SSH' line was wrong (SSH -D
carries only the SOCKS5 TCP control connection). Real UDP-over-
stream prior art: tun2proxy udpgw + SOCKS5's own UDP relay.
- OpenSSH -D is protocol-level indistinguishable from -L (one
direct-tcpip channel per SOCKS CONNECT) — direct confirmation of
the -D = 'tunnel a socks5 connection' resolution.
OQ ledger updates:
- OQ-TN-01: minimal params shape supported ({resource, substrate});
direct-streamlocal is the extensibility template; ATYP not needed
in base params
- OQ-TN-02: RESOLVED as split by path — endpoint-at-open for base UDP
resources; per-datagram addressing only inside the -D payload;
channel ID replaces CONN_ID (no second demux); KEEPALIVE drops
- OQ-TN-07: option A (single alk/tunnel ALPN) strengthened
- OQ-TN-09: reason-code vocabulary from SSH's four codes + detail
string; udpgw ERR bit flagged as the counterexample to avoid
- OQ-TN-10: reverse-flow POC gains the tcpip-forward template
- Checklist: SSH/SOCKS5 survey item checked off
Worktree/branch for repo-dependent POCs; standalone crate in the
global workspace for self-contained POCs (alknet-channels-poc
precedent); findings always land in docs/research/.
- OQ-TN-08 residue updated with the upstream posture: we own alkcall,
upstream asks happen early (alktty/alkhttp precedent — both required
upstream fixes/additions; only 3 downstream dependents now, each
resolved issue makes future dependents cheaper). Ops-listing
metadata is expected to be a small upstream alkcall change, not a
workaround.
- OQ-TN-09: control-frame hunch accepted (self-contained frame,
alktty ADR-006 shape; dial errors are channel-closing, byte EOFs
stay per-direction); frame vocabulary follows the alktty pattern;
concrete set = Phase 1 ADR.
- New prior art section: the alktty stream-splitting pattern
(split BiStream -> ChunkReader/ChunkWriter -> pump to handle halves)
proposed as the general tunnel pattern — 'tty without the five
stream types'. Comparison table (tty vs TCP vs UDP codecs/handles/
pumps); implications for OQ-TN-09 (ctrl frames via stream types),
OQ-TN-05 (handle = boxed AsyncRead/AsyncWrite halves; UDP flow
table is the real design surface), OQ-TN-06 (helper convergence
test against pump_session may be nearly free). Open: codec choice
(reuse 5-byte / stripped / raw + UDP framing).
- Checklist updated accordingly.
Untangles the 'host owns the resource and proxies on top' posture for
tunnels:
- Protocol-crate levels assume the resource is owned by the other side
of the connection; alkcall's existing op-level ACL applies as-is. No
new policy layer, target allowlists, or tunnel-specific ownership
machinery. Proxy/overlay is a downstream (assembly-layer) concern
(hub workers expose tunnels; hub overlays them per-ACL).
- Discovery resolved: openable channels are operations (alkcall
ADR-047), so the existing bidirectional ACL-filtered ops listing IS
tunnel-resource discovery. A consumer learns a socks5/postgres/redis
tunnel is available from the same listing it already uses for ops.
- -D simplifies to 'just tunnel a socks5 connection': socks5 server
lives on the producing side; target selection happens in the socks5
protocol, not tunnel params; the dynamic-target policy residue
dissolves (socks5 resource ACL governs reachability).
Residue for Phase 1: per-resource metadata in the ops listing
(substrate type, name/description) — an alkcall ADR-047 interaction,
not a new mechanism. OQ-TN-01 discovery residue and the hub-model
residue list cross-linked accordingly.
Four corrections from discussion:
- ALPN prefix: alknet/ -> alk/ swap happened in alkcall v0.1.1; docs
must not perpetuate the old prefix (consumers would bake it in)
- XY problem: except in the -D/dynamic composition case, params only
identify a produced resource + substrate discriminator (tcp/udp/
extensible); the producer owns where the resource comes from (most
likely a local port); UDP must be structurally supported even
though TCP dominates expected use
- self-contained JSON open-op object accepted as the params path
(alktty NegotiateRequest precedent)
- russh has no UDP channel support at all — no direct-udp prior art;
noted in OQ-TN-02 and the survey list; SOCKS5 relevance scoped to
the -D composition path
Residue for OQ-TN-01: resource naming shape, discovery mechanism,
exact JSON layout (Phase 1 ADR before first consumer).
Captures the standing hub stance as prior art: the hub owns the
connection and explicitly proxies to expose resources for others.
Role follows the resource — whoever reaches the target is the producer
(registers openable channels), whoever wants bytes is the consumer.
SSH -L/-R collapse to the same producer/consumer pair with the entry
point on different machines (assembly-layer wiring); the exposed port
is a virtual ACL-scoped resource (register_openable shape), not a
bind; the hub proxy is a producer wrapping a consumer, terminating
and re-producing per hop so ACL applies per hop (distinct from
ADR-042 transparent relay); -D composes as a consumer opening
channels with per-connection dynamic targets.
OQ-TN-03 resolved. OQ-TN-04 mostly resolved (binding always
assembly-layer, optional, either side). OQ-TN-01 reframed: params
name a produced resource — resource naming + discovery added. OQ-TN-08
strengthened: register_openable per-resource registration is the
primary gate; remaining residue is the dynamic-target policy hook.
OQ-TN-05 gains the hub re-produce composition sub-question. OQ-TN-02
cross-linked: endpoint-at-open aligns with resource naming; per-
datagram addressing matches the -D composition path.
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.
Captures the ten open design questions from the setup discussion:
addressing format, UDP datagram semantics, -L/-R/-D direction model,
no-forced-binding API surface, backend inversion point, two-pump
helper extraction, ALPN strategy, access control scope, lifecycle/
error reporting, and candidate targeted POCs. Includes the settled
foundation (POC-validated prior art), a prior-art survey list, and
the Phase 0 convergence checklist.