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...
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
git clone https://github.com/mgrybyk/node-tunnel.git
cd node-tunnel
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.
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:
agentSocket.pipe(clientSocket)
clientSocket.pipe(agentSocket)
Let's take a look at this example:
In an example with SSH, the following happens:
- SSH client connects to client (that is listening on some port)
- Client forwards all data to server
- Server knows that it is required to forward data to specific agent
- 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:
- On new connection to client server, a new connection opens to agent dedicated server
- 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 - Once agent is connected - dedicated server pipes agent to client and back, and removes data listeners of client and agent sockets
- Dedicated server sends notification to client saying that pipe is created and we are ready to go
- 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