Skip to main content

Documentation Index

Fetch the complete documentation index at: https://rustunnel.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

rustunnel supports direct peer-to-peer tunnels between two clients. This allows two machines — neither with a public IP — to communicate through the rustunnel server, with an optional upgrade to a direct connection that bypasses the server entirely.

Concepts

A P2P tunnel connects two rustunnel clients:
  • Publisher — the client exposing a local service (e.g., a game server on port 27015). Registers a named tunnel with a shared secret.
  • Subscriber — the client connecting to the publisher’s service. Listens on a local port (e.g., 8000) and forwards incoming TCP connections through the tunnel.
  • Shared secret — a password both sides must know. The SHA-256 hash is sent to the server for verification; the plaintext never leaves the client.
App -> Subscriber (localhost:8000) --tunnel--> Publisher (localhost:27015) -> Service
Unlike HTTP/TCP tunnels, P2P tunnels don’t require a public port or subdomain on the server. The server acts as a signaling relay and (optionally) a data relay.

Connection Modes

P2P tunnels support two modes:
ModeData pathLatencyServer loadMetered
RelayedApp -> Subscriber -> Server -> Publisher -> ServiceHigher (two WS hops)Full bandwidthYes
DirectApp -> Subscriber -> Publisher -> Service (via UDP hole punch + QUIC)Lower (peer-to-peer)Signaling onlyNo
The mode is selected automatically based on NAT compatibility. If direct mode fails or isn’t possible, the connection transparently falls back to relayed mode.

Server-Relayed Mode

This is the default mode and always works regardless of NAT type or firewall configuration.

Connection flow

1

Publisher registers

The publisher connects to the server and registers a P2P tunnel with a name (e.g., my-game) and a SHA-256 hash of the shared secret.
2

Subscriber accepts a local connection

The subscriber listens on its local port (e.g., 8000). When an app connects, the subscriber sends a P2pConnect request to the server with the tunnel name and secret hash.
3

Server verifies and creates relay

The server looks up the publisher by name, verifies the secret hash matches, then sends NewConnection to both the publisher and subscriber sessions.
4

Both sides open yamux streams

The publisher opens a yamux stream and connects to its local service (e.g., localhost:27015). The subscriber bridges the already-accepted TCP connection with its yamux stream.
5

Server bridges the streams

The server runs copy_bidirectional between the publisher’s and subscriber’s yamux streams. Data flows bidirectionally until either side disconnects.
Each incoming connection to the subscriber’s local port creates a new relay. Multiple concurrent connections are supported, each with its own yamux stream pair.

Key details

  • On-demand relay: The relay is established per-connection, not at startup. Each app connection to the subscriber’s local port triggers a new P2pConnect.
  • Yamux multiplexing: Both clients maintain persistent WebSocket connections to the server. Each relay uses a separate yamux stream over these existing connections.
  • No public ports needed: Neither client needs to accept inbound connections from the internet.

Direct Mode (NAT Hole Punching)

Direct mode bypasses the server for the data path. After an initial signaling exchange through the server, the publisher and subscriber establish a direct UDP connection using NAT hole punching, then upgrade it to a QUIC session for reliable, encrypted transport.
Direct mode is enabled per-server via the [p2p] config section (direct_enabled = true). When enabled, the server automatically attempts hole punching for compatible NAT pairs. The relay is always available as a fallback.

Why direct mode matters

  • Lower latency: Data travels directly between peers instead of bouncing through the server.
  • Lower server cost: The server only handles signaling (~1 KB), not the full data stream.
  • Better for real-time applications: Game servers, VoIP, and live streaming benefit from reduced round-trip time.

NAT Classification via STUN

Before attempting hole punching, each client determines its NAT type using the STUN protocol (Session Traversal Utilities for NAT).

How STUN probing works

The client sends a STUN Binding Request to two different STUN servers. Each server replies with the client’s mapped address — the public IP and port the server saw the request come from. By comparing the two responses, the client classifies its NAT:
STUN Server A replySTUN Server B replyNAT type
1.2.3.4:50001.2.3.4:5000Cone NAT (traversable)
1.2.3.4:50001.2.3.4:6001Symmetric NAT (hard to traverse)
Public IP = Local IPPublic IP = Local IPOpen (no NAT)
No responseNo responseUnknown (use relay)

NAT types explained

