Skip to main content
Mole with UDP

UDP Relay

Tunnel UDP traffic through your mesh - DNS queries, NTP, and other UDP protocols. Resolve DNS through a remote network or route UDP-based applications through your tunnel.

# DNS query through the mesh
proxychains4 dig @8.8.8.8 example.com

# Any UDP-capable SOCKS5 client works
socksify nslookup internal.corp 10.0.0.53

How It Works

UDP datagrams flow through your mesh just like TCP - encrypted end-to-end from ingress to exit.

All UDP datagrams are encrypted end-to-end between ingress and exit using ChaCha20-Poly1305.

Requirements

UDP relay requires:

  1. Ingress agent with SOCKS5 enabled
  2. Exit agent with UDP relay enabled
  3. A route from ingress to exit
Configuration

See UDP Configuration for exit node options and SOCKS5 Configuration for ingress setup.

UDP Relay Binding

The UDP relay socket binds to the same IP address as the SOCKS5 TCP listener. If SOCKS5 listens on 127.0.0.1:1080, UDP relay sockets will bind to 127.0.0.1. If SOCKS5 listens on 0.0.0.0:1080, UDP relay sockets will bind to 0.0.0.0 (all interfaces).

Usage

DNS Queries

Use standard DNS tools through the SOCKS5 proxy:

# Using proxychains
proxychains4 dig @8.8.8.8 example.com

# Using socksify
socksify dig @8.8.8.8 example.com

Python Example

import socket
import struct

# Connect to SOCKS5 proxy
proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
proxy.connect(('127.0.0.1', 1080))

# SOCKS5 greeting (no auth)
proxy.send(b'\x05\x01\x00')
proxy.recv(2) # Method selection

# UDP ASSOCIATE request
proxy.send(b'\x05\x03\x00\x01\x00\x00\x00\x00\x00\x00')
reply = proxy.recv(10)

# Parse relay address
relay_ip = socket.inet_ntoa(reply[4:8])
relay_port = struct.unpack('>H', reply[8:10])[0]

# Create UDP socket
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Build SOCKS5 UDP header + DNS query
# RSV(2) + FRAG(1) + ATYP(1) + ADDR + PORT(2) + DATA
header = b'\x00\x00\x00\x01' # RSV, FRAG=0, ATYP=IPv4
header += socket.inet_aton('8.8.8.8') # Destination IP
header += struct.pack('>H', 53) # Destination port
header += dns_query # Your DNS query bytes

# Send through relay
udp.sendto(header, (relay_ip, relay_port))

# Receive response
response, _ = udp.recvfrom(4096)
dns_response = response[10:] # Skip SOCKS5 header

Limitations

LimitationValueDescription
Max datagram size1472 bytesMTU minus IP/UDP headers
FragmentationNot supportedFragmented datagrams are rejected
Association lifetimeTied to TCPEnds when TCP control connection closes

No Fragmentation Support

UDP datagrams with FRAG > 0 are rejected. Most applications do not use SOCKS5-level fragmentation. If you encounter issues, check if your application is fragmenting at the SOCKS5 layer.

End-to-End Encryption

UDP traffic is encrypted between ingress and exit:

  1. Ingress generates ephemeral X25519 keypair
  2. Exit generates ephemeral X25519 keypair
  3. Both derive shared secret via ECDH
  4. Each datagram encrypted with ChaCha20-Poly1305

Transit nodes cannot decrypt UDP payloads.

Troubleshooting

UDP ASSOCIATE Fails

Error: UDP relay is disabled
  • Verify exit node has udp.enabled: true
  • Check that a route exists to the exit node
  • Ensure exit node is connected to the mesh

Datagram Too Large

Error: datagram too large
  • Reduce payload size to under 1472 bytes
  • Check udp.max_datagram_size configuration

Association Timeout

UDP associations expire after idle_timeout (default 5 minutes) of inactivity. Send keepalive datagrams if needed.

Security Considerations

  1. Authentication: Use SOCKS5 authentication to control access
  2. Monitor associations: Check max_associations limit
  3. Timeouts: Use appropriate idle_timeout values