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.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.
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.
- 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
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 }.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.
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.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).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:- 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.
Sessions
A UDP session holds:| Field | Purpose |
|---|---|
| Forwarding channel | Edge listener pushes datagrams into the yamux stream |
| Last-activity timestamp | Used 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 |
Server Configuration
UDP tunnels require a dedicated port range inserver.toml. It must not overlap with tcp_port_range, and the ports must be reachable through the host firewall.
CLI Usage
Expose a local UDP service:host:port is forwarded to your local service, and replies are returned to the original sender.
Config File
In~/.rustunnel/config.yml:
rustunnel start.
Limitations and Notes
| Topic | Notes |
|---|---|
| Datagram size | Up to 65,507 bytes. Payloads exceeding the path MTU will fragment at the IP layer; rustunnel does not perform application-level segmentation. |
| Per-source sessions | Each unique source_ip:source_port creates its own session. NATs that rewrite ports aggressively (some mobile carriers) can produce many short sessions. |
| Idle reaping | 60-second inactivity timeout. Services that rely on very infrequent keepalives may need to send periodic traffic. |
| Rate limiting | Per-tunnel rate limits apply; each datagram counts as one request against rate_limit_rps. |
| Metering | bytes_proxied counts raw UDP payload only; the 4-byte framing header is excluded. |
| No TLS | UDP is forwarded as-is. For encryption, use DTLS, QUIC, or WireGuard inside the tunnel. |
Troubleshooting
| Symptom | Likely cause |
|---|---|
udp_port_range is disabled on registration | udp_port_range = [0, 0] in server.toml. Set a real range. |
| Datagrams reach the server but not the local service | Local 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 remotely | The 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 client | Source NAT rewriting the client’s port — a client-network issue, often unavoidable on mobile. |
| Connection “drops” after ~60 seconds of idle | Idle reaper closed the session. Send a keepalive or reopen on demand. |
| Public UDP port not reachable from the internet | Host firewall isn’t open for udp_port_range. Run ufw allow 20100:20199/udp. |

