Get better routing and latency with UDP SSH Tunneling

Submitted by rdv on Wed, 05/16/2018 - 10:40


In the example that follows, I convert a UDP connection from local port 9100 to remote port 9200 into one that routes through an SSH tunnel over TCP port 1337 on both machines. Your application which normally expects to connect to your.other.machine:9200 instead connects and sends data to localhost:9100, and a series of tunnels will eventually take it to your.other.machine:9200.

Create the SSH tunnel.

$ ssh -L 1337:localhost:1337 your.other.machine


  • This sets up an SSH tunnel over TCP from your local machine to another. When you send data to localhost:1337 over TCP, it will forward it via SSH to the remote machine's TCP port 1337.
  • You can replace "1337" with a port of your choice. Note that choosing a port < 1024 requires root privileges.

Set up the bidirectional UDP<->TCP forwarding on the local machine.

First, make a named pipe:
$ mkfifo /tmp/udp2tcp

Then use netcat to send and receive on this pipe:

$ netcat -l -u -p 9100 < /tmp/udp2tcp | netcat localhost 1337 > /tmp/udp2tcp


  • This command is quite subtly complicated. Let's examine all the file descriptors involved.
  • The first netcat process sits on UDP port 9100, and prints whatever data it receives there to its standard output.
  • The second netcat process takes whatever it gets on standard input and sends it to the local machine's TCP port 1337, where the SSH tunnel is listening. The SSH tunnel encrypts this data and sends it to the other machine over TCP.
  • The second netcat process is also listening on TCP port 1337, i.e. the local machine's receiving end of the SSH tunnel, and it'll route whatever it gets from the tunnel to its standard output.
  • The first netcat command will also be listening for what comes on its standard input, and it'll send that to whatever client originally connected to it on UDP port 9100.

Set up redirections

  • The first netcat's standard output (#1, above) goes directly to the second netcat as standard input (#2, above).
  • Therefore, anything your application sends to localhost:9100 will be sent through #1 and #2, and then out to the remote machine via the SSH tunnel.
  • Whatever gets sent back from the SSH tunnel is picked up by the second netcat and dumped to its standard output (#3), which is connected via named pipe to the first netcat's standard input (#4).
  • Therefore, any reply that is sent via the SSH tunnel on TCP port 1337 is routed through #3 and #4, and then back to your application on UDP port 9100.
  • The named pipe is a critical piece of this setup. It is impossible syntactically to connect the second process' standard output to the first's standard input, so a named pipe must be used. If this second connection was not made, your application would never receive replies, because the second process would just dump those replies to the console instead of getting them to the first process (which ultimately sends it to your application), meanwhile the first process would be waiting on human input on the same console.

Set up the bidirectional TCP<->UDP forwarding on the remote machine.

As in #2, make a named pipe:
$ mkfifo /tmp/tcp2udp
Then do some more netcat voodoo:
$ netcat -l -p 1337 < /tmp/tcp2udp | netcat -u localhost 9200 > /tmp/tcp2udp



  • This hookup works the same way as in the above step: one process listens on whatever comes out of the tunnel on TCP 1337 and forwards it to the other process, which passes it to UDP port 9200. The second process takes the response on UDP port 9200 and sends it via the named pipe to the first, and the first sends that response back down the SSH tunnel on TCP 1337.
  • Once that's set up, just point your application to localhost:9100 instead of your.other.machine:9200, and it'll get sent to the right place.



  • You may be asking this question because you want to be able to use the Steam gaming client on a network that does not have the correct UDP ports open.
  • You will still need a machine somewhere outside the restrictive network that can make those UDP connections, e.g. a machine at a private residence or a rented machine on some ISP's server room. Know also that this will adversely affect your latency, and your connection will be more susceptible to network congestion.
  • The setup is as above, except that your.other.machine (your machine on the less restrictive network) must be set up to forward connections from the SSH tunnel to the proper host. I've never sniffed my Steam connections, but let's say that it tries making a connection to In that case, instead of "localhost" in step #3's second netcat command, use "".
  • In addition, you need to trick your local machine into believing that the remote server it's trying to contact is in fact itself. This is most easily accomplished by modifying your operating system's hosts file so that it resolves "" to the local address:
  • Unfortunately, this will have to be done individually for every port Steam uses. And that's where knowing some shell scripting comes in useful.