This small guide tells you how to send UDP traffic via SSH using tools that come standard (ssh,nc,mkfifo) with most UNIX-like operating systems.
Performing UDP tunneling through an SSH connection
Step by step
Open a TCP forward port with your SSH connection
On your local machine (local), connect to the distant machine (server) by SSH, with the additional -L option so that SSH with TCP port-forward:
local# ssh -L 6667:localhost:6667 server.foo.com
This will allow TCP connections on the port number 6667 of your local machine to be forwarded to the port number 6667 on server.foo.com through the secure channel.
Setup the TCP to UDP forward on the server
On the server, we open a listener on the TCP port 6667 which will forward data to UDP port 53 of a specified IP. If you want to do DNS forwarding like me, you can take the first nameserver's IP you will find in /etc/resolv.conf. But first, we need to create a fifo. The fifo is necessary to have two-way communications between the two channels. A simple shell pipe would only communicate left process' standard output to right process' standard input.
server# mkfifo /tmp/fifo
server# nc -l -p 6667 < /tmp/fifo | nc -u 192.168.1.1 53 > /tmp/fifo
This will allow TCP traffic on server's port 6667 to be forwarded to UDP traffic on 192.168.1.1's port 53, and responses to come back.
Setup the UDP to TCP forward on your machine
Now, we need to do the opposite of what was done upper on the local machine. You need priviledged access to bind the UDP port 53.
local# mkfifo /tmp/fifo
local# sudo nc -l -u -p 53 < /tmp/fifo | nc localhost 6667 > /tmp/fifo
This will allow UDP traffic on local machine's port 53 to be forwarded to TCP traffic on local machine's port 6667.
Enjoy your local DNS server :)
As you've probably guessed it now, when a DNS query will be performed on the local machine, e.g. on local UDP port 53, it will be forwarded to local TCP port 6667, then to server's TCP port 6667, then to server's DNS server, UDP port 53 of 192.168.1.1. To enjoy DNS services on your local machine, put the following line as first nameserver in your /etc/resolv.conf:
nameserver 127.0.0.1
I faced another problem with this: the pipe and fifo are buffered so frame boundaries are lost. So many UDP frames can be concatenated into one TCP frame. – Julio Guerra – 2015-07-20T14:47:22.280
Mac OS Sierra's
nc
complains about thissudo nc -l -u -p 53 </tmp/dnsfifo | nc localhost 6866 > /tmp/dnsfifo
saying "nc: missing port with option -l" but oddly each command without the pipe works fine, so it's pretty odd. Of course they don't actually function if they don't pipe to each other so... – dlamblin – 2017-08-18T01:56:21.160I had to install the homebrew netcat
brew install netcat
to get around the MacOS whack-a-do problem. But then I could not get OpenVPN traffic through, Maybe the boundary issue? – Sukima – 2019-01-10T15:01:51.89728This solution is not safe. TCP streams are not guaranteed to preserve message boundaries, so a single UDP datagram may be split in parts, breaking any protocol. – Juho Östman – 2011-08-08T15:50:01.613
2It could also be nice to use port 1153 instead of 6667 (from man SSH example), which is used by IRC. – phil pirozhkov – 2012-05-30T14:04:49.830
1@JuhoÖstman Thanks for pointing out this pitfall. Being aware of the problem... have you run accros a solution? would small enough messages be a way to make it likely to work? – humanityANDpeace – 2013-03-29T07:42:10.063
4The solution would be to prepend a length to each packet before they are sent through the TCP stream, and reconstruct the original packets from that. It would easy to write a C script to do that, but I am not sure if a readily-available solution exists. In practice, the TCP usually seems to preserve message boundaries in this case, but strange failures can occur at any time. – Juho Östman – 2013-04-23T09:33:43.027