Files
alknet/docs/research/pivot/alpn-service-architecture.md

14 KiB

ALPN-as-Service Architecture

Status: Research / Pivot Proposal Last updated: 2026-06-14

Core Insight

A service IS an ALPN. Every protocol handler registers an ALPN string on a shared QUIC+TLS endpoint. The ALPN negotiation during the TLS/QUIC handshake is the dispatch mechanism — it routes the connection to the correct protocol handler before any application bytes are read.

This insight comes from observing how iroh already works: Router dispatches incoming QUIC connections to ProtocolHandler implementations based on the ALPN string. The reverse-proxy project at /workspace/@alkdev/reverse-proxy uses the same pattern for TLS (ALPN → h2 or http/1.1). Hickory DNS registers ALPN protocols (dot, doq, h2, h3) in its TLS/QUIC server configs. The pattern is universal.

The ProtocolHandler Trait

A single trait replaces the current StreamInterface + MessageInterface split, the ListenerConfig enum, and the three-layer dispatch model:

#[async_trait]
pub trait ProtocolHandler: Send + Sync + 'static {
    /// The ALPN string this handler claims (e.g. b"alknet/ssh")
    fn alpn(&self) -> &'static [u8];

    /// Handle an incoming bidirectional QUIC stream
    async fn handle(&self, stream: BiStream, auth: &AuthContext) -> Result<()>;
}

BiStream is a joined (SendStream, RecvStream) implementing AsyncRead + AsyncWrite. AuthContext carries the authenticated identity (resolved during the TLS/QUIC handshake or from the first protocol frames, depending on the handler).

Every handler receives a byte stream and manages its own wire format. HTTP is just a handler on ALPN h2. DNS is just a handler on its own ALPN. SSH is a handler on alknet/ssh. The call protocol is a handler on alknet/call.

Handler Registry

