Reverse tunneling / port forwarding with Node.js

0
38

Introduction

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

Background

Time to time I have to connect via RDP, SSH, proxy, etc. to several machine inside private network. Sadly, tools like TeamViewer, Hamachi, SSH tunneling, VPN are blocked there… 

So I decided to build a Node.js app to fulfil 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 minimal config file, I’ll provide two examples: for people who has SSH, another one – for guys with browser 🙂

Create .env file with content:

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

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!

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 yours 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 free container on AWS.

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 client, 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)

.env file per agent

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

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:

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

Let’s take a look at this example:

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

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

Let me try to explain what each part of 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 a 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 connects 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 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

Initial version.

LEAVE A REPLY