Technical reference

tun-el protocol

v1

Version 1 — WebSocket + yamux + typed JSON.

TL;DR

Transport
WebSocket over TLS (WSS) on the control plane
Multiplexing
yamux — one stream per proxied request
Auth
Bearer token in the Hello frame
Messages
Line-delimited, typed JSON (LDJSON)
Keepalive
Ping / Pong every 15s, 45s idle timeout

Handshake sequence

Solid arrows are synchronous control frames; dashed arrows are async per-request streams multiplexed over the single yamux session.

Hello → HelloAck → yamux
client                          server
  │                               │
  │ ── WSS connect ─────────────▶ │
  │ ── Hello {token, subdomain} ▶ │
  │                               │  validate token
  │ ◀──────── HelloAck {url,sid} ─│   (success)
  │                               │
  │ ═══ yamux session open ══════ │
  │                               │
  │ ◀╌╌╌ Stream(open) req#1 ╌╌╌╌╌ │   public request in
  │ ──── Stream(data) resp#1 ───▶ │
  │                               │
  │ ── Ping ───────────────────▶  │
  │ ◀──────────────────── Pong ── │
  │                               │
  │ ── Close {reason} ─────────▶  │
  │ ◀──────────── Close {ack} ─── │

Phases

Handshake

Client dials WSS, sends Hello with its token and requested subdomain. Server replies HelloAck with the assigned public URL and session id.

Active tunnel

Each inbound public request opens a new yamux stream. The client proxies it to localhost and streams the response back over the same stream.

Teardown

Either side sends a close frame. In-flight streams drain within the grace window, then the session and its subdomain are released.

Messages

All control messages are JSON objects with a type discriminator, one per line.

Hello

First frame the client sends after the socket opens. Authenticates the session and requests a subdomain.

FieldTypeReqDescription
protocol_versionintyesProtocol revision the client speaks. Currently 1.
tokenstringyesBearer auth token.
subdomainstringnoRequested subdomain; server assigns a random one if omitted or taken.
clientstringyesClient name and version, e.g. tunel/0.1.0.
Hello (JSON)
{
  "type": "Hello",
  "protocol_version": 1,
  "token": "tok_8f3a21…",
  "subdomain": "acme-staging",
  "client": "tunel/0.1.0"
}

HelloAck

Server response on a successful handshake. After this frame the yamux session is considered open.

FieldTypeReqDescription
session_idstringyesOpaque id for this tunnel session.
urlstringyesPublic URL now routing to the client.
regionstringyesEdge node that terminated the tunnel.
expires_atstringnoRFC 3339 timestamp if the token has a TTL.
HelloAck (JSON)
{
  "type": "HelloAck",
  "session_id": "sess_4d9c0b",
  "url": "https://acme-staging.tunnel.example.dev",
  "region": "par",
  "expires_at": null
}

Error

Sent before the socket closes when the handshake or an operation fails. Always terminal.

FieldTypeReqDescription
codestringyesMachine-readable code, e.g. invalid_token, subdomain_taken.
messagestringyesHuman-readable explanation.
retryableboolyesWhether the client should back off and retry.
Error (JSON)
{
  "type": "Error",
  "code": "subdomain_taken",
  "message": "subdomain 'acme-staging' is reserved by another account",
  "retryable": false
}

Ping / Pong

Application-level keepalive multiplexed alongside data streams. Independent from WebSocket control pings.

FieldTypeReqDescription
nonceintyesEchoed back in the matching Pong to measure RTT.
tsintyesUnix millisecond timestamp at send time.
Ping / Pong (JSON)
{ "type": "Ping", "nonce": 42, "ts": 1748534400123 }
{ "type": "Pong", "nonce": 42, "ts": 1748534400131 }

Versioning

The client advertises protocol_version in the Hello frame. The server rejects versions it cannot speak with an Error of code unsupported_version. New fields are added in a backward-compatible way; removing or renaming a field is a breaking change that bumps the major protocol version.

See protocol changes in the changelog

Security

Hardening lives in the security docs

Tokens are peppered and hashed at rest, the control plane is rate-limited per IP and per token, and a cluster-wide abuse tracker auto-revokes credentials on detected abuse. See the security reference for the full threat model.