Setting up a gateway with SSH

A combination of remote and local port forwarding to set up a gateway with SSH

With the help of a combination of remote and local port forwarding, it is possible to establish connections between computers that cannot be reached directly with one another. No "open" gateway is required for this.

Another article in this blog described how remote port forwarding can be used to make services in the home network available for access from the Internet. This requires access via SSH on a computer on the Internet, which is basically obtained via every VPS (Virtual Private Server).

The problem with this is that the SSH server has to work as an open gateway, which is usually deactivated in the standard configuration of an SSH server.

This article shows how you can avoid setting up an open SSH gateway with a combination of remote and local port forwarding.

The solution described here only works for users who have SSH access to the server. Another article introduces a solution that makes access available to everyone and yet dispenses with setting up an open gateway.

Example: Accessing the internal website of a Pi-hole

The Pi-hole program should run on a Raspberry Pi in a home network. Pi-hole blocks all types of Internet advertising in the home network by resolving DNS requests and simply ignoring known domain names from advertising providers. This Raspberry Pi will be called Cadamosto in the following.

The Pi-hole program includes a web interface via which the program can be configured and which simultaneously displays statistics and logs in an appealing and clear form in a dashboard. When installing the Pi-hole, a lighttpd web server is also installed that accepts requests on port 80.

The following figure shows the dashboard of the Pi-hole, which can be called up with http://localhost/admin/ on the Raspberry Pi:

Pi-hole dashboard on Cadamosto

The aim is to be able to access this dashboard from outside the home network, e.g. when you're on the go. The solution is to set up a remote port forwarding on the Raspberry Pi and a local port forwarding on the computer that is outside of the home network.

In order for this combination of remote and local port forwarding to be possible at all, a computer is required on the Internet - for example a VPS from any provider - on which you can log in via SSH. This server will be called Cayenne.

Setup of remote port forwarding

The first task is to set up a remote port forwarding from Cadamosto to Cayenne:

uwe@cadamosto:~ $ ssh -fN -o ExitOnForwardFailure=yes -R 8080:localhost:80 Cayenne

Cayenne is here an alias for the full host name of the server Cayenne.

The command sets up port forwarding as a background process from port 8080 on Cayenne to port 80 on Cadamosto. Since the forwarding is set up from Cadamosto, but leads from Cayenne back to Cadamosto, it is called remote port forwarding (sometimes also known as reverse port forwarding).

A free port on Cayenne must be selected to start port forwarding. In the example used here, it is assumed that a web server is already running on port 80 on Cayenne, so port 8080 is selected.

The forwarding can be seen on Cadamosto (Raspberry Pi) if you will display all processes by running the ps command:

uwe@cadamosto:~ $ ps -x -o pid,cmd
  PID CMD
 4585 sshd: uwe@pts/2
 4588 -bash
 7855 ps -x -o pid,cmd
14072 /lib/systemd/systemd --user
14075 (sd-pam)
14871 ssh -fN -o ExitOnForwardFailure=yes -R 8080:localhost:80 Cayenne
uwe@cadamosto:~ $

To end the forwarding one would send a SIGTERM to the PID, e.g. with kill 14871.

It would of course be an advantage to be able to start and end the forwarding with a single command, preferably with the option of querying the current status. This can be achieved with the ReverseTunnel bash script below:

#!/bin/bash
# Script to start or stop remote port forwarding (rpf)
# Exit code is 1 if rpf is established, 0 if not
#
# Usage: ReverseTunnel (start|stop|status)

RHOST="server.example.com" # Remote Host (Cayenne)
RPORT="8080" # Remote Port on Remote Host
RUSER="$USER" # Remote User (here: same as local)
LHOST="localhost" # Local Host (Cadamosto)
LPORT="80" # Local Port on Local Host
SSHOPT="-fN -o ExitOnForwardFailure=yes" # Options for ssh

# Define the command to establish rpf
CMD="$(which ssh) $SSHOPT -R $RPORT:$LHOST:$LPORT $RUSER@$RHOST"

# Define a function to get the PID for a specific command
function getPID {
  pid=$(ps x -o user,pid,cmd | grep "$1" | grep -v "grep" | tr -s " " | cut -d" " -f2)
  [[ $pid ]] && echo $pid
}

# Get PID of $CMD (will be empty if rpf is not established)
PID=$(getPID "$CMD")

