When the Raspberri Pi is connected via the 3G/UMTS network (how to do this) you can no longer ssh into it because 3G uses NAT. Only the Pi itself can open a connection to other machines. Fortunately we can use a feature built into ssh called “remote port forwarding” which works like this:
The Pi’s ssh client connects to your Linux desktop’s ssh server and tells it to forward connections from a local local port (e.g. 2222) back to a remote port on the Pi. In this case the remote port is 22 so we can connect to the Pi’s ssh server. The first connection is the tunnel through which the second connection in the reverse direction is established. Each side acts both as client and server.
If your desktop doesn’t run Linux you can simply use another Pi! :-) There probably are ssh servers for Windows that support port forwarding – contact me if you’re aware of one.
We’ll relax security a bit to make things work smoothly:
- On the Pi we will store the password to be used for the desktop’s ssh server in plaintext. Do not use this password for any other account. On the desktop, restrict the user account so that it can’t do anything harmful. Be aware that if someone steals or breaks into the Pi they can use these credentials to ssh into your desktop. (This would be the same for certificate based authentication, so it doesn’t help us here.)
- We’ll also disable strict host key checking on the Pi so the connection doesn’t break when your desktop’s host key changes.
Unless you have a static IP you’ll need to configure your router to use a dynamic DNS service such as noip.com (or myfritz.net if you use a FritzBox). Set up port forwarding in the router (cf. Wikipedia’s list of ports). Let’s assume your dynamic DNS address is foobar.noip.com and you are forwarding port 12345 to port 22 on your dekstop.
To tighten the setup of your desktop’s ssh server you might want to restrict the users that may log in at all. For example, to allow only the
john account add this line at the top of
Test your setup by ssh-ing from the Pi to your desktop:
ssh firstname.lastname@example.org -p 12345
sshpass on the Pi.
sshpass avoids the interactive ssh password prompt so the connection can be established automatically.
sudo apt-get install sshpass
Now initiate the reverse ssh connection from the Pi:
#!/bin/bash REMOTE_PORT=12345 REMOTE_ADDRESSemail@example.com PASSWORD="johnspassword" sshpass -p "$PASSWORD" \ ssh -o ServerAliveInterval=60 \ -o ServerAliveCountMax=2 \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=15 \ -N -R 2222:localhost:22 $REMOTE_ADDRESS -p $REMOTE_PORT
See here and here for a documentation of the options used above. The
ServerAlive... options make sure that when the desktop is shut down the connection is also shut down. This is required so that it can be re-established when the desktop is up again. The
...Host... options avoid key verification failures when your desktop’s dynamic IP changes.
On the desktop you can now connect to the Pi as user
ssh pi@localhost -p 2222
Of course we really want the Pi to initiate the connection automatically. So we wrap the above call in a loop and add an
@reboot line to the crontab to run this script at startup (the code below is copied from
reverse_ssh.sh in the ip-anemometer repository):
#!/bin/bash FWD_PORT=2222 WAIT_SECONDS=60 if [ "$#" != "3" ]; then echo "Maintain a reverse ssh connection, forwarding port $FWD_PORT on the remote machine." echo "If the connection fails or is dropped, wait $WAIT_SECONDS seconds and retry." echo "Usage: $(basename $0) [remote_user@]remote_server port password" echo "Then, on the remote ssh server: ssh $USER@localhost -p $FWD_PORT" exit fi REMOTE_ADDRESS=$1 REMOTE_PORT=$2 PASSWORD=$3 # By default the tunnel never seems to timeout. This is bad because if the connection to the # server has been established once, then the server disconnects (link down, or maybe it's a laptop # that doesn't run 24/7), it could never be re-established. So it's important to set a timeout for # the tunnel. Note that keepalive is handled transparently by ssh; it does not mean any payload data # has to be sent through the tunnel at these intervals. SERVER_ALIVE_INTERVAL=60 SERVER_ALIVE_COUNT_MAX=2 while true; do sshpass -p "$PASSWORD" \ ssh -o ServerAliveInterval=$SERVER_ALIVE_INTERVAL \ -o ServerAliveCountMax=$SERVER_ALIVE_COUNT_MAX \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=15 \ -N -R $FWD_PORT:localhost:22 $REMOTE_ADDRESS -p $REMOTE_PORT sleep $WAIT_SECONDS done
The crontab entry:
@reboot ./reverse_ssh.sh firstname.lastname@example.org 12345 johnspassword
Keep in mind that after starting up the desktop you’ll have to wait up to a minute (or whatever interval you chose) before the connection is established. In my experience there is no harm in setting
WAIT_SECONDS as low as 60 – it doesn’t use up bandwidth, doesn’t bog down the system or cause any other trouble.