ngrok--: ngrok but worse!

I’ve been a big fan ngrok ever since I’ve stumbled on it accidentally. It makes it dead simple to share whatever you’re working on locally without having to go through the motions of putting it somewhere accessible over the internet. You just run your local app, then ngrok and voilà! It gives you a public URL that magically connects to your local server!

The way it punches through the Firewall/NAT has always intrigued me and honestly, they aren’t exactly keeping it a secret either. What better way than to satisfy that curiosity than build one?

Introducing: ngrok--, it’s like ngrok, but worse!

Prerequisites

Before we dive into ngrok--, let’s run through first how the tunneling aspect is achieved. The diagram below illustrates the step-by-step process:

Note: This is a simplified version of how ngrok works. We’ll only be limiting the scope of ngrok-- to tunneling a single local web app so we’ll be skipping some steps described in the link.

                           5
+-------------------------------------------------+
| |
| +------------------+ |
| | 4 | |
| | | |
| | +----|----------------|------+
v v | v v |
+----+ 2 +------+ | +------+ +---------+ |
|user|------>|server| | |client|<----->|local app| |
+----+ +------+ | +------+ 4 +---------+ |
^ | ^ |
| +----|-----------------------+
| 1 | Firewall/
+------------------+ NAT
3
  1. The server comes online and waits for a connection from the client
  2. The client establishes a control connection with the server
  3. The server waits for a public user to connect
  4. When a user connects, the server tells the client (over the control connection) to create a proxy connection.
  5. The client establishes a proxy connection with the server and a private connection with the local app
  6. The server and client joins all the connection together, forming a logical connection between the user and the local app

Implementation

To keep this entry brief, I’ve set up a repository the contains the code for ngrok-- along with accompanying walkthroughs. I’ve also split the implementation steps into 3 stages (4 if we count the proof of concept connection piping experiment utilized throughout the project) since I think it would be easier to grasp the implementation by slowly iterating over rather than dumping everything in one go. The stages are as follows:

  1. Stage 1 - We implement the base server and client code. We also include the control connection implementation with a simple ping/pong test to check.

  2. Stage 2 - Here, we implement the public and proxy connection feature. To test, the client simply echoes back every data sent by the user.

  3. Stage 3 - We now establish the private connection between the client and target local app and add our finishing touches.

Results

To check whether our implementation works as expected:

  1. Spin up an instance from your favorite cloud provider and run the server code there

  2. Run your test app and the client code on your machine

  3. Point a browser to <Server IP>:60624 and realize that you are routing through the internet just to access your locally available app! Bonus points for spinning up an instance as far away from you as possible.