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 /etc/ssh/sshd_config
:
AllowUsers john
Test your setup by ssh-ing from the Pi to your desktop:
ssh john@foobar.noip.com -p 12345
Next install 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_ADDRESS=john@foobar.noip.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
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. See here for the full documentation of all options.
On the desktop you can now connect to the Pi as user pi
:
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 john@foobar.noip.com 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.