When the Raspberri Pi is connected via the 3G/UMTS network 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 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:
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 secure 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 with the following script:
#!/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
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. See here for the full documentation of all options.
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:
#!/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.