Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / Node.js

Reverse Tunneling / Port Forwarding with Node.js

5.00/5 (7 votes)
8 Jul 2017CPOL6 min read 36.9K  
Connect to PC with no public IP using Node.js app for reverse port forwarding / tunneling

Introduction

An app allows you to forward specific port of some machine inside network with no public IPs.

Background

From time to time, I have to connect via RDP, SSH, proxy, etc. to several machines inside private network. Sadly, tools like TeamViewer, Hamachi, SSH tunnelling, VPN are blocked there...

Image 1

So I decided to build a Node.js app to fulfill my needs.

Using the Code

Running Locally

I'll start with a minimal amount actions required to run app and play around with it.

Prerequisites: Node.js (tested with 8), git client

  1. git clone https://github.com/mgrybyk/node-tunnel.git
  2. cd node-tunnel
  3. npm install

Now, let's create a minimal config file. I'll provide two examples: for people who have SSH, another one - for guys with browser. :)

Create .env file (its full file name ".env", not an extension!) with content:
NOTE: ".env" file should be created in project root folder, in this case: node-tunnel

For SSH (you may change host/port to any other if you want):

N_T_AGENT_DATA_HOST=localhost
N_T_AGENT_DATA_PORT=22
N_T_CLIENT_PORT=2222

For http (you may change host/port to any other; most likely website won't work due to certificates and other issues, but it's enough as an example):

N_T_AGENT_DATA_HOST=inplainsite.org
N_T_AGENT_DATA_PORT=80
N_T_CLIENT_PORT=8000

We are ready to launch it! Start three terminal windows because we need to start 3 node instances, and run:

node server
node agent
node client

If you've chosen SSH way, connect to localhost:2222 -> ssh -p 2222 localhost
If you've chosen http way, open your browser and visit localhost:8000

Yeah! This is the minimal working example. All your traffic goes through client->server->agent and back.

Real Life Case, Two PCs

In this example, we have such situation. You have PC with public IP and another one without it. Goal - connect to it with SSH/RDP/whatever.

Let's start with cloning repo and installing modules on each machine.

Now, go to remote PC and create .env file. In this example, I'm using RDP port, feel free to change to anything else.

N_T_SERVER_HOST=ip of machine with public ip
N_T_SERVER_PORT=1337
N_T_SERVER_PORTS_FROM=1338
N_T_SERVER_PORTS_TO=1340

N_T_AGENT_DATA_HOST=localhost
N_T_AGENT_DATA_PORT=3389

Now, start agent here: node agent

Switch to local PC, and create .env file:

N_T_SERVER_HOST=localhost
N_T_SERVER_PORT=1337
N_T_SERVER_PORTS_FROM=1338
N_T_SERVER_PORTS_TO=1340

N_T_CLIENT_PORT=8000

Now, start server and client here:

node server
node client

Once you are done - you can connect with RPD client to localhost:8000 that will open connection to remote PC.

Real Life Case, two PCs and Server

That looks great but what if your local PC has no public IP? You have to forward all traffic though some PC with public IP. If you haven't such - you may create a free container on AWS.

Image 2

Let's do it! As usual, install node.js, clone repo and install modules on each machine.

Go to PC with public IP (server) and create .env file there:

N_T_SERVER_HOST=localhost
N_T_SERVER_PORT=1337
N_T_SERVER_PORTS_FROM=1338
N_T_SERVER_PORTS_TO=1340

Great, run server: node server

On remote PC, create .env file. In this example, I'm using SSH port, but you can change host/port to anything you want. Also, I would give some name to agent (that should match with client name).

N_T_SERVER_HOST=ip of machine with public ip
N_T_SERVER_PORT=1337

N_T_AGENT_DATA_HOST=localhost
N_T_AGENT_DATA_PORT=22
N_T_AGENT_NAME=test-ssh

Ready to launch agent: node agent

Switch to client PC and create .env file. Client name should match agent name.

N_T_SERVER_HOST=ip of machine with public ip
N_T_SERVER_PORT=1337

N_T_CLIENT_PORT=8000
N_T_CLIENT_NAME=test-ssh

Finally, start client: node client

Cool. Now we can open SSH connection to localhost:8000 that will open ssh connection to remote PC.

We have created tunnel though server machine, as we, actually, did before. All the data goes like this:

ssh client -> client -> server -> agent -> ssh server

and back ssh server -> agent -> server -> client -> ssh client

There is no direct connection between agent and client.

More Agents and Clients

Multiple agents and clients can go through one server. Example:
You can run one agent to handle SSH, another to handle RDP. Please note that each agent should have name (N_T_AGENT_NAME).

Each agent may work with multiple clients, so you can run client on your machine and others to go to specific agent. Don't forget to specify which agent client should use by providing name (N_T_CLIENT_NAME).

Multiple .env Files

If you need to run multiple agents/clients/servers, you may create multiple .env files, example:

.env.rdp
.env.ssh
.env.test

Having such .env.* files, you can start server/client/agent by passing .env file name as argument:

node server .env.rdp
node agent .env.ssh
node client .env.test

NOTE: ".env" file should be created in project root folder, in this case: node-tunnel.

How It Works?

Core stuff here: Net that allows to create stream-based TCP servers/clients and stream pipes.
To forward data from one socket to another and back - I simply piped them like this:

JavaScript
agentSocket.pipe(clientSocket)
clientSocket.pipe(agentSocket)

Let's take a look at this example:

Image 3

In an example with SSH, the following happens:

  1. SSH client connects to client (that is listening on some port)
  2. Client forwards all data to server
  3. Server knows that it is required to forward data to specific agent
  4. Agent opens connection to SSH server and forwards data from server to it

    The response from SSH server goes back to agent, server, client and reaches SSH client finally.

Let me try to explain what each part of the app does.

Server

  • By default, server listens for clients and agents connections
  • Once new agent connects - server creates a dedicated server for it
  • Once new client connects - server notifies it that there is a dedicated server for agent and its port
  • Client and agent names should match in order to start data forwarding, agent with same name are not allowed
  • There may be multiple agents, and there may be multiple client per agent (there may be a low of clients with same name)

Dedicated server for agent behaves this way:

  1. On new connection to client server, a new connection opens to agent dedicated server
  2. Once client connected - a notification to agent is sent so it can open connection to server
    in the meanwhile: client socket is now stored and waiting for agent socket
  3. Once agent is connected - dedicated server pipes agent to client and back, and removes data listeners of client and agent sockets
  4. Dedicated server sends notification to client saying that pipe is created and we are ready to go
  5. Done!

Client

Client creates server and listens for incoming connections on port provided in .env file.

On new connection, client starts forwarding data to server (see server section for details).

Agent

Agent waits for notification regarding client connection. When such happened - agents connect to host:port specified in .env file and forwards it to server.

What's Next?

Encryption! Currently data is not encrypted, it's not a problem for SSH, RDP but is an issue for plain text protocols.
Only service messages are encrypted at the moment.

And, of course, fix some defects, cleanup code, increase stability.

If you have any ideas - feel free to share. :)

As a Conclusion

Thank you for reading!

I hope it was somehow interesting, somewhat understandable and maybe even useful. :)

Points of Interest

Helpful module when working with pipes may be though2.
With this module, you pass data though your worker and do stuff like: logging, error handling or changing data, let's say change some keyword (ebay) to another one (amazon), or change all 0 to 1 . :)

History

  • 8th July, 2017: Initial version, fixed some typos

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)