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 UDP tunnels alongside HTTP and TCP. A UDP tunnel exposes a local UDP service — a game server, DNS resolver, QUIC endpoint, VoIP service — on a public port on the rustunnel server, forwarding datagrams bidirectionally between remote clients and the local service.

Concepts

UDP is connectionless, so there is no persistent “connection” to forward. Instead, rustunnel tracks sessions — one per unique remote source address (IP:port) — and forwards datagrams in both directions for as long as the session is active.
Remote client -> server:20100 (UDP) --tunnel--> Client -> localhost:27015 (UDP) -> Service
  • Public port — allocated from a dedicated UDP port range on the server (separate from the TCP tunnel pool).
  • Session — identified by the remote client’s source IP:port. Each unique source gets its own yamux stream to the client and its own locally bound UDP socket.
  • Idle timeout — sessions are reaped after 60 seconds of inactivity.
Unlike HTTP/TCP tunnels, there is no long-lived connection to track. Sessions are created on first datagram and expire on inactivity.

Connection Flow

1

Client registers a UDP tunnel

The rustunnel client sends RegisterTunnel with protocol = Udp. The server allocates a port from udp_port_range and replies with TunnelRegistered { public_port }.
2

Remote client sends a datagram

A remote UDP client sends a datagram to the allocated public port. The server’s UDP edge looks up a session keyed by the datagram’s source address.
3

Server creates a session on first datagram

If no session exists, the server creates one, buffers the datagram (up to 64 packets), and sends NewConnection { conn_id, protocol: Udp } to the client over the control-plane WebSocket.
4

Client opens a yamux stream

The client opens a new yamux stream for the conn_id, binds a local UDP socket, and connect()s it to the local service address (e.g., 127.0.0.1:27015).
5

Bidirectional datagram forwarding

The server forwards the buffered and subsequent datagrams into the yamux stream. The client writes them to the local service and copies replies back through the stream. The server delivers replies to the original source address.
Subsequent datagrams from the same remote source reuse the existing session and yamux stream. Datagrams from a new source create a new session.

Transport and Framing

UDP datagrams ride over the same yamux-over-WebSocket tunnel used by HTTP and TCP. Because yamux is a reliable byte stream and UDP is message-oriented, rustunnel preserves datagram boundaries with a simple length-prefixed frame:
[ 4-byte u32 big-endian length ][ N-byte payload ]
  • No encoding overhead beyond the 4-byte header.
  • Maximum payload is bounded by UDP itself (65,507 bytes after the IP/UDP header).
  • Framing overhead is not counted toward bytes_proxied — only raw payload bytes are metered.
UDP tunnels reuse the client’s existing WebSocket connection to the server. No new sockets are opened from client to server for UDP.

Sessions

A UDP session holds:
FieldPurpose
Forwarding channelEdge listener pushes datagrams into the yamux stream
Last-activity timestampUsed by the reaper to detect idle sessions
Initial buffer (up to 64 datagrams)Holds packets that arrive while the client is opening the yamux stream
A reaper task runs every 10 seconds and closes any session idle for more than 60 seconds. Because UDP has no FIN, this is how rustunnel decides when a session has ended.

Server Configuration

UDP tunnels require a dedicated port range in server.toml. It must not overlap with tcp_port_range, and the ports must be reachable through the host firewall.
[limits]
# Inclusive [low, high] port range reserved for UDP tunnels.
# Each active UDP tunnel consumes one port from this range.
# Set to [0, 0] to disable UDP tunnels entirely.
udp_port_range = [20100, 20199]
Open the range in the host firewall:
ufw allow 20100:20199/udp comment "rustunnel UDP tunnels"
udp_port_range and tcp_port_range must not overlap. Setting udp_port_range = [0, 0] disables UDP tunnel registration entirely — any client attempting to register a UDP tunnel will get an error.

CLI Usage

Expose a local UDP service:
# Expose a game server on udp/27015
rustunnel udp 27015 --token YOUR_TOKEN

# Pin to a specific region
rustunnel udp 27015 --region eu --token YOUR_TOKEN

# Local development against a self-hosted server
rustunnel udp 53 --server localhost:4040 --insecure --token dev-secret-change-me
On success the client prints the allocated public UDP endpoint:
UDP tunnel ready: udp://eu.edge.rustunnel.com:20100 -> localhost:27015
Any datagram sent to that public host:port is forwarded to your local service, and replies are returned to the original sender.

Config File

In ~/.rustunnel/config.yml:
tunnels:
  gameserver:
    proto: udp
    local_port: 27015

  dns:
    proto: udp
    local_port: 53
Start all configured tunnels with rustunnel start.

Limitations and Notes

TopicNotes
Datagram sizeUp to 65,507 bytes. Payloads exceeding the path MTU will fragment at the IP layer; rustunnel does not perform application-level segmentation.
Per-source sessionsEach unique source_ip:source_port creates its own session. NATs that rewrite ports aggressively (some mobile carriers) can produce many short sessions.
Idle reaping60-second inactivity timeout. Services that rely on very infrequent keepalives may need to send periodic traffic.
Rate limitingPer-tunnel rate limits apply; each datagram counts as one request against rate_limit_rps.
Meteringbytes_proxied counts raw UDP payload only; the 4-byte framing header is excluded.
No TLSUDP is forwarded as-is. For encryption, use DTLS, QUIC, or WireGuard inside the tunnel.

Troubleshooting

SymptomLikely cause
udp_port_range is disabled on registrationudp_port_range = [0, 0] in server.toml. Set a real range.
Datagrams reach the server but not the local serviceLocal firewall blocking the loopback UDP port, or the service isn’t actually listening on UDP (ss -ulnp to confirm).
Replies from the local service are never seen remotelyThe service isn’t replying to the packet’s source — make sure it replies to the datagram source, not a hard-coded peer.
New session every few packets from the same clientSource NAT rewriting the client’s port — a client-network issue, often unavoidable on mobile.
Connection “drops” after ~60 seconds of idleIdle reaper closed the session. Send a keepalive or reopen on demand.
Public UDP port not reachable from the internetHost firewall isn’t open for udp_port_range. Run ufw allow 20100:20199/udp.