Skip to content

UDP tunneling with TURN-over-Tor for Onionmasq

Micah Elizabeth Scott requested to merge beth/onionmasq:udp-tunnel into main

This merge adds experimental UDP tunneling using the TURN protocol, transported over TCP and Tor.

The basic TURN/STUN implementation comes from the 'stun' and 'turn' crates that form part of the 'webrtc' crate. On top of this, the patch adds a workspace crate 'turn-tcp-client' to carry the TURN-over-TCP protocol logic I needed that was not specific to Tor or onionmasq. This crate has a simple 'turn-dns-ping' example that can be used to test TURN-over-Tor performance separately from onionmasq proper.

The 'onion-tunnel' crate gains a 'udp-tunnel' feature, off by default, which includes the turn-tcp-client crate as well as a new 'onion_tunnel::udp_tunnel' module. This module implements storage for ongoing TURN allocations, separated by application ID, and maintains these allocations during UDP send. An optional pool of pre-built UDP tunnels, isolated per-app, can be prepared early to hide the latency of creating a new TURN allocation.

These pre-builds can happen as soon as we have a signal that the isolation ID is in use. Right now the patch triggers pre-builds when any UDP packet is sent, when a TCP socket opens, and when a DNS question is answered by the built-in fake resolver.

New functions on the TunnelScaffolding allow selecting TURN parameters each time a tunnel is opened, and generating parameters for pre-built tunnels.

Interaction with smoltcp is minimal; any UDP packets that are consumed by an active tunnel will be hidden from smoltcp, and we inject received UDP packets directly into the Device while building the headers manually. This isn't ideal, but our usage doesn't map well to 'sockets' since each UDP allocation can have an arbitrary number of peers.

UDP traffic can be sent using either a real IP address or one of the fake IPs returned by the resolver. Fake IPs need to be translated to real ones, and this uses a new features in the 'dns' module which performs a real DNS lookup in parallel with each fake one. The result of this real lookup is currently only used by the UDP tunnel. If UDP traffic needs to be sent before the real DNS completes, it will be delayed.

The incoming traffic path performs all necessary reverse mappings directly from a TURN channel number to an IP address, using the proper fake or real address depending on how the TURN channel was established.

The 'udp-tunnel' feature is available in both the onionmasq command line app and in the Android sample app. Thanks to cyBerta for implementing the Android UI part! To try this out, you'll need to enter a TURN server host:port and shared secret.

Tested with Discord calling, WhatsApp calling, QUIC, DNS lookups. Tested with Linux and with physical Android phones. Things seem to basically work, but I'm already finding application compatibility issues that may or may not be due to low overall network performance. At this point UDP performance and stability seems similar to TCP. It needs more testing, both automated and manual.

Merge request reports