alknet Endpoint (QUIC + TLS on a single port)
  │
  ├── ALPN "alknet/ssh"     → SshAdapter
  │     • russh doing SSH-2 handshake, auth, channel multiplexing
  │     • direct-tcpip, forwarded-tcpip, streamlocal-forward
  │     • SOCKS5 server on the client side
  │     • This is the "gateway" — richest tunneling primitives
  │
  ├── ALPN "alknet/call"    → CallAdapter
  │     • JSON-RPC: call/request + streaming (subscribe)
  │     • EventEnvelope framing (length-prefixed JSON)
  │     • Operation registry, access control, pending request map
  │     • Cross-language: consumable from JS, Python, WASM, anything
  │
  ├── ALPN "alknet/git"     → GitAdapter
  │     • gix (Apache-2.0/MIT) for pack generation, ref resolution, object store
  │     • Custom pkt-line protocol adapter (~1000 lines)
  │     • Capability advertisement v2, ls-refs, fetch, receive-pack
  │     • No HTTP layer — git protocol directly over QUIC streams
  │
  ├── ALPN "alknet/sftp"    → SftpAdapter
  │     • russh-sftp protocol core (WASM-ready, transport-agnostic)
  │     • 26 packet types, custom serde codec, pure data transformation
  │     • Only read_packet() couples to I/O — easily adapted
  │     • Can compile to WASM for browser SFTP clients
  │
  ├── ALPN "alknet/msg"     → MessageAdapter
  │     • E2E encrypted direct messages (encrypt with recipient's public key)
  │     • Mixnet support (Chaum 1981): nested encryption, batch-and-reorder
  │     • Return addresses as digital pseudonyms
  │     • Replicators are naturally mixes — they already relay data
  │
  ├── ALPN "alknet/http"    → HttpAdapter
  │     • axum router with auth middleware
  │     • REST API, dashboard, MCP endpoint
  │     • Maps HTTP requests to call protocol operations
  │
  ├── ALPN "alknet/dns"     → DnsAdapter
  │     • hickory-proto for DNS wire format (#![no_std], WASM-compatible)
  │     • pkarr::SignedPacket for self-sovereign DNS (iroh-dns pattern)
  │     • Service discovery: _alknet.<z32-node-id>.alk.dev TXT
  │     • Control channel: AuthToken in query labels (censorship fallback)
  │     • Encrypted transports: DoT, DoQ, DoH3 via ALPN dispatch
  │     • Mainline DHT fallback for fully decentralized resolution
  │
  ├── ALPN "h3"             → WebTransportAdapter (wtransport)
  │     • Browser-compatible WebTransport (W3C standard)
  │     • Bidirectional streams + datagrams
  │     • Enables browser-to-head without SSH key exchange
  │     • AuthToken in CONNECT request headers
  │
  ├── ALPN "h2" / "http/1.1" → Standard HTTP
  │     • For browsers, curl, standard HTTP clients
  │     • Same axum router as alknet/http but on standard ALPNs
  │
  └── custom ALPNs          → third-party adapters

What This Simplifies

1. The Interface Layer Collapses

No more StreamInterface vs MessageInterface split. No more ListenerConfig enum with three variants (Stream, Http, Dns). No more server accept loop handling three different listener types. Every handler receives a stream and manages its own protocol. HTTP is just a handler on ALPN h2. DNS is just a handler on its own ALPN. SSH is a handler on alknet/ssh.

2. The Call Protocol Becomes One ALPN Among Many

It's a generic JSON-RPC protocol that supports call/request and streaming. Services that need structured RPC register operations under alknet/call. Services with their own wire format (Git, SFTP, SSH) get their own ALPN. Cross-node calls go through alknet/call. In-process calls are direct.

3. OperationEnv's Three Dispatch Paths Collapse

Dispatch is "which ALPN does this belong to?" — not local/irpc/remote path selection. A handler that needs to call another service opens a stream on alknet/call and sends a call.requested envelope. The handler doesn't need to know whether the target is local, in-cluster, or cross-node.

4. Crate Decomposition Gets Simpler

A core crate provides the endpoint/router + auth/identity. Protocol crates register handlers. No need for trait interop without crate dependencies (the current alknet-storage-implements-IdentityProvider-without-depending-on-core pattern).

5. The WASM Story Gets Clean

If we assume a byte stream, protocol parsers compile to WASM:

  • russh-sftp's protocol core is already WASM-ready (pure data transformation, no I/O)
  • hickory-proto is #![no_std] with a wasm-bindgen feature
  • The call protocol's JSON framing is inherently cross-language
  • Git's pkt-line is simple enough to implement anywhere
  • A browser gets a WebTransport stream and speaks SFTP, Git, or call protocol directly

6. The Reverse-Proxy ALPN Pattern Applies Directly

The reverse-proxy at /workspace/@alkdev/reverse-proxy does: TLS handshake → read ALPN → dispatch to h2 or http/1.1 handler. Alknet does the same, just with more ALPNs. The ArcSwap<DynamicConfig> pattern for hot-reloading handler routing works identically.

7. SSH Is the Tunneling Gateway, Not the Only Interface

SSH provides direct-tcpip, forwarded-tcpip, streamlocal-forward — rich tunneling primitives that other protocols don't have. But SSH is just one ALPN. A node that only needs Git can skip SSH entirely and use alknet/git directly. A browser can use h3 (WebTransport) + alknet/call without SSH.

Auth and Identity

Auth is shared across all handlers. The IdentityProvider trait resolves credentials to an Identity:

pub trait IdentityProvider: Send + Sync + 'static {
    fn resolve_from_fingerprint(&self, fingerprint: &str) -> Option<Identity>;
    fn resolve_from_token(&self, token: &AuthToken) -> Option<Identity>;
}

Each handler presents credentials differently, but all resolve through the same provider:

Handler Credential presentation Resolves via
SshAdapter SSH public key handshake resolve_from_fingerprint()
CallAdapter AuthToken in first frame resolve_from_token()
HttpAdapter Authorization: Bearer header resolve_from_token()
DnsAdapter AuthToken in query labels resolve_from_token()
WebTransportAdapter AuthToken in CONNECT headers resolve_from_token()
GitAdapter Signed push certificate resolve_from_fingerprint()

Distributed Git via ERC721

The git adapter integrates with on-chain identity for decentralized repository management:

Creator mints RepoToken (ERC721) → on-chain metadata stores:
  • name, owner (UserToken)
  • authorized committers (list of UserTokens or key fingerprints)
  • optional: preferred replicator set

Committer pushes → GitAdapter verifies:
  1. Resolve signer's key → UserToken (via on-chain IdentityProvider)
  2. Check UserToken is in RepoToken's committer list
  3. Accept push, compute new refs
  4. Gossip update to replicator mesh

Replicator receives gossip → verifies independently → stores in local repo

Replicators are voluntary nodes that:

  • Subscribe to gossip for specific repos (by token ID)
  • Serve repos via the git ALPN
  • Advertise which repos they replicate via pkarr/DNS records
  • May also serve other ALPNs (dns, msg, call)
  • Have donation addresses in node metadata (no protocol-level economics)

Messaging Layers

Three distinct privacy layers on the same relay infrastructure:

Layer What's hidden Mechanism Use case
E2E encryption Message content Encrypt with recipient's public key Direct messages, file transfer
Gossip + call protocol Nothing hidden by design Public broadcast, structured RPC Group chat, repo notifications
Mixnet (Chaum 1981) Sender/recipient metadata Nested encryption, batch-and-reorder Pseudonymous commits, anonymous tips

The replicator doesn't care which layer it's serving. It sees "encrypted blob, forward to X" and does the same thing regardless. A replicator that handles git gossip is also a mix node is also a relay for e2e DMs — it's all encrypted bytes through the same relay infrastructure.

Crate Decomposition

alknet-core          Endpoint, ALPN router, auth/identity, config, call protocol
alknet-ssh           SshAdapter (russh, SOCKS5, port forwarding)
alknet-call          CallAdapter (JSON-RPC, operation registry, access control)
alknet-git           GitAdapter (gix, pkt-line protocol)
alknet-sftp          SftpAdapter (russh-sftp protocol core)
alknet-msg           MessageAdapter (E2E encryption, mixnet)
alknet-http          HttpAdapter (axum, REST API)
alknet-dns           DnsAdapter (hickory-proto, pkarr, service discovery)
alknet-secret        BIP39/SLIP-0010/AES-GCM (standalone)
alknet-napi          Node.js native addon (call protocol client)
alknet               CLI binary (assembles everything)

Each protocol crate depends on alknet-core for auth/identity/config. No trait interop without crate dependencies. The CLI binary registers handlers with the endpoint.

What Stays (Preserved from Current Implementation)

  • Transport implementations (TCP, TLS, iroh) — become the endpoint's connection acceptors
  • SSH client/server (russh, SOCKS5, port forwarding, channel proxy) — become alknet-ssh
  • Call protocol (EventEnvelope framing, operation registry, access control, pending request map) — become alknet-call
  • Auth/identity (Ed25519 keys, API keys, IdentityProvider, ConfigIdentityProvider) — shared in alknet-core
  • Dynamic config reload (ArcSwap<DynamicConfig>, ConfigReloadHandle) — shared in alknet-core
  • Secret crate (BIP39/SLIP-0010/AES-GCM, irpc service, key caching) — standalone alknet-secret
  • NAPI addon (connect, serve, ThreadsafeFunction callbacks) — becomes call protocol client
  • Stealth mode (byte-peek protocol detection) — replaced by ALPN negotiation (cleaner, no peeking needed)

What Gets Dropped/Deferred

  • StreamInterface / MessageInterface traits — replaced by ProtocolHandler
  • ListenerConfig enum — replaced by ALPN advertisement configuration
  • OperationEnv three dispatch paths — replaced by "call through alknet/call ALPN"
  • The metagraph data model (alknet-storage) — defer until concrete need
  • The flowgraph crate — defer, observability infrastructure
  • CredentialProvider phase progression (A through D) — simplify to what's needed now
  • 38 ADRs for unbuilt features — archive, revisit if needed

Migration Path

  1. Define ProtocolHandler trait and ALPN router in alknet-core
  2. Extract SSH code into alknet-ssh implementing ProtocolHandler
  3. Extract call protocol into alknet-call implementing ProtocolHandler
  4. Wire existing auth/config into shared alknet-core
  5. Build GitAdapter on gix + pkt-line protocol
  6. Build SftpAdapter on russh-sftp protocol core
  7. Build HttpAdapter on axum
  8. Build DnsAdapter on hickory-proto + pkarr
  9. Build MessageAdapter (E2E + mixnet)
  10. Integrate WebTransport via wtransport
  11. Update NAPI to be a call protocol client
  12. CLI binary registers all handlers

Reference Projects

Project Path Relevance
iroh /workspace/iroh/ ALPN-based protocol dispatch on QUIC endpoints
reverse-proxy /workspace/@alkdev/reverse-proxy ALPN routing (h2/http1.1), ArcSwap config, TLS acceptor pattern
russh-sftp /workspace/russh-sftp/ Transport-agnostic protocol core, WASM-ready
hickory-dns /workspace/hickory-dns/ DNS wire format (#![no_std]), ALPN-registered transports (dot/doq/h2/h3)
wtransport /workspace/wtransport/ WebTransport (h3 ALPN), browser-compatible QUIC streams
rtc /workspace/rtc/ Sans-I/O WebRTC (datachannel, RTP) — reference for P2P alternative
gix crates.io Apache-2.0/MIT git operations (pack generation, ref resolution, object store)