Files
alksocks/docs/architecture/decisions/003-udp-handle-swap.md
T
glm-5.3-flash e196ed43e3 docs: remove em dashes from repo prose and forbid them going forward
- Rewrote em dash prose across the whole tree outside docs/reviews/
  (the historical reviews stay verbatim, matching the alktunnels and
  alktty precedent): src doc comments and inline comments (587),
  docs/architecture (319), docs/research + sdd_process (484), tests
  (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77),
  AGENTS.md (85), and the .opencode/agents prompts (109)
- Fenced content follows the precedent: comments, ASCII-diagram label
  separators, and template placeholders rewritten; wire data, JSON/BAST
  documents, byte diagrams, and transcripts survive verbatim; assert,
  expect, panic, error-display, and format message literals rewritten
  as prose; no code, fixture, or test data change
- src/rfc1928/LICENSE: the two dashes in alksocks' own provenance
  header rewritten; the MIT legal text untouched
- AGENTS.md adds Project Convention 18: no em dashes in prose; varied
  restructure, no spaced-hyphen substitutes, fenced prose rule, en
  dash numeric ranges exempt, docs/reviews/ exempt from the greps
- tasks/*.md frontmatter: the name values rewritten by the sweep are
  now quoted (a raw colon inside an unquoted YAML scalar parsed as
  null); taskgraph validate reports 37 tasks, no issues

Verification: cargo test; cargo clippy --all-targets -D warnings;
cargo fmt --check; cargo doc --no-deps; fuzz corpus replay
(cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
the repo returns matches only in docs/reviews/ and the bast.md BAST
JSON data fence
2026-11-03 19:31:53 +00:00

4.6 KiB

ADR-003: The Handle Swap: one Channel per Association, Frames In-Band

Status

Accepted (2026-09-20, Phase 1; baseline settled 2026-09-13, POC #2 validated end to end 2026-09-14)

Context

UDP ASSOCIATE is the hard case for the no-bind requirement: RFC 1928 replies with a UDP relay address (BND.ADDR/BND.PORT) and the client sends datagrams to it, which seems to demand a producer-side kernel bind, the one thing the protocol layer must never do.

The design's load-bearing reading of RFC 1928 (OQ-SK-03, settled 2026-09-13): the ASSOCIATE reply's BND.ADDR/BND.PORT is just the handle for the relay, and one relay serves the whole association: the per-datagram destination is carried in the datagram header (ATYP/addr/port), not in the handle. The relay port was never "a port that goes to one place"; it is a multiplexing endpoint. Swapping the handle (channel instead of UDP socket) preserves every RFC property. POC #2 validated the whole shape end to end, both egress variants, including the front door.

Decision

The in-band (channels-native) UDP ASSOCIATE shape is:

  • One channel per association: the CONNECT shape; no second channel, no correlation params (the op's params stay trivial, ADR-001).
  • Per-datagram destinations ride in-band frames: every datagram is [len: u16 BE][addr + data], the boundary-preserving length codec shaped on alktunnels' (frame_datagram/ DatagramReader, re-implemented in first-party src/wire.rs; behavior pinned by the same F-2 tests) over the RFC address encoding (TargetAddr::to_be_bytes(), the RFC UDP header minus RSV/FRAG). The RFC header codec (new_udp_header/parse_udp_request) exists only at vanilla front-door boundaries. RSV/FRAG do not ride the tunnel wire; fragmentation is refused at front doors (FRAG ≠ 0 → refuse, the iroh-socks5 v1 posture; it cannot even be expressed in-band).
  • Association lifetime = channel lifetime: channel EOF is the end-of-life signal; no extra control vocabulary (POC #2 confirmed post-reply garbage is refused structurally: the frame's first byte is ATYP, so any post-reply control byte parses as a garbage ATYP and fails the association loud).
  • The reply's BND.ADDR/BND.PORT on the in-band path are pure RFC-compatibility surface (the sentinel 127.0.0.1:0, POC #2's shape). Only a front door with a real socket reports a real address.
  • Producer egress is the EgressFactory seam (ADR-004, the dial-callback shape): the protocol layer sees neither egress variant. The two assembly-layer variants (local socket; composed via the alktunnels direct op) are policy, not protocol.
  • The datagram stage is select!-driven with the egress readiness contract (next_reply is Pending when nothing is ready; an instantly-ready Ok(None) starves a biased select into livelock; POC #2's readiness findings are load-bearing API design for the crate). Framing is applied exactly once, by the wire layer: Egress::next_reply returns inner bytes; the producer's frame writer adds the single length prefix.
  • The consumer side mirrors the framing-once invariant, and its reader state persists. The associate session's send_datagram frames once; its recv_datagram's incremental reader state (mid-frame position + batched-datagram pending queue) persists across calls; a datagram split across channel chunks survives a caller-driven receive loop (POC #2: the first cut dropped the reader state per call; the 2-byte-prefix chunk test exposed it). This is part of the datagram stage's consumer-side surface this ADR owns.

Consequences

  • The no-bind requirement holds for all three commands: CONNECT and BIND need no socket at all; ASSOCIATE's "bind" is virtualized as the channel (the handle swap). Local binds remain optional assembly-layer shapes (front doors; the local egress variant).
  • The datagram framing is wire-stable once published; the one-way door is now closed with empirical ground under it (open cost ~0.8 ms per composed destination, measured; LRU + dedup pinned by test).
  • Boundary preservation is mandatory (empty datagram ≠ EOF; alktunnels F-2's lesson transfers directly; the frame shape meets it structurally). The BAST document for the frame is bast.md.

References

  • OQ-SK-03 (the full reasoning trail), OQ-SK-09 (fan-out bound; see ADR-010)
  • POC #2 poc-udp-associate-findings.md (the validation record + the readiness/framing/ownership findings)
  • alktunnels ADR-003 (the length codec + F-2), alktunnels ADR-007 (the direct op, the composed egress vehicle)
  • iroh-socks5 eval (docs/research/iroh-socks5-eval.md, the frame design prior art)