Monday, October 27, 2008

Some more fun with SSH port forwarding and socks proxy

Few days ago I made the following post:

Prologue: Our Institute has several nice Dual Core Machines deployed for the students. Unfortunately the machines are behind a NAT with no port forwarded for external SSH access. Student's hostel is a bit far off from the computational centre. As such if someone felt the need of accessing the machines during non-office hours, it was a wee bit difficult. The sysadmin would not have agreed to forward any ports. Something had to be done....

SSH has a very useful feature - Remote and Local Port Forwarding. We have an old rickety PIII running Ubuntu 8.04.1 in the Hostel, it is connected to the net and is accessible via SSH from the internet. Using a tiny little shell script running on one of the machines in the Institute, I managed to make the old PIII an intermediate gateway for gaining SSH access to the Institute's machines from anywhere in the internet. The script is of few lines, but nevertheless powerful enough to serve our purpose.

while [ 1 ]; do
ssh -C -o ServerAliveInterval=30 -R 4321:localhost:22 serververhostname
echo Retrying

Let us analyse what this code does.
First of all serververhostname is the hostname of the PIII server. For example if resolves to the public ip of the PIII server, then would have been used instead of serververhostname.
The -C option requests compression of all data, to improve data transfer speed over slow connections.
The -o ServerAliveInterval=30 makes the SSH client send a keepalive packet at the application layer every 30s, this is to prevent a timeout, in case the connection is idle.
-R 4321:localhost:22 is the most important part. There is a SSH server running in the Institute's workstation, listening on port 22. "-R 4321:localhost:22" specifies that port 22 of localhost, i.e. of the institute's workstation is to be forwarded to port 4321 of the PIII, such that whenever a connection is made to the PIII on port 4321, the connection gets forwarded over the secure channel to port 22 of the Institute's Workstation.
The while loop ensures that the connection gets re-established if it breaks. You need to use a passphraseless RSA or DSA key for authentication instead of a password, otherwise the ssh client will wait for a password input.
Now suppose in the midst of the night I feel an urge to log in to the machine in my college, but I am (say even a few hundred or thousand miles :D) away from the computational centre, all I need to do is log in to the PIII server in the hostel using ssh from anywhere in the internet. Once I am in there, I issue the command: "ssh -l username localhost -p 4321". Though I am ssh-ing into port 4321 of localhost, effectively the connection is made to port 22 of the Institute's workstation, thanks to the previous ssh port forwarding. No need for persuading the sysadmin to make changes to the NAT or Institute's firewall

This time I will talk a bit more on the same stuff.
Several websites are hosted in the Institute's network, which are accessible from the Institute's LAN only. Most of these are web pages by the faculty, meant to provide class notes and assignments. As such it gets difficult to access the stuffs from our hostel. One way is to connect to any of the Institute's workstations through ssh using the former tunnel and then fetch the required page or file using wget and scp. But I didn't like the idea of manually logging in every time I needed to access any file. Possessed by an unusual craving for automation, I set out fixing this itchy problem. Initially I used to set up a ssh SOCKS proxy by using ssh -l username -D *:9090 localhost -p 4321 . This sets up SOCKS proxy on Oak which listens on port 9090. Firefox can be configured to use SOCKS proxy directly. I could browse websites located in the Instiute's LAN, now using this proxy. Anyway this involved the "trouble" of reconfiguring Firefox's proxy settings every now and then. For sometime I was using the Firefox extension Foxyproxy to manage my proxy settings automatically. It recognizes URLs and can be configured to use different proxy settings for different sites. Still I was not completely happy with this solution. I craved for something more.

I had an old PIV named Mars lying around, being used as a torrent downloader. I decided to put it to some good use. My aim was to set up a cgi based proxy server on Mars, so that using a web browser I would get to the cgi proxy and be able to surf any website within the Institute's LAN using the CGI Proxy. For this purpose I set up Privoxy on Mars and made it forward connections to the SOCKS proxy listening on port 9090 on Oak. Apache was already running on Mars. I installed the CGIProxy 2.1beta18 CGI script from James Marshall's Home Page. It gives a nice web interface. Just enter the URL, and it loads the page by relaying the connection through whatever proxy it has been configured to use. In my case it was using Privoxy.
So in effect this is what was happening-

The CGIProxy 2.1beta18 CGI script was relaying connections through Privoxy, which relayed them through the ssh SOCKS proxy, which in turn was going through the encrypted secure tunnel established right at the beginning by the ssh session initiated by the workstation at the Institute. So far so good.

But life is never smooth sailing, there were quite a few more hurdles to overcome. Oak doesn't have 24x7 internet connectivity, it is connected to the net only for 12hours from 1530 UTC to 0330 UTC which corresponds to 2100 IST to 0900 IST. On top of that the connection is an unreliable ADSL connection, which breaks quite often. The script at the Institute's Workstation tries to connect to Oak whenever Oak is online. If unfortunately the ADSL link of Oak, suddenly breaks, the ssh client gets disconnected, but the ghost session persists in Oak. Next when the ADSL link gets re-established, the ssh client in the Institute's Workstation tries to reconnect back to Oak. However due to the previous ghost session, the client is now unable to forward the required port, since the same port is already reserved. So it was necessary to detect and exorcise these ghosts.

The following script ensures that all ghosts are killed and a successful connection has been established from the Institute's Workstation. Only then does it creates the ssh SOCKS proxy server.

while [ 1 ]; do
echo talkback started.
if [ `/usr/bin/w | /bin/grep -wc IPaddress` == "0" ]
echo talkback: Sleeping...
sleep 120
elif [ `/usr/bin/w | /bin/grep -wc IPaddress` == "1" ]
if [ `/bin/ps aux | /bin/grep ssh | /bin/grep -wc localhost` == "0" ]
echo "talkback: logging in..."
/usr/bin/ssh -N -l students -D *:9878 localhost -p 4321 -i $HOME/id_rsa &
echo talkback: already logged in.
sleep 60
echo talkback: already logged in. shall kill ssh session :D
/usr/bin/skill -KILL username
echo talkback: sleeping...
sleep 60
exit 0

IPaddress is replaced by the IP address from which the connection is being made. If no connections exists, which is the generally the case during 0900IST to 2100IST, the script sleeps for 2min and checks again. If there is a connection, then the script checks whether Oak has already logged in to the Institute's workstation or not. If not it logs in and creates the ssh SOCKS proxy listening on port 9878. If more than one connection were made from the Institute's workstation to Oak and the previous sessions didn't get timed out, then the script kills all the sessions of "username", reruns and waits for a new connection to be made to Oak from the Institute's workstation.
P.S. This post has slightly technical stuff and my way of explaining things is somewhat ugly. Leave a comment if something is not clear.


Abhishek said...

Ghosts cannot be killed. You can only exorcise a ghostly ssh session and persuade it to pass on to the other world :D

Sambit said...

He he he..