case $1 in
  start) # If parameter start is given

    # If PID exists then a rpf is already established
    if [ $PID ];then
      echo "Reverse tunnel already established!"
      echo "PID $PID ($RHOST:$RPORT -> $LHOST:$LPORT)"
      exit 1
    fi

    # Otherwise we can establish it
    $CMD > /dev/null 2>&1
    RETURNCODE=$?

    # Check the returncode and inform about new status
    if [ $RETURNCODE == 0 ];then
      PID=$(getPID "$CMD")
      echo "Reverse tunnel from $RHOST:$RPORT -> $LHOST:$LPORT established"
      echo "PID $PID"
      exit 1
    else
      echo "Reverse tunnel could not be established!"
      echo "Return code $RETURNCODE"
      exit $RETURNCODE
    fi
    ;;

  stop) # If parameter stop is given

    # If PID does not exist then a rpf is not established
    if [ -z $PID ];then
      echo "Reverse tunnel not established!"
      exit 0
    fi

    # Otherwise we can stop it by sending the SIGTERM to its PID
    kill $PID
    RETURNCODE=$?

    # Check the return code and inform about status
    if [ $RETURNCODE == 0 ];then
      echo "Reverse tunnel from $RHOST:$RPORT -> $LHOST:$LPORT terminated"
      exit 0
    else
      echo "Reverse tunnel could not be terminated!"
      echo "Return code $RETURNCODE"
      exit $RETURNCODE
    fi
    ;;

  status) # If parameter status is given

    # If PID exists then a rpf is already established
    if [ $PID ];then
      echo -n "Reverse tunnel already established with PID $PID"
      echo " from $RHOST:$RPORT -> $LHOST:$LPORT"
      exit 1
    fi

    # If PID does not exist then a rpf is not established
    if [ -z $PID ];then
      echo "Reverse tunnel not already established"
      exit 0
    fi
    ;;

  *) # If none or any other parameter is given, print usage info and exit
    echo "Usage: $(basename $0) (start|stop|status)" && exit 125
    ;;
esac

With the command ReverseTunnel start you can activate remote port forwarding and deactivate it with ReverseTunnel stop. ReverseTunnel status can be used to query the current status of port forwarding.

On the VPS Cayenne you can see from the list of open ports that this forwarding exists - but unfortunately not where it leads to or from where it was set up:

uwe@cayenne:~$ sudo netstat -ltpn | grep 8080
tcp        0      0 127.0.0.1:8080      0.0.0.0:*       LISTEN      25789/sshd: uwe     
tcp6       0      0 ::1:8080            :::*            LISTEN      25789/sshd: uwe     
uwe@cayenne:~$

The netstat command is called here with sudo, otherwise not all details are displayed.

As you can see, only the locally accessible port 8080 is forwarded by Cayenne. It is not possible to use port forwarding from outside Cayenne. This is the disadvantage (or, from a security point of view: advantage) of the standard setting described, that the SSH server does not form an open gateway.

However, that would not correspond to the goal of the website being accessible while on the move.

You could of course now establish an SSH connection to Cayenne in a terminal, but you could not call up the website in a terminal and if so - e.g. with lynx - this would not display properly. The aim is still to call up the dashboard with a normal browser on the computer you are currently working with.

Setting up the local port forwarding

To do this, a local port forwarding to Cayenne must now be set up on the computer with which the website is to be called up. In the following, this is done on a laptop called Caboto. Since a web server is already running on Caboto and occupying port 80, port 8008 is used for port forwarding:

uwe@Caboto:~$ ssh -fN -o ExitOnForwardFailure=yes -L 8008:localhost:8080 Cayenne

The command sets up port forwarding as a background process from port 8008 on Caboto to port 8080 on Cayenne. Since the setup is done from Caboto and leads from Caboto to Cayenne, this is called local port forwarding.

You can also see the background process on Caboto using ps and terminate it with a SIGTERM to the PID if necessary. Of course, a script could also be created here that fulfills both tasks.

This time the open port is not to be seen on Cayenne but on Caboto and as you can see, the port forwarding can only be reached via the local interface too:

uwe@Caboto:~$ sudo netstat -ltpn | grep 8008
tcp        0      0 127.0.0.1:8008      0.0.0.0:*       LISTEN      5813/ssh            
tcp6       0      0 ::1:8008            :::*            LISTEN      5813/ssh            
uwe@Caboto:~$

Now you can enter the address http://localhost:8008/admin/ in a browser on Caboto and see the dashboard of the Pi-hole on Cadamosto:

Pi-hole dashboard on Cadamosto viewed from Caboto

As you can see, you would have to log in to see the full dashboard, which incidentally is not a problem despite the supposedly unencrypted connection (http://...). This is because opening localhost: 8008 on Caboto causes an immediate forwarding through the existing SSH connection to Cayenne. And there the request is directed to the local port 8080 and in turn forwarded directly to Cadamosto via the existing SSH connection, where it ends up on the local port 80.

In other words, the connection is encrypted the whole way and there is no need to additionally secure the website with a certificate.

The following figure shows the whole construction again schematically:

Remote and local port forwarding in interaction

A remote port forwarding is set up from the Raspberry Pi Cadamosto in the home network to the Cayenne VPS, which forwards requests on port 8080 on Cayenne through the router to Cadamosto on port 80.

A local port forwarding is then set up on the way from the Caboto laptop to Cayenne, which forwards requests on port 8008 on Caboto to Cayenne on port 8080 - where they are then forwarded directly to Cadamosto.

So you can now enter the address http://localhost:8008/ in a browser on Caboto and see the dashboard of the Pi-hole in the home network.

Further options

Of course, the use of this combined port forwarding is not limited to a website or websites in general. For example, SSH access to the Raspberry Pi could also be released to the outside by replacing the local port 80 with 22 and the remote port 8080 with 2222 in the ReverseTunnel script - the latter, because there is already a SSH server on the Cayenne VPS listens on port 22.

With an appropriately adapted local port forwarding one would then have direct access to a console on the Cadamosto home network computer from anywhere.

Top