From 4df3c618d4ceaa31f2095f76c29b134fdd2520e5 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Sat, 5 Sep 2026 20:24:13 +0000 Subject: [PATCH] docs: resolve OQ-TN-03 via hub-owns-the-connection model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/research/phase-0-findings.md | 140 ++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 17 deletions(-) diff --git a/docs/research/phase-0-findings.md b/docs/research/phase-0-findings.md index fe62596..0953834 100644 --- a/docs/research/phase-0-findings.md +++ b/docs/research/phase-0-findings.md @@ -65,6 +65,71 @@ from zero. It inherits: about what the tunnel protocol frames *inside* the channel, not about the transport. +## Prior art: the hub-owns-the-connection model (`OQ-TN-01`, `OQ-TN-03`, `OQ-TN-04`, `OQ-TN-08`) + +Discussed 2026-09-05. The standing stance across the alk* crates: **the +hub owns the connection and explicitly proxies to expose resources for +others.** A produced resource is not a socket — it is an ACL-scoped +*virtual resource* on the side that can reach the real target. Applied +to tunnels, this dissolves the SSH direction model: + +- **Role follows the resource.** Whoever can reach the target is the + producer (registers openable tunnel channels via + `ChannelCore::register_openable`, dials/serves the target); whoever + wants the bytes is the consumer (opens channels via `ChannelClient`). + This holds for both SSH `-L` and `-R`: in both, the entry-point side + opens the channel (consumer) and the target side handles the open + (producer) — the only difference is which machine hosts the entry + point, which is assembly-layer wiring, not protocol. +- **The "exposed port" is a virtual port.** The far side may *think* it + exposed a port on the remote end; what actually exists is an + ACL-scoped resource in the connection/identity registry — the same + shape as operation registration, not a bind. Example: a self-hosted + gitea (HTTP in a docker container, really binding a port) is tunneled + to the hub; the hub proxies the tunnel to consumers whose ACL grants + access to the gitea HTTP service. A SOCKS5 service is the same shape, + further downstream. +- **The hub proxy is a producer wrapping a consumer.** The proxy is an + assembly-layer construct. On leg 1 (gitea side ↔ hub): gitea side is + producer, hub is consumer. On leg 2 (hub ↔ service consumer): the hub + re-produces the resource ACL-scoped — producer wrapping its leg-1 + consumer role. Data path per hop is the same two-pump shape; the + consumer opens a channel naming the resource, the producer's open + handler dials the target. This means the hub *terminates and + re-produces* rather than relaying byte-for-byte — unlike alkcall + ADR-042's transparent data-channel relay — because ACL must apply + per hop. (ADR-042 relays remain valid for transports that don't + need per-hop ACL; the hub's tunnel proxy is the explicit-ACL path.) +- **`-D` (dynamic/SOCKS) composes.** A SOCKS5 server at some assembly + layer is just a consumer that opens channels with per-connection + dynamic targets, gated by the producer's target policy. Not a base + crate concern (same conclusion as the original half-answer, now with + the mechanism named). +- **No forced binding, everywhere (OQ-TN-04 resolved).** Binding is + always assembly-layer and optional, on either side. The protocol + never binds: a producer "produces a resource" (which may or may not + correspond to a local bind — a docker container's port, a unix + socket, an in-process service); a consumer "consumes it." The SSH + mental image of "expose a port" is an illusion the assembly layer + may create locally; the protocol only ever carries + produce/consume bookkeeping. + +**Model, one sentence:** a producer produces a resource (a stream of +basically anything, if TCP and UDP are supported); a consumer consumes +it; direction, bind, and ACL scoping are assembly-layer concerns +wrapped around that pair. + +**Residue (folded into other OQs):** +- How does a consumer *discover* produced resources — call-level + listing op, out-of-band config, or resource names? → OQ-TN-01 + (params/resource naming). +- Dynamic-target policy for `-D`-style opens (which identities may open + channels to which targets) → OQ-TN-08. +- Whether the hub proxy needs anything from this crate (a + re-produce helper? typed open-handler composition?) or composes from + the public producer/consumer surface as-is → spec question for + Phase 1, tracked here as part of OQ-TN-05. + ## 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 @@ -157,7 +222,13 @@ 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. +params design must account for both modes. Reframed 2026-09-05 by the +hub model (§Prior art: the hub-owns-the-connection model): `params` +names a *produced resource* (an ACL-scoped virtual resource), not a +raw socket spec — so the design must also decide resource naming +(stable names vs target addresses) and discovery (call-level listing +op vs out-of-band config). SOCKS5 ATYP remains the in-band transport +encoding candidate underneath the resource naming. ### OQ-TN-02: Datagram substrates (UDP) — boundary preservation @@ -189,11 +260,30 @@ 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. +itself doing the demux and one channel per UDP flow). Note (2026-09-05, +hub model §Prior art): if UDP resources are produced like any other +resource, endpoint-at-open aligns naturally with resource naming +(OQ-TN-01); per-datagram addressing matches the `-D`/dynamic-target +composition path instead. A targeted POC (OQ-TN-10 #1) is likely still ++EV for the chosen shape. ### OQ-TN-03: Direction semantics (`-L` / `-R` / dynamic) +**Status: resolved 2026-09-05** by the hub-owns-the-connection model +(§Prior art: the hub-owns-the-connection model). There is no +protocol-level direction: role follows the resource. Whoever can reach +the target is the producer (registers openable channels); whoever wants +the bytes is the consumer (opens channels). SSH `-L` and `-R` are the +same producer/consumer pair with the entry point on different machines — +assembly-layer wiring, not protocol. The "exposed port" is a virtual, +ACL-scoped resource on the producing side; the hub proxy is a producer +wrapping a consumer. `-D`/SOCKS composes as a consumer opening channels +with per-connection dynamic targets, gated by the producer's target +policy (OQ-TN-08) — not a base-crate concern. Original question retained +below for context. + +Considerations (original): + SSH has three forwarding flavors; the crate must model them without "server/client" framing: @@ -242,11 +332,12 @@ channel" without assuming either endpoint is a bound socket. Substrate modules (behind feature flags) own actual `bind()` calls; the protocol owns bookkeeping only. -**Status:** open — mostly a spec-level requirement to encode in the -architecture docs and API shapes rather than a research question. Half- -answer: already agreed as a requirement (AGENTS.md convention 9); what's -missing is the concrete API surface (who calls what to start a tunnel in -each mode). +**Status:** open — mostly resolved (2026-09-05, see §Prior art: the +hub-owns-the-connection model): binding is always assembly-layer and +optional, on either side; the protocol never binds. What remains is the +concrete API surface — who calls what to start a tunnel in each mode +(produce-with-dial, produce-without-dial/accept-style, consume). This is +now a spec-shape task, not a research question. ### OQ-TN-05: Backend inversion point — is there a `TunnelBackend` trait? @@ -265,6 +356,12 @@ so the question: listener variant. - Backpressure/limits come from channels (AGENTS.md convention 10); the backend trait must not add a second layer of them. +- New sub-question from the hub model (2026-09-05, §Prior art): the + hub's tunnel proxy *re-produces* a resource it consumes (producer + wrapping a consumer). Does the proxy need a composition helper from + this crate, or does it assemble from the public producer/consumer + surface as-is? If a helper is warranted, it may share shape with the + substrate dial/listen trait — which would argue for the trait. **Status:** open — needs a survey of what backends would actually implement (local TCP? docker exec? ssh -w?) before deciding trait vs @@ -339,7 +436,14 @@ path gets `AccessControl` wiring for free via alktty scoped its gate. Half-answer (hunch): scope-gate for the open plus an open-handler-level target policy hook; ownership for *registered/listened* tunnels (which are persistent resources), not for -ephemeral dials. +ephemeral dials. Strengthened 2026-09-05 by the hub model (§Prior art): +the "virtual port" IS an ACL-scoped registered resource — so +`register_openable` per-resource registration is the primary gate +shape, and ownership naturally attaches to produced resources +(persistent), not to ephemeral dials. Remaining: the dynamic-target +policy hook for `-D`-style opens (which identities may open channels to +which targets when targets arrive per-channel), and whether that policy +lives in `AccessControl` or in the open handler's params validation. ### OQ-TN-09: Lifecycle, teardown, and error reporting @@ -423,14 +527,16 @@ Candidate reading for the research specialist (to be expanded): 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) — 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) + per-datagram fork unresolved; resource naming + discovery now + in scope (hub model) +- [x] Direction model resolved (OQ-TN-03) — hub-owns-the-connection + model: role follows the resource, no protocol-level direction; + `-D` composes at the assembly layer +- [x] No-forced-binding requirement encoded (OQ-TN-04) — binding is + always assembly-layer and optional; remaining work is the + concrete produce/consume API surface sketch (spec task) +- [ ] Decision input: backend trait vs no-trait (OQ-TN-05), now + including the hub re-produce composition question - [ ] Targeted POC(s) run + summary (OQ-TN-10) — UDP first, reverse flow second - [ ] Open questions promoted to Phase 1