UDP Game Forwarding Over SSH
One of SSH's most prominent features is that in addition to getting a login shell on a destination server, it can also forward ports and sockets across the secure channel and open them on the other side. For instance, the following command:
$ ssh -R *:6000:localhost:5000 user@hostname
will cause a service running on TCP port 5000 on your computer to be accessible to anyone connecting to port 6000 on the server computer. (If you want to have a more restrictive bind address, replace * with that address).
However, this is only for TCP sockets - SSH does not have built-in tools for forwarding UDP services, which are often used for games and video streaming services, and increasingly for other services where TCP's packet handling is too restrictive.
Fortunately, however, there are multiple tools that we can use to translate UDP traffic into TCP traffic and back again, we can create an intermediary TCP stream (or listening socket) that can then be forwarded over SSH in order to forward a UDP service to a remote host.
One such tool is socat(1), which is a GNU tool for connecting many kinds of bidirectional streams. To use socat to forward a UDP service running on your machine on UDP port 4000, first run this command in one terminal:
$ socat -d -d TCP4-LISTEN:5000,fork UDP4-CONNECT:localhost:4000
- Both -d flags are debug verbosity settings for both ends (add more "d"s for more verbosity)
- The TCP4-LISTEN:5000,fork option opens a TCP socket on port 5000. The "fork" bit causes the process to fork every time a new connection is received (without this, socat will terminate after the first few packets)
- The UDP4-CONNECT option connects to the UDP service on port 4000. The command as a whole means that when a packet is sent to port 4000, socat will open a connection to port 5000 to deliver the packet
Then, in another terminal on the client, type:
(client)$ ssh -R 127.0.0.1:6000:localhost:5000 user@hostname
(server)$ socat -d -d TCP4-CONNECT:localhost:6000 UDP4-LISTEN:4000,fork
This connects to SSH with the local TCP socket created by socat forwarded to port 6000 on localhost, then uses socat to translate the TCP socket to a UDP listening socket on port 4000.
At this point, anyone can now connect to port 4000 on the server and access the UDP service running on your local machine!
A blog post with more information on this strategy, used with the Simple Network Management Protocol
The socat utility works great, but unfortunately has some limitations for UDP. In particular, while it does do some amount of connection consolidation, it tends to err on the side of creating new TCP connections for each packet, which can slow performance to a crawl. In particular, I was looking for a solution to remotely forward netgames of Dr. Robotnik's Ring Racers, which uses UDP port 5029 by default - socat was not able to maintain a stable connection for long.
Dr. Robotnik's Ring Racers
I was, however, able to find a different utility called "udp-over-tcp", which is a tool by Mullvad that is more purpose-built for this situation. Instead of constantly creating new connections, it creates one TCP connection for the peer, which consists of a sequence of UDP packets prepended by their length.
udp-over-tcp
Using udp-over-tcp is similar to using socat - two terminals plus whatever your service is running in. On one terminal, type:
$ tcp2udp --tcp-listen 127.0.0.1:5000 --udp-forward 127.0.0.1:4000
And on another terminal, type:
(client)$ ssh -R 127.0.0.1:6000:localhost:5000 user@hostname
(server)$ udp2tcp --udp-listen 0.0.0.0:4000 --tcp-forward 127.0.0.1:6000
Once again, at this point, anyone can now connect to port 4000 on the server and access the UDP service running on your local machine.
udp-over-tcp does have its own limitation, in that each instance of udp2tcp can only service traffic from one peer - if you want multiple peers, you need to create multiple instances that either listen to specific addresses, or take turns with the global listening port using some forking shenanigans. Another option, though, if you know the users you're attempting to connect to, is to expose just the TCP port on the server and have each of peers run udp2tcp on their local machines to translate the traffic. (You could even have the TCP port remain private if your peers also have SSH keys to the server, as they can pull the TCP port to their local machine using the -L option).
And of course remember, for either method, to change your firewall settings to expose the UDP port on the server you're forwarding things to.
Home