Skip to main content

UDP tunnel

otoroshictl udp-tunnel starts a local UDP server and bridges datagrams to a remote UDP service through Otoroshi via a persistent WebSocket tunnel.

your client ──► local UDP :1053 ──► WebSocket ──► Otoroshi ──► remote UDP service
otoroshictl │
auth & access control

The typical use-case is forwarding UDP-based protocols (DNS, syslog, SNMP, game servers, …) to internal services only reachable from the Otoroshi cluster.

How it works

Unlike the TCP tunnel which opens one WebSocket per connection, the UDP tunnel uses a single persistent WebSocket for all datagrams:

  1. A single WebSocket connection is established to /.well-known/otoroshi/tunnel on Otoroshi
  2. Each incoming datagram is wrapped in a JSON frame {"address": "…", "port": …, "data": "<base64>"} and sent over the WebSocket
  3. Otoroshi replies with the same JSON format; otoroshictl decodes and forwards the payload back to the originating client
  4. If the WebSocket drops, it reconnects automatically every 2 seconds while keeping the UDP socket bound

Basic usage — DNS via API key

Tunnel a DNS resolver authenticated with an Otoroshi API key:

$ otoroshictl udp-tunnel --host myotoroshi.example.com:9999 --tls --remote-host dns.internal.com --remote-port 53 --local-port 1053 --access-type apikey --apikey-client-id my-client-id --apikey-client-secret my-client-secret
[INFO  otoroshictl::tunnels::udp] UDP tunnel listening on 127.0.0.1:1053 → dns.internal.com:53 via Otoroshi (myotoroshi.example.com:9999)
[INFO  otoroshictl::tunnels::udp] UDP tunnel ready, waiting for datagrams ...
[INFO  otoroshictl::tunnels::udp] Connecting WebSocket tunnel ...
[INFO  otoroshictl::tunnels::udp] WebSocket tunnel connected

Then query the local DNS resolver:

$ dig @127.0.0.1 -p 1053 internal.service.com
tip

The local port defaults to 1053 (not 53) so that otoroshictl does not require root privileges. You can then configure your system or application to use 127.0.0.1:1053 as its resolver.

Bearer token authentication

$ otoroshictl udp-tunnel --host myotoroshi.example.com --tls --remote-host syslog.internal.com --remote-port 514 --local-port 5140 --access-type bearer --bearer-token eyJhbGciOiJSUzI1NiJ9...

Private apps session token

$ otoroshictl udp-tunnel --host myotoroshi.example.com --tls --remote-host snmp.internal.com --remote-port 161 --local-port 10161 --access-type session --session-token <papp-token>

Public access (no authentication)

$ otoroshictl udp-tunnel --host myotoroshi.example.com --remote-host service.internal.com --remote-port 9999 --local-port 9999 --access-type public

Custom local address

$ otoroshictl udp-tunnel --host myotoroshi.example.com --tls --remote-host dns.internal.com --remote-port 53 --local-host 0.0.0.0 --local-port 1053 --access-type apikey --apikey-client-id my-client-id --apikey-client-secret my-client-secret

Differences from the TCP tunnel

TCP tunnelUDP tunnel
WebSocket connectionsOne per TCP connectionOne shared, persistent
ReconnectionPer connectionAutomatic (every 2 s)
FramingRaw binary bytesJSON {"address", "port", "data"}
StateConnection-orientedStateless / connectionless
Aliasttut

Authentication types

--access-typeMechanismRequired flags
apikey (default)Authorization: Basic base64(clientId:clientSecret) header--apikey-client-id, --apikey-client-secret
bearer / jwtAuthorization: Bearer <token> header--bearer-token
sessionpappsToken=<token> query parameter--session-token
publicNo authentication

When to omit --remote-host / --remote-port

--remote-host and --remote-port are optional. You can omit them when the Otoroshi route target already contains the destination — for example when the target URL uses the ${remote_host} or ${remote_port} placeholders. In that case the values are resolved by Otoroshi itself and do not need to be passed on the command line.

Command usage

$ otoroshictl udp-tunnel -h
Tunnel UDP traffic through an otoroshi cluster

Usage: otoroshictl udp-tunnel [OPTIONS] --host <HOST>

Options:
      --host <HOST>
          The Otoroshi routing hostname (e.g. myotoroshi.example.com or myotoroshi.example.com:9999)
      --tls
          Use TLS (WSS) for the WebSocket connection to Otoroshi
      --local-host <LOCAL_HOST>
          Local address to listen on [default: 127.0.0.1]
      --local-port <LOCAL_PORT>
          Local port to listen on [default: 1053]
      --remote-host <REMOTE_HOST>
          Remote host that Otoroshi should forward traffic to (optional if the Otoroshi target uses ${remote_host})
      --remote-port <REMOTE_PORT>
          Remote port that Otoroshi should forward traffic to (optional if the Otoroshi target uses ${remote_port})
      --access-type <ACCESS_TYPE>
          Authentication type: apikey, bearer, session, or public [default: apikey]
      --apikey-client-id <APIKEY_CLIENT_ID>
          API key client ID (for apikey access type)
      --apikey-client-secret <APIKEY_CLIENT_SECRET>
          API key client secret (for apikey access type)
      --bearer-token <BEARER_TOKEN>
          Bearer token - JWT or OAuth access token (for bearer access type)
      --session-token <SESSION_TOKEN>
          Private apps session token - papp token (for session access type)
  -h, --help
          Print help