Setting up an SSH tunnel
Or, How To Get From Point A To Point C
Suppose you have the following common situation:
- You are sitting at the console of machine A.
- You are trying to get to machine C, which is inside a firewall.
- You can get to machine B, which is the firewall that protects machine C. From there, you can get to machine C.
Now, what if you want to make a connection directly from A to C? You can connect from A to B, and you can connect from B to C, but the firewall doesn't permit you to make a standard TCP/IP connection from A to C.
If you have an SSH (secure shell) connection from A to B, however, you can set up a tunnel that gives you the appearance of talking directly to machine C. Your packets are actually sent through your connection from A to B; and then they're forwarded on to machine C automatically.
In order to set up a tunnel, you need to understand exactly where you want the packets to be sent. There must be some sort of service running on machine C that you want to reach, and you'll need to know what port that service is listening to.
For the purposes of our example, let's suppose what we really want to do is transfer files from A to C. Further, let's suppose we're using SSH to make all the connections (A-B and B-C). If we want to send files to C, the natural way to do it would be to use scp or sftp, and those both piggy-back on top of the SSH service. That means we need to reach machine C's SSH port (TCP port 22).
So, in order to do that, we want to set up a tunnel that forwards packets to machine C port 22. If we aren't connected to machine B yet, we can use this command to set up the A-B connection plus the tunnel:
ssh -L 2222:C:22 me@B
In this example,
We're making a connection to machine B using username me.
The ssh client will listen on port 2222 (loopback interface only).
Any connections to port 2222 on the client machine will be forwarded from machine B to machine C port 22.
There is nothing magical about the number 2222. We just need to choose any unused port that we're allowed to listen to (i.e., greater than 1023 if we are not running with root privileges). However, C:22 must designate the final destination of the tunnel in a way that the sshd process on machine B can resolve.
Now let's see how this works in practice:
wooledg@wooledg:~$ timeout 1 nc arc1 22 SSH-2.0-OpenSSH_3.8.1p1 Debian-8.sarge.6 Timeout: aborting command ``nc'' with signal 9 Killed wooledg@wooledg:~$ ssh -L 2222:arc3:22 -N -f wooledg@arc1 Password: wooledg@wooledg:~$ timeout 1 nc localhost 2222 SSH-2.0-OpenSSH_4.3p2 Debian-9etch2 Timeout: aborting command ``nc'' with signal 9 Killed
The first command shows us what SSH daemon is running on host arc1, which is our "machine B". We're just going to use it as a stepping stone to get to host arc3 ("machine C"). As you can see, arc1 is running OpenSSH 3.8.1p1 from Debian's "sarge" distribution.
The second command creates a connection to arc1 and also creates a tunnel. The tunnel will use that connection to forward packets to arc3 port 22. The -N option tells it not to run any commands on arc1. The -f option tells the ssh client to put itself in the background after authentication is complete. (For details on these and other options, please see the manual. I won't attempt to list all possible options here.)
The third command shows what happens when we make a connection using our tunnel. Instead of seeing arc1's SSH banner, we see arc3's -- OpenSSH version 4.3p2 from Debian's "etch" distribution.
At this point, we can make connections directly to "machine C" (arc3). OK, not really directly -- but they appear to be direct.
wooledg@wooledg:~$ ssh -p 2222 wooledg@localhost The authenticity of host '[localhost]:2222 ([127.0.0.1]:2222)' can't be established. RSA key fingerprint is 56:f5:e1:4e:ab:bd:c3:d6:10:e5:32:43:ae:98:9a:eb. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '[localhost]:2222' (RSA) to the list of known hosts. Password: Linux arc3 2.6.18-6-686 #1 SMP Fri Jun 6 22:22:11 UTC 2008 i686 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Tue May 13 12:20:18 2008 from imadev.eeg.ccf.org arc3:/net/home/wooledg$
We're in! See how easy and useful that was? Instead of making an ssh connection for an interactive shell, we could have used scp to transfer files, etc.
SSH tunnels can be used for any protocol that uses a single TCP/IP connection. If you want to reach a web server that's firewalled, from your web browser on your desktop, when your only means of access to the web server is through an SSH connection to a firewall box, then try something like this:
ssh -L 8080:webserver:80 me@B -N
And then go to http://localhost:8080/ in your browser, and voila!
Another common example is trying to reach an IRC network from within a firewall that blocks outgoing connections to the IRC port (or, worse, one that filters content and severs a connection when it sees IRC traffic in the clear -- and, in fact, my firewall at work does both of those). If you have a host that you can reach via SSH, you can tunnel your IRC traffic through that:
ssh -L 6667:irc.freenode.net:6667 me@B -N
Tell your IRC client to connect to localhost port 6667, and now you're talking to Freenode!
If you already have a connection open to machine B, and you don't want to disconnect, you can tell the SSH client to open a new tunnel. Your client will have to be in the foreground of a terminal, because this process involves sending keystrokes to it. You open a command line within the SSH client by pressing Enter ~ C (Enter, tilde, shift-C). Then you can type your -L a:b:c as a "command" (followed by Enter) to start a new tunnel.
Setting up an ssh reverse tunnel
Many times it's helpful to connect outbound through a NAT-ing firewall to another machine, say to your home machine from work, setting up a backgrounded reverse tunnel. This lets you connect back through the firewall to the work host you established the reverse tunnel on when you get home later.
To make sure the connection doesn't drop while you're driving home, you need to make a couple of edits to the work machine's ~/.ssh/config file and increase some keep-alive variables.
ServerAliveCountMax 300 ServerAliveInterval 15
Choose whatever values you like (man ssh_config for more info). The above will not disconnect for 75 minutes even if no responses are received.
To initiate the tunnel from the work host, run the following in a shell:
ssh -fNTR localhost:2222:localhost:22 email@example.com
This will create the backgrounded reverse tunnel to your house. Once you get home, you can reconnect right back to the work host using:
ssh -p2222 user@localhost