Files
alksocks/docs/architecture/overview.md
T
glm-5.3-flash 986bd13ce7 adr-014: the idle-timeout posture — teardown-governed phases, no protocol-layer idle bound
- ADR-014 accepted (review 002 C-2): the pre-command handshake, the
  BIND quiet wait, and the association lifetime are all
  teardown-governed; the protocol layer never times out (option 1)
- the BIND relationship recorded as same regime (ADR-007 decided one
  phase's ends by designing BIND; ADR-014 records the inherited
  resolution — one principle, not two)
- the doors' accept-path obligations recorded pre-batch-2 (accept-stage
  idle bound, per-credential/per-peer caps, accept-queue bounds);
  revisitable additively (an optional producer-side idle seam would
  be config, not wire)
- spec lines carry the decided posture: wire.md (association-lifetime
  bullet, no-in-band-control bullet, preamble, frontmatter);
  producer.md (the open handler, the local-backend section, decision
  table, frontmatter); overview.md ADR index
- one upstream-fidelity correction: upstream's dial timeout maps to
  0x06 (TTL expired), not 0x01 as the review sketch suggested
- open-questions.md unchanged (C-2 was never tracked there)

Verification: cargo test green (357 passed), clippy -D warnings clean,
fmt clean, cargo doc clean, wasm check + clippy (wasm32) clean
2026-09-24 19:16:30 +00:00

14 KiB


status: draft last_updated: 2026-09-24 (ADR-014 indexed — the idle-timeout posture; the targeted fork — ADR-013, the module is src/rfc1928/; the vendor extraction amendment — no net feature — 2026-09-21; Phase 1 initial draft 2026-09-20 — the OQ-SK ledger promoted into ADRs 001-012 and open-questions.md)

alksocks — Overview

A SOCKS5 (RFC 1928) producer/consumer protocol crate on alkcall channels: an arbitrary-egress proxy service that never binds a port unless explicitly configured to, carrying the RFC conversation inside a channels data channel like any other produced resource. The sibling of alktty (alk/tty) and alktunnels (alk/tunnel) on the alkcall substrate; where alktunnels realizes the -L/-R forwarding flavors, alksocks realizes the -D flavor (dynamic / SOCKS5 proxy) — the one alktunnels explicitly deferred to composition. This document covers the crate's purpose, the resource model in brief, dependencies, the ALPN, feature gates, and the module map; component details are in the sibling documents (wire.md, producer.md, consumer.md, rfc1928.md).

What

