Relay raw TCP packets across NAT layer

Vasu Sharma
5 min readDec 10, 2020

If you are making an application which requires to connect to some device on the internet over TCP connection , while you are behind a NAT ( or your application is running on localhost ), and exchange TCP raw packets between the devices, then you have landed at right place ……..

While there are quite a lot of methods available for packet forwarding for http, the resources and options for forwarding of raw packets are quite limited. So, lets explore one of the method I found working for me here.

What will we be doing here ? An Overview……..

For this, we will of course require a remote machine with public IP which will connect with devices over the internet and forward packets on some port( say relay port ) on the same machine.Then, we will start an SSH tunnel between our local machine ( on which our indented application is running ) and the remote machine with public IP to forward the packets to us.

Seems Simple ? Lets dig deep into the process……

Before we move on, I would like to point out that this is quite analogous to a reverse proxy flow. Read more here. Here, the remote machine with public IP will acts as a reverse proxy server for us and forward our requests to the local web server(or servers) behind NAT layer, thus intercepting requests from clients.

Requirements :

  • A machine with public IP — probably access to an Amazon ec2 instance will work.
  • Little knowledge of NginX and SSH

Step 1 : Forwarding TCP raw packets to the relay port

Wondering what this relay port is ? This is the port with which the local machine running our application,and listening for requests on some port, will establish SSH tunnel to. Yes you connected it right… We will be forwarding packets from (and to) outside world through this relay port to the local machine. And for this we will be using NginX, for forwarding our packets.

First of all move to the remote server with public IP,and install NginX on the machine. For installation guide, move here.

Next, we will configure NginX for our needs. For this we will be editing nginx.conf file in /etc/nginx directory. Here are the steps :

cd /etc/nginx
sudo vim nginx.conf

On the top of the file, below the include and other stuff, we will be adding a few lines for starting our server and packet forwarding. Lets understand the process first…..

NginX, being a web server, will listen for requests from outside world for us, to which the clients of our application on internet can connect to.

So, we need to decide a port on which NginX will listen for requests for us. This is the port you will be advertising to the outside world to connect to your app. I am choosing it to be 6887 here.

Next, you need to decide the relay port we have been talking about since long. This is the port on which packets to the already decided external port (6887 here) will be forwarded to and from. I am choosing it to be 5000 here.

stream {
upstream backend {
server 127.0.0.1:5000;
}
server {
listen 6887;
proxy_pass backend;
}
}

Now, what if you want to forward the packets from the external port to multiple relay ports and setup multiple ssh tunnels, just for load balancing or what-ever other requirement,we can cover this easily here ……this can be done by just adding line given below to the upstream backend block.

server 127.0.0.1:<port_number>

So, you may add server 127.0.0.1:5001, server 127.0.0.1:5002 and multiple lines with whatever ports you want !! So the final nginx.conf file will look like this —

nginx.conf file

Here we complete configuration of NginX. Now, save and close the file and then restart nginx for changes to take effect.

sudo service nginx restart

Step 2 : Set up SSH Tunnel

This step is just executing a line of command in the terminal on machine where your application is running locally. Lets call the port on which your app is listening for connection requests as internal port. (I am choosing it as 8000 for demo ). This the port through which the communication between the relay port decided earlier and your application will take place. So lets set up SSH tunnel to bring about this communication —

Fire up your terminal and type in —

ssh -R <relay_port>:localhost:<internal_port> -N <username>@<remote_machine_ip>

Here relay port is 5000, internal port is 8000. Username and IP address is of the remote server , the one bearing the public IP in the whole process.Here I am assuming that open ssh server is running on the remote machine and you can connect to it remotely.

For example, when all the values substituted, the whole command may look like —

ssh -R 5000:localhost:8000 -N vasusharma7@10.10.10.10

And thats it ! We are done. Just leave this terminal open while your app is running.

Now you can advertise to your clients , your IP as the public IP of the remote machine which is listening for requests on external port for you. The packets will be eventually forwarded to the relay port by NginX. And our SSH tunnel is relaying packets from the relay port to a port on local machine on which our app is listening.

Where did I use it ?

I used it while making my Torrent Client. As I was caught up in the NAT world, it wasn’t possible for other peers to connect to my client deep inside the NAT. So, I setup NginX on my Amazon ec2 instance with public IP to forward raw TCP packets to my torrent client running locally and I was able to seed to the clients while still being behind NAT layer. I also advertised the public IP of my Amazon ec2 instance to the tracker in the announce request, so that other peers are able to connect to me through this IP.

What did we achieve ?

Ok ! Long story in short……Now the users outside NAT will connect to our app using the public IP of the remote machine and external port advertised to them. NginX will forward the packets to the relay port to which we have established an SSH tunnel to forward those packets to our application.

This is basically something like STUN server kind of functionality using simple tools like SSH and NginX.

--

--