NAT TypeDescriptionHole punch success
OpenNo NAT — client has a public IP~100%
Full ConeSame public mapping for all destinations; any external host can send to the mapped port~100%
Restricted ConeSame mapping, but only hosts the client has sent to can reply~95%
Port-Restricted ConeSame mapping, restricted by both IP and port~90%
SymmetricDifferent mapping for each destination (port changes per target)~10-60%
The key distinction: Cone NATs reuse the same public port for all outbound connections, making the mapped address predictable. Symmetric NATs assign a different port per destination, making prediction unreliable.
STUN probing takes ~100ms (two UDP round trips) and happens once at tunnel registration time. It’s cheap and non-invasive.

Hole Punching Strategy

The server classifies the NAT pair and selects one of three strategies:

Strategy 1: Direct Exchange (Cone + Cone)

Both peers have predictable mapped addresses. Both send a UDP probe to the other’s mapped address. The first probe “punches” the hole in each NAT by creating an outbound mapping. The second probe passes through. Success rate: ~95%

Strategy 2: Port Prediction (Cone + Symmetric)

The Cone peer has a predictable address. The Symmetric peer’s port changes per destination, but the server observes the port increment pattern from the STUN probes and predicts the next port range. The Cone peer sends probes to the predicted range. Success rate: ~60-70%

Strategy 3: Skip (Symmetric + Symmetric)

Both peers have unpredictable port mappings. Brute-force probing has a success rate under 10% and can trigger firewall alarms. Decision: fall back to relay immediately. No hole punching attempted, no delay.

Decision matrix

Publisher NATSubscriber NATStrategyExpected success
Open/ConeOpen/ConeDirect Exchange~95%
ConeSymmetricPort Prediction~60-70%
SymmetricConePort Prediction~60-70%
SymmetricSymmetricSkip -> RelayN/A
UnknownAnySkip -> RelayN/A

Automatic Fallback

When direct mode is attempted but fails, the connection transparently falls back to server relay:
1. Subscriber connects via P2pConnect
2. Server classifies NAT pair
3. If traversable:
   a. Server sends hole-punch instructions
   b. Peers send UDP probes (5-second timeout)
   c. Success -> QUIC session -> direct mode
   d. Failure -> relay mode
4. If not traversable:
   a. Relay immediately (no delay)
From the user’s perspective:
OutcomeConnection timeLatency
Direct succeeded~1-2 secondsLow (peer-to-peer)
Direct failed (timeout)~6-7 secondsNormal (relay)
Direct skipped (incompatible NATs)~1-2 secondsNormal (relay)
The subscriber never needs to know which mode is active. The CLI command is identical in all cases.

Security

Shared secret authentication

  1. Client computes SHA-256(secret) locally.
  2. Only the hash is sent to the server.
  3. The server compares hashes. Mismatch = connection rejected.
  4. The plaintext secret never leaves the client.

Transport encryption

ModeEncryptionServer visibility
RelayedTLS (WebSocket)Server can see plaintext (same as HTTP/TCP tunnels)
DirectQUIC (TLS 1.3, end-to-end)Server cannot see plaintext

Tunnel name visibility

P2P tunnel names are visible to the server. Anyone who knows both the tunnel name and the shared secret can connect. Use strong, unique secrets for production.

Billing and Metering

ModeMetered by server?Billing
RelayedYesPer-byte, same as TCP tunnels
DirectNo (data bypasses server)Informational only (client-reported)

CLI Usage

Publisher (expose a service)

rustunnel p2p 27015 --name my-game --secret "shared-secret-123"
Exposes localhost:27015 as a P2P tunnel named my-game.

Subscriber (connect to a service)

rustunnel p2p 8000 --target my-game --secret "shared-secret-123"
Listens on localhost:8000. Any TCP connection to that port is forwarded through the tunnel to the publisher’s localhost:27015.

Config file

tunnels:
  # Publisher
  gameserver:
    proto: p2p
    local_port: 27015
    p2p_name: my-game
    p2p_secret: shared-secret-123

  # Subscriber
  connect:
    proto: p2p
    local_port: 8000
    p2p_target: my-game
    p2p_secret: shared-secret-123

Error cases

ErrorCause
P2P tunnel name 'X' is already in useAnother publisher is using this name
P2P tunnel 'X' not foundNo publisher registered with this name
invalid P2P secretSubscriber’s secret doesn’t match publisher’s
P2P mode requires --name or --targetNeither publisher nor subscriber mode specified