Skip to main content
This page covers rustunnel P2P tunnels — direct peer-to-peer connections between two clients without an exit node in the data path. Once the two peers complete NAT hole punching, traffic flows directly between them over QUIC; the rustunnel server only signals during the handshake. If hole punching fails (symmetric NAT, restrictive firewall), rustunnel transparently falls back to relayed mode. 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.
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: 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:

NAT types explained

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


Automatic Fallback

When direct mode is attempted but fails, the connection transparently falls back to server relay:
From the user’s perspective: 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

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


CLI Usage

Publisher (expose a service)

Exposes localhost:27015 as a P2P tunnel named my-game.

Subscriber (connect to a service)

Listens on localhost:8000. Any TCP connection to that port is forwarded through the tunnel to the publisher’s localhost:27015.

Config file

Error cases


See also

UDP Tunnels

Forward UDP datagrams (game servers, DNS, QUIC, VoIP) to a public port on the rustunnel server.

Load Balancing & Health Checks

Run multiple backends behind one subdomain or TCP port with automatic failover.

Client Guide

Every flag and config option for the rustunnel CLI client.

Architecture

How HTTP / TCP / UDP / P2P tunnels flow through the system.