- README rewritten in alktty's shape: quick start (producer dial + listen, consumer open/reverse/pump), the role table, the resource-not-an-address model, the data-plane summary, features + wasm sections — examples mirror tests/harness.rs wiring - CHANGELOG.md added (0.1.0 entry, Keep a Changelog house style) - tasks/ added to the package exclude (was shipping 16 internal SDD task notes; package 50 -> 35 files) - docs/architecture spec docs: status draft -> implemented (Phase 2 complete, review 001 remediated) Verified: 71 tests default / 91 --features local, clippy -D warnings (all-targets + wasm32), fmt, cargo doc warning-free, wasm32-unknown-unknown check, publish dry-run (35 files, 406.2KiB)
8.2 KiB
status, last_updated
| status | last_updated |
|---|---|
| implemented | 2026-09-10 |
alktunnels — Overview
Arbitrary bidirectional tunnels over alkcall channels: TCP, UDP, unix
sockets, and other stream or datagram substrates — in the ssh -L /
ssh -D / ssh -R sense. A producer/consumer protocol crate riding
alkcall channels the same way alktty does (alk/tty is the sibling
precedent). This document covers the crate's purpose, the resource
model in brief, dependencies, the ALPN, and the module map; component
details are in the sibling documents (wire.md,
producer.md, consumer.md).
What
alktunnels registers the alk/tunnel ALPN's open op on alkcall
channels and provides the typed consumer session:
- The producer half (a
channels/tunnel/subopen op registered viaChannelCore::register_openable_with_establisher, alkcall ADR-047/049): the establisher validates params semantically (registry lookup of{resource, substrate}→ backing), dials (or accepts for) the substrate, and returns the dialed handle viaEstablishment::new(plan); the pump handler awaitsalkcall::channels::pump_bidiover the channel and the substrate halves (ADR-050, R-02's lifetime contract). - The consumer half (
TunnelSession, ADR-005): opens tunnel channels (ChannelClient::open_channelon the forward path; the reverse path's call + adopt equivalent), splits the channelBiStream, presents the substrate-shaped data plane (raw halves or datagram codec), and owns teardown (close/join/Drop).
The guiding insight (the OQ-TN-01 reframe):
A tunnel is a resource, not an address.
paramsidentify a produced resource + substrate; the producer owns the backing. Rich addressing (SOCKS5 ATYP, per-datagram remotes) enters only through the-D/dynamic composition path — inside the tunnel payload, never in the wire.
Why
The crate's purpose is to be the tunnel library for downstream consumers, the same role alktty plays for terminal sessions:
- A hub that runs agent workspaces exposes workers' services (postgres, redis, gitea HTTP) as named resources; a coordinator opens tunnels to them over alkcall channels — the VPN/SSH-tunnel service pattern without SSH.
- A worker behind NAT dials the hub and serves its own open ops
(the reverse flow,
ssh -R); the hub opens tunnel channels toward it per local accept. Both POCs validated this end-to-end. - A
-D-style consumer tunnels a SOCKS5 connection; the socks5 server (assembly layer) does dynamic target selection inside the tunnel payload. - The protocol layer is substrate-agnostic by construction — the bookkeeping (target addressing, direction, lifecycle) never hardcodes a substrate (AGENTS.md convention 7). TCP, UDP, unix sockets, and in-process pipes ride the same open op, pump shape, and session type.
The Resource Model in Brief
A tunnel channel's life (full detail in wire.md):
- Open (the establishment phase). The consumer calls
channels/tunnel/subwithparams = {resource, substrate}(ADR-001). The producer's establisher — the open op's awaited phase (alkcall ADR-049) — resolves the resource, dials the substrate, and returns the handle in the plan. Failure is a typedchannel:open_failed(dial_failed/unknown_resource/resource_shortage/handler_error/timeout); the channel never exists on the opener's side afterward. - Pump (the data plane). Two pumps, one per direction, via
pump_bidi(ADR-050): each shuts the opposite sink down on completion; half-close semantics fall out. Stream substrates ride raw pass-through (0 B tunnel overhead); UDP rides the mandatory[len: u16 BE]codec (ADR-003, F-2). - Teardown. The serving side is wrapper-managed (R-02); the
adopting side's
TunnelSessionowns teardown (ADR-005, W3). EOF sentinels are the channels layer's; the codec never collides with them.
Direction is not in the protocol: whoever can reach the target is the
producer; whoever wants the bytes is the consumer; connection
direction (who dialed the transport) is independent of tunnel
direction (OQ-TN-03's hub model). -L, -R, and -D are assembly
shapes over the same open op.
Dependencies
alktunnels (default — wasm-clean)
├── alkcall 0.7.0 (core types, channels: ChannelCore/ChannelClient/manager,
│ pump_bidi, ServingConfig identity seam — CF-005/006)
├── tokio (wasm-clean subset: rt, sync, io-util, macros, time)
├── bytes, futures, serde/serde_json, thiserror, tracing, async-trait
└── (no backend deps — sockets/process live in feature-gated modules)
alktunnels (local feature) — non-wasm by design
└── adds: tokio/net (TCP dial, UDP connect, unix sockets — unix per OQ-TN-14)
Same posture as alktty: the default crate is protocol-only and wasm-clean (AGENTS.md convention 4); substrate I/O is feature-gated (ADR-004). The wire format carries no alkcall-internal types (alktty ADR-006's self-containment rule applies to the tunnel codec — trivial here, since the codec is a 2-byte length prefix).
Feature Gates
| Feature | Contents | Wasm |
|---|---|---|
| (default) | params, wire codec, open-op spec, establisher shapes, TunnelSession — protocol only |
yes |
local |
TCP/UDP/unix dial + listen helpers — halves-producing dial/listen functions (ADR-004). Unix ships (OQ-TN-14); stdio does NOT — a spawned process's stdio is alktty's pipe mode (exit codes, signals), not a tunnel | no |
Module Map
src/
lib.rs — re-exports; crate docs
params.rs — TunnelParams {resource, substrate} + open-op input schema (ADR-001)
wire.rs — the data-plane codec: frame_datagram / DatagramReader (ADR-003)
producer.rs — the open op: tunnel_open_spec (scope-gated), establisher
shapes (dial + listen), pump handler (pump_bidi),
register_tunnel_openable (ADR-004 shapes)
consumer.rs — TunnelSession: open / adopt / stream_halves /
take_halves / send_datagram /
recv_datagram / pump_against / close / join / Drop (ADR-005)
error.rs — TunnelError + the typed open-error surface (ADR-049 §4)
local/ — the `local` feature module: real socket halves functions
Public API surface is lib.rs re-exports (AGENTS.md convention 16).
Design Decisions
All design decisions are documented as ADRs in decisions/.
| ADR | Decision | Summary |
|---|---|---|
| 001 | Open-op params: resource + substrate | {resource, substrate} — the producer's stable name, not an address; wire-stable from the first consumer |
| 002 | Single alk/tunnel ALPN |
Substrate in params selects the framing; no ALPN split, no API bifurcation |
| 003 | Data-plane codec | Raw pass-through (stream) / mandatory [len: u16 BE] (UDP — F-2 mandate); len=0 is a legal empty datagram |
| 004 | No TunnelBackend trait | Halves functions at the assembly layer; listen is an establisher shape; trait re-evaluated at the 3-consumer threshold |
| 005 | Consumer session owns teardown | TunnelSession with close/join/Drop — the W3 gap closes structurally |
| 006 | Access-control posture | The open gate is the boundary: tunnel:open scope (stable once published), op-level ACL, ownership seam, no allowlists in v1 |
Open Questions
Open questions are tracked in open-questions.md. Key questions affecting this document:
- OQ-TN-11: resource naming collision domain + lifecycle (partially resolved)
- OQ-TN-12: hub re-produce composition helper (deferred(scope))
- OQ-TN-13: UDP truncation semantics (resolved — fail-loud, ADR-003)
- OQ-TN-14: unix/stdio placement (resolved — unix in via
local, stdio out: alktty's pipe mode owns process stdio)
References
- Phase 0:
docs/research/(findings, both POC summaries, survey) - Upstream ADRs: see README.md §References
- Sibling: alktty (
/workspace/@alkdev/alktty/docs/architecture/)