docs: resolve OQ-TN-03 via hub-owns-the-connection model
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.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user