Reverse-tunnel SSH through NAT: Remote-access a computer that is behind a NAT router/firewall without manually forwarding ports on the router

Using port-forwarding over an SSH connection allows one to set up a SSH reverse-proxy server, allowing SSH access to a machine that cannot receive incoming connections (e.g. behind NAT firewall/router) by proxying them through a machine which can (e.g. a cheap VPS)

Problem: Dynamic IP, NAT and a bad router

While travelling around Europe (and now, living in Estonia), I occasionally want to connect to my home PC in order to backup photos to it from my camera, or to copy music from it to my phone. Unfortunately my PC is at the family house (now that I don’t have a UK house) and they have a crappy ISP router which “forgets” its settings every time the ISP pushes some new pointless stealth update to it.  The dynamic IP problems arising from the cheap ISP are easily resolved via my Dynamic DNS package, however that isn’t even necessary for the solution in this article.

Reverse port forwarding

This means that my PC in England essentially cannot receive connections from the outside world. However it can make connections. This is where “port backwarding” or rather “reverse port forwarding” comes in.  In typical port forwarding over tunnels, you connect to some other device and messages sent to some port on your end are forwarded down the tunnel to the other end.  In a reverse port forward, you connect to some other device and instruct it to forward packets back down the tunnel to you.

When my PC is powered on, a service loads which creates a tunnel to one of my cloud servers. On the remote end of this tunnel, a port on the server is forwarded back down the tunnel to my home PC. Therefore, connections made to that port on the server are forwarded down the tunnel to my home PC. Hence, my home PC can receive connections from the outside world, as long as they are passed through the tunnel that it created to the outside world. This is similar to a VPN, but much simpler and also quick and easy to configure.  In order to prevent me from having to enter a password for SSH login every time the service starts, I use public key authentication*.  If security is not an issue, you can also create port forwarding (reverse and forward) via netcat and pipes.

* I actually use this between ALL my secured PCs and servers, and disable password login.  This vastly reduces the risk of Chinese botnets getting into my networks.  The server hosting this blog (and others) gets on average three failed login attempts per second from China, plus some from other parts of the world too.  An IPTables rule-set restricting connection initiation rates on certain ports also hardens security against these bots somewhat.

Remote wake-on-lan without VPN or DDNS

First, to power my PC on remotely, I issue a command to one of my cheap cloud servers. A service running on a Raspberry Pi in the England house creates a connection to my cloud server.  The cloud server uses this connection to notify the Pi when a “power on” command is received by the server.  The Pi uses Wake-on-lan to power my PC on – much cheaper and simpler than the 1980’s power-relay method.  My PC powers on and the “port backwarding” service starts.  This method does not require a VPN or my Dynamic DNS service.  Nor is my old remote wake-on-lan interface needed any more.

The code

The service on my home PC runs the following:
[code language=”bash”]ssh -nNTR remoteIntf:remotePort:localHost:localPort remoteUser@remoteHost[/code]
This creates an SSH tunnel between localHost and remoteHost, and forwards any packets bound for remoteIntf:remotePort on remoteHost to localHost:localPort. Using this, we can forward ports from the remote host to any host/port on our local network. In this case, the machine creating the tunnel is also the target for the port forwarding, so we have:
[code language=”bash”]ssh -nNTR localhost:9000:localhost:22 server-user@cloud-server[/code]
which listens for packets via local loopback, port 9000 on the remote host, and forwards them over the tunnel to local loopback, port 22 (SSH port) on the home PC.

I could of course use this on the Pi instead, to tunnel from Pi to server, forward from server throuh Pi to PC:
[code language=”bash”]ssh -nNTR localhost:9000:home-pc:22 server-user@cloud-server[/code]
But I see no advantage in forwarding through the Pi, and it will definitely slow things down a little. Also if I decide to reboot the Pi then I would lose my connection to the home PC via this method.

Using the first method (service on home PC initiates reverse forwarding), to create an SSH connection to my home PC, I simply log onto my cloud server via SSH/Putty and issue:
[code language=”bash”]ssh home-user@localhost -p 9000[/code]
which creates an SSH connection through local loopback to port 9000 – which is forwarded over the previously-created tunnel to my home PC’s SSH port (22).

If I bind the tunnel to the public IP of my server, then I can connect to my home PC from my SSH client here, rather than logging into my server first.

This has security risks, so to enable it you must first set the following in /etc/ssh/sshd_config:
[code language=”bash”]GatewayPorts=yes[/code]
Service on target PC:
[code language=”bash”]ssh -nNTR cloud-server:9000:localhost:22 cloud-user@cloud-server[/code]
Connection from other PC/phone/tablet:
[code language=”bash”]ssh home-user@cloud-server -p 9000[/code]
Obviously, change the user-names and server names/ports to your own preferred values, I use home-user/cloud-user/cloud-server/9000 as examples here.

Wake-on-lan remote interface

This software provides a way to wake (via wake-on-lan) machines on a remote network.

After making my home PC accessible externally via Dynamic DNS, I needed a way to remotely power it on and off, since leaving it on 24/7 would run up a large energy bill.  I took one of my (many) Raspberry Pi computers and set up a script to log the MAC addresses of all machines detected on the network, along with corresponding IP addresses and hostnames.  This script runs periodically as a cron job, providing a list of machines by name or last known IP, and their associated MAC addresses.

A web interface allows any PC from this database to be chosen, and for a wake-on-lan magic packet to be dispatched to it.  Provided the PC is connected to the network via an ethernet cable and wake-on-lan is enabled on the associated adapter, the end result is that the chosen PC will turn on in response to me clicking a button in my web browser from 40 miles away.

The PC must be wired into the network in order to receive wake-on-lan packets, but there can be wireless links between the router/switch that the PC is wired to and the Raspberry Pi.  Wake-on-LAN magic packets are sent as UDP broadcasts in my script, although they could be in any type of container that will get routed to the target PC’s ethernet port.

Download: Remote Wake-on-LAN, MAC database scripts and web interface
Latest version (github): battlesnake/macs

Dynamic DNS server

This software provides a cheap dynamic DNS solution for anyone with a Linux VPS (or a friend who is willing to share one).

I occasionally want to access my home PC while I’m away from home, but my ISP doesn’t offer static IP addressing.  Thanks to a resurgence in the popularity of virtualisation, servers are extremely cheap to rent nowadays (as little as £3/month).  In addition to the server hosting this website (and others), I run a DNS nameserver (using PowerDNS) on one of my other virtual servers.  With this simple BASH script and a web interface, my server gained dynamic DNS capabilities and allows me to access my home PC despite the varying IP address.

The packaged script and web interface run on the nameserver, and a separate script is run on the target machine (in my case, the home PC), which curl’s the web interface periodically to update the necessary A-record.  It is important for the A-record to have a small TTL value (e.g. 60 seconds), in order to allow updates to propagate through DNS caches at a reasonable speed.  Look at the code for the PHP web interface to understand the syntax for the CURL request.

Rather than leave the home PC on 24/7 and waste electricity, I also created a remote interface to dispatch wake-on-lan packets.

Download: Dynamic DNS scripts
Latest version (github): battlesnake/ddns