alksocks registers the alk/socks5 ALPN's open op on alkcall channels and provides the typed consumer sessions plus the optional front doors:

  • The producer half (producer.md): the channels/socks5/sub open op (registered via ChannelCore::register_openable_with_establisher, alkcall ADR-047/049). The establisher is a session gate (validates the opener identity; the SOCKS5 target is unknown at open time); the open handler runs the RFC 1928 state machine in-stream over the channel's BiStream — the forked RFC 1928 core (rfc1928.md) drives negotiation, command read, and replies; the dial/egress/accept seams (ADR-004) supply the I/O planes; alkcall::channels::pump_bidi (ADR-050) is the data plane for CONNECT and BIND's post-reply#2 stage.
  • The consumer half (consumer.md): a typed channels-native session that opens channels/socks5/sub and speaks RFC 1928 inside them (Socks5Stream::use_stream-shaped; the associate session and the BIND reply#2 reader). Wrapper-aware means a client that knows the in-band conventions (the sentinel BND.ADDR, in-band frames) rather than a vanilla RFC 1928 client. Optionally it exposes the service locally: the front door — a real socks5://127.0.0.1:port endpoint relaying each accepted connection into a channel (the ssh -D local-exposure shape; the path curl/browser/tun2proxy point at).
  • The optional producer local backend: the producer may serve from a real listener (feature-gated; the same door shape in the other direction — vanilla clients → a locally-served SOCKS5 resource).

The defining posture (the crate's reason to exist as a separate crate): the protocol never binds. Kernel binds exist only as the two explicit, optional, feature-gated assembly shapes above; the binding decision belongs to the caller.

Why

  • The -D conclusion, realized as a first-class protocol crate: alktunnels' Phase 0 settled that -D composes at the assembly layer ("tunnel a socks5 connection"); this crate makes the SOCKS5 service itself a produced, ACL-scoped resource, so both channels-native consumers and vanilla SOCKS5 clients get the same service in-band or through the front door.
  • The vpn-like endgame composition: user tunnels a produced SOCKS5 resource to a local port (the front door), points tun2proxy (--proxy socks5://user:pass@host:port natively) at it, and the whole host's traffic rides the alk* egress. This crate is the SOCKS5 leg; alktunnels carries the tunnel; tun2proxy closes the loop.
  • The substrate-agnostic protocol layer: the forked RFC 1928 core is generic over T: AsyncRead + AsyncWrite + Unpin — a channels BiStream drops in directly (POC #1), a TcpStream drops in at the local backends. Neither substrate leaks into the protocol layer (the alktty TtyBackend / alktunnels pump-halves inversion-point precedent).

The Resource Model in Brief

A SOCKS5 channel's life (full detail in wire.md and producer.md):

  1. Open (the establishment phase). The consumer opens channels/socks5/sub with trivial params (ADR-001). The establisher is the session gate — it checks/records the opener identity and does session-level policy; it never sees a target. Failure is a typed channel:open_failed (alkcall ADR-049); no phantom channel.
  2. RFC conversation (the control plane, in-stream). The RFC 1928 handshake (method negotiation → command → reply) runs in-stream over the BiStream, driven by the forked typestate. Command dispatch: CONNECT/ASSOCIATE require socks5:open; BIND requires socks5:bind (the command gate, ADR-006).
  3. Data plane. CONNECT: the dial seam (ADR-004) produces boxed halves; pump_bidi two-pumps channel ↔ target — raw pass-through, 0 B overhead. ASSOCIATE: the reply's BND.ADDR is a pure RFC handle (the sentinel in-band); the datagram stage rides [len: u16 BE][addr + data] frames (ADR-003, the handle swap) to the egress seam. BIND: reply#1 → quiet wait → exactly one accept (the accept seam) → reply#2 → pass-through (ADR-007).
  4. Teardown. Per-command: CONNECT/BIND end on channel EOF (the channels sentinel); ASSOCIATE's association lifetime = channel lifetime; BIND's quiet wait is select!-single-shot with the client-gone teardown riding the accept future's drop. Ownership rides the handle structurally (ADR-004).

Producer and consumer are roles, not sides: both ends of a channels connection can be both simultaneously (alkcall ADR-022/ADR-037). RFC 1928's own client/server roles keep their RFC names inside the protocol layer; crate-level vocabulary is producer/consumer.

Dependencies

alksocks (default — wasm-clean)
├── alkcall 0.8.0   (core types: Connection, ProtocolHandler, BiStream,
│                    AuthContext, Identity, AccessControl, OwnershipProvider;
│                    channels: ChannelCore/ChannelClient,
│                    register_openable_with_establisher, pump_bidi,
│                    Establishment/ChannelPlan + reply projection,
│                    ServingConfig identity seam — CF-005/006)
├── tokio (wasm-clean subset: rt, sync, io-util, macros, time)
├── serde/serde_json (params schema), thiserror (error surface), tracing
└── (no backend deps — sockets live in feature-gated modules)

alksocks (local feature) — non-wasm by design
└── adds: tokio/net + socket2 (the feature's own socket providers) and
    the first-party local backends + front doors

alksocks (composed feature)
└── adds: alktunnels 0.2.0 (the direct op egress; the bind-first
    listen accept; ADR-008's composed providers) — its default
    crate is wasm-clean; the composed providers use only the
    ungated channels surface (verify with the wasm check)

Same posture as alktty/alktunnels: the default crate is protocol-only and wasm-clean — the RFC 1928 core is an extraction of fast-socks5's T-generic subset, containing zero kernel-net code, so there is no net feature at all (ADR-011 §Amendment); substrate I/O rides local alone (ADR-009 §Amendment). fast-socks5 is NOT a Cargo dependency — it was extracted in-tree and forked (ADR-013); /workspace/fast-socks5 stays as the differential-testing behavioral reference.

Feature Gates

Feature Contents Wasm
(default) the RFC 1928 core (src/rfc1928/, ADR-013 — codecs, typestate), wire codec, open-op spec, the three seams' traits/types, producer + consumer session types — protocol only; zero kernel-net code, wasm-clean by construction yes
local the crate's first-party local backends: TCP dial, UDP egress socket, BIND TcpListener accept, the front doors (consumer + producer local shapes); brings tokio/net + socket2 into the graph no (build error on wasm, the alktty posture)
composed the composed providers over alktunnels 0.2.0: direct-op egress (lazy destination→tunnel table), bind-first listen accept — adds the alktunnels dep yes (alktunnels' default crate is wasm-clean; the composed providers use only its ungated channels surface — verify with the wasm check at implementation)

Module Map

src/
  lib.rs            — re-exports; crate docs
  rfc1928/          — the RFC 1928 core: the targeted fork of
                      fast-socks5's T-generic subset (ADR-013;
                      extraction per ADR-011; fork point noted
                      in-tree): consts, codecs, errors, typestate,
                      client use_stream path; the fork's two
                      carried changes (BIND reply surface,
                      new_reply publication); no kernel-net code
  params.rs         — the trivial params schema + SOCKS5_OPEN_SCOPE/
                      SOCKS5_BIND_SCOPE constants (ADR-001/006)
  wire.rs           — the ASSOCIATE datagram frame codec, re-implemented
                      in first-party code shaped on alktunnels' length
                      codec (ADR-003; framing is wire-layer, applied
                      exactly once)
  producer.rs       — the open op spec (scope-gated), the session-gate
                      establisher, the command-dispatch pump handler
                      (typestate driving + the three seams), teardown
  consumer.rs       — the channels-native session types (connect/associate/
                      bind reply#2 reader), teardown ownership
  door.rs           — the front doors (feature `local`): the raw-pipe
                      bridge + the RFC 1929 cred-bearing door + the
                      credential→identity mapping (ADR-005); the UDP
                      front door's RFC codec boundary (ADR-003)
  egress.rs         — the egress/accept seam types (DialFn/EgressFactory/
                      BindAcceptFactory contracts — ADR-004)
  local/            — feature `local`: the socket-backed provider
                      constructors (dial/egress/accept)
  composed/         — feature `composed`: the alktunnels-composed
                      providers (direct-op egress, bind-first accept)
  error.rs          — SocksError + the typed open-error surface
                      (alkcall ADR-049 §4)

Public API surface is lib.rs re-exports (AGENTS.md convention 15). Backend modules are feature-gated and never imported from the protocol/adapter/client modules.

Design Decisions

All design decisions are documented as ADRs in decisions/.

ADR Decision Summary
001 ALPN + open op + trivial params alk/socks5, channels/socks5/sub, {} — the RFC conversation is the data plane
002 Protocol base fast-socks5's explicit typestate API (extracted + forked, src/rfc1928/); faithful reply-code mapping
003 The handle swap one channel per association; {addr, data} frames in-band; RFC codec at front doors only
004 The dial-callback shape function-not-trait at three seams (dial/egress/accept); ownership rides the handle
005 Auth: "both," split by the door producer = identity seam; front door = RFC 1929 + credential→identity mapping (static config)
006 Access-control posture socks5:open gate + socks5:bind command gate; target policy = the dialer
007 BIND in the base crate phase model extended (reply#1 → quiet wait → accept → reply#2 → pass-through); additive
008 Provider defaults local-socket + composed (direct op) providers, both feature-gated; no implicit dialer
009 Feature gates local + composed; no net feature (the RFC 1928 core is wasm-clean by construction — ADR-011 §Amendment); default crate wasm-clean
010 Channel capacity no wire change; assembly-layer config + the documented proxy-workload recommendation
011 The extraction the T-generic subset copied in-tree; two carried changes (BIND, new_reply); v1.0.0 noted (superseded as a posture by ADR-013)
013 The targeted fork the extracted region is owned; src/rfc1928/; fork point 5b18d4d; hand-port replaces re-vendor
012 Target policy dialer-refuses; domain targets pass through unresolved; virtual subnets = assembly policy
014 Idle-timeout posture every phase teardown-governed, no protocol-layer idle bound; doors own the accept-path defense; revisitable additively

Open Questions

Open questions are tracked in open-questions.md. Key questions affecting this document:

  • OQ-SK2-06: sandboxed-wasm-consumer adapter story (deferred(scope) — a product input, nothing waits on it)
  • OQ-SK2-08: recommended proxy-workload cap figure (open — a docs tuning note; ADR-010 pins the posture)

References

  • Phase 0: docs/research/phase-0.md (the OQ ledger OQ-SK-01..09 + the convergence), POC findings (poc-connect-wasm-findings.md, poc-udp-associate-findings.md, poc-bind-findings.md), iroh-socks5-eval.md
  • Upstream ADRs: alkcall ADR-037 (channel ops), ADR-039 (params is ALPN-specific), ADR-047 (openable ALPNs are operations), ADR-049 (establishment + reply projection), ADR-050 (pump_bidi), ADR-040/ 041 (channel caps), ADR-051 (hub relay), ledger CF-005/CF-006 (identity seam); alktunnels ADR-003 (length codec), ADR-007/008 (direct op; bind-first + forwarded), ADR-006 (ACL posture)
  • Siblings: alktty (/workspace/@alkdev/alktty), alktunnels (/workspace/@alkdev/alktunnels); reference checkout /workspace/fast-socks5 (v1.0.0, MIT, third-party)
  • RFC 1928, RFC 1929; tun2proxy (/workspace/tun2proxy)