Files
alktunnels/docs/architecture/overview.md
T
glm-5.3-flash 9ad7429440 docs: prepublish v0.1.0 — README rewrite, changelog, packaging, doc statuses
- 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)
2026-09-10 14:27:32 +00:00

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/sub open op registered via ChannelCore::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 via Establishment::new(plan); the pump handler awaits alkcall::channels::pump_bidi over the channel and the substrate halves (ADR-050, R-02's lifetime contract).
  • The consumer half (TunnelSession, ADR-005): opens tunnel channels (ChannelClient::open_channel on the forward path; the reverse path's call + adopt equivalent), splits the channel BiStream, 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. params identify 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):

  1. Open (the establishment phase). The consumer calls channels/tunnel/sub with params = {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 typed channel:open_failed (dial_failed / unknown_resource / resource_shortage / handler_error / timeout); the channel never exists on the opener's side afterward.
  2. 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).
  3. Teardown. The serving side is wrapper-managed (R-02); the adopting side's TunnelSession owns 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/)