Creating an SSH tunnel in Linux

General

Creating SSH tunnels can be a powerful tool in your every day work in an environment where many devices are located in different networks. For example, tunnels made through gateways may allow you to access an internal Web server while you are on a Wireless or create a local port allowing you to print to a printer only available on a specific network to which you have no specific access to. Other use may be to run a service as if you were "inside" a network.

The syntax used to create tunnels is not always immediate so, through examples, we will carefully go through options and syntax.

Basic syntax - simple tunnels to a remote service (1 or 2 HOPS) illustrated

General syntax: % ssh {options} -L <local-port-to-listen>:<remote-host>:<remote-port> <gateway> {options}

[A] 1 HOP syntax (only one remote host is involved)

% ssh -f -L $LOCALPORT:remote.server.com:$REMOTEPORT user@remote.server.com -N

-L is the option allowing to create a tunnel.
The port number on the left hand-side is the port which is local to the machine where you issue the ssh command and the port on the right hand-side is the port on the remote server.
The middle server name is the remote host or node to which the tunnel will be created.
The -L does not fully specify what to do - you also need to tell ssh (as usual) where it will connect to create $LOCALPORT:remote.server.com:$REMOTEPORT or a gateway server. In our first examples, where to connect is identical to where we want to create a tunnel.

Here -f indicates to stop before anything is executed (wait) while -N indicates to not execute any command but create on the local machine (where you issue the ssh command) a local port $LOCALPORT which will essentially allow to access port $REMOTEPORT available on remote.server.com . There are optional arguments and ssh command line switches.

An example of that would be create an SMTP local handler to a remote host using REMOTEPORT=25.  For example

% ssh -f -L 1234:my-mail.server.com:25 user@my-mail.server.com -N

would create on your local node a port 1234. Whenever you connect to that local port, what will happen is that you will be tunneled through my-mail.server.com as user user and connect to my-mail.server.com on port 25 (SMTP). In other words, you will be able to send Emails via a local port 1234 even though (a) the real  mail-server is a remote one and (b) perhaps my-mail.server.com can only be accessed via SSH (and all other ports are blocked).
 

[B] 2 HOPS syntax, a gateway AND a second remote node (accessible from a gateway)

Within the same generic syntax, you could create a 2 HOPS tunnel

% sh -f -L 3000:talk.google.com:5222 home.mydomain.net -N

Here again -f and -N are used but unlike the previous command, you log to home.mydomain.net a node able to communicate to talk.google.com via port 5222. Whenever you will need to access the service, you will be able to connect to a local port 3000.

A more standard example at facilities would be something like

% ssh -L 1234:nx01.rcf.bnl.gov:22 user@sssh01.sdcc.bnl.gov

This would create a local port 1234. When connecting to that port, you would essentially, through login to sssh01.sdcc.bnl.gov (as user) access port 22 on nx01.rcf.bnl.gov. Port 22 is by the way, the SSH default port. In this case, nx01.rcf.bnl.gov is not directly accessible where you are but (a) the gateway sssh01.sdcc.bnl.gov is and you can log there as user and (b) sssh01.sdcc.bnl.gov can access nx01.rcf.bnl.gov.

So, whenever you start a NX client on your local machine and ask it to connect to port 1234, you essentially ask the client software to access nx01.rcf.bnl.gov via the ssh protocol (all the way through a tunnel going through a gateway node). Modulo the fact that nx01 SSH "protocol" does more than a regular ssh, NX will be able to open a full session on your laptop or desktop. And voila! Mystery resolved.

Let us ramp this a notch up and create multiple tunnels ...
 

Multiple HOPS, multiple FW ... port forwarding

First, let us stick to one and one command syntax convention only as the syntax is complicated enough to confuse many. We will assume each HOP will use the syntax

% ssh -L $LOCALPORT:localhost:$REMOTEPORT user@gateway

This syntax is essentially saying that $LOCALPORT will be created on localhost which is visible to gateway. You will log to gateway as user and create a port $REMOTEPORT there. If you focus on making a chain of ssh commands work, you will be able to simplify afterward but I highly recommend you get it to work using this canonical syntax first.

Generic example 1

Let us take the specific example of creating a local port essentially going through a two gateways but accessing a remote HTTP service on yet another node. You would do this via three commands (the numbers and names below are arbitrary)

% ssh -L 1234:localhost:1111 user@gateway1.domain1.com           
% ssh -L 1111:localhost:2222 user@gateway2.domain2.net
% ssh -L 2222:localhost:80   root@webserver.domain2.net

So, what did I do here?

On my laptop, I issued the first ssh command. This created a local port 1234, a tunel to a port 1111 assumed to be available on gateway1.domain.com. The ssh command will log me to gateway1.domain.com as user.
Now on gateway1.domain.com (becoming the new localhost), I created port 1111 and a tunnel to port 2222 assumed to be available on gateway2.domain2.net . gateway1.domain1.com can contact and communicate with gateway2.domain2.net. Because this second ssh command has essentially created port 1111, at this stage, my laptop port 1234 goes all the way now an existing port 1111. 2222 does not yet exists but that second ssh command logs me to gateway2.domain2.net becoming the new localhost.
On gateway2.domain2.net, that third ssh command will create pot 2222, a tunnel to port 80. Note that -N is used here so no commands issued. But because port 80 is a privileged port, I need to use the root account to communicate with it. This is not always possible (you may not have access to the root account) but formally, this would work (we will see below how to simplify).

Now, accessing on the original machine http://localhost:1234/ would essentially, without a direct connection or port opening, drive you all the way through to the equivalent of http://webserver.domain2.net/.

Tip 1: Whenever you create ports, please use large numbers as low number ports are privileged and require a privileged account to access them - you cannot "chose" the last port in the chain of course as it has to correspond to the port of the service you need access to (80=http in our case) but any intermediate port should be a high number.
Tip 2: If the port number you have chose does not work, it may already be in use - then try another number. If you work in a team, you may use some convention such that your user ID enters into the choice of the arbitrary port number used to create a tunnel.


But now that you are mastering port forwarding :-), you know this could be simplified even more. Especially, if webserver.domain2.net is accessible from gateway2.domain2.net, you could reduce this to two commands using a syntax similar to [B] above AND avoid the access to a privilege port (and using the root account) issue.

So, the following

% ssh -L 1234:localhost:1111 user@gateway1.domain1.com
% ssh -L 1111:webserver.domain2.net:80 user@gateway2.domain2.net -N

would equally work (or work better for that matter). Note that -N is used here so the port creation does not issue any commands, your web client will do this. If you do not use -N, you will need to log to port 80 as a valid user (doubt this is possible) or revert to using the root account to log to the privileged port.

And because by now, you are wondering "can I do this in one line?" ... you should realize that ssh takes any extra argument on the command line and pass it as a command to be executed on the remote host. Leveraging this fact, you could then do

% ssh -L 1234:localhost:1111 user@gateway1.domain.com 'ssh -L 1111:webserver.domain2.net:80 \
user@gateway2.domain2.net -N'

and get it in one command line.

Another trick is that if you use agent forwarding, you may not need to type any password along this chain of ssh. In such a case, the command may look more like

% ssh -A -L 1234:localhost:1111 user@gateway1.domain.com 'ssh -A -L 1111:webserver.domain2.net:80 \
user@gateway2.domain2.net -N'

 

Specific example 2

OK, we are ready with a specific example. A Web server resides in the internal network (an "Online enclave" for example) you cannot access from the outside. You would like to access that Web server on your laptop Web-client without having to start a remote Web browser (over X11) which would be slow. Here is a possible valid command

% ssh -A -L 5666:locahost:2220  user@sssh01.sdcc.bnl.gov 'ssh -A -L 2220:nagios.starp.bnl.gov:80 user@stargw.starp.bnl.gov -N'

Now, accessing http://localhost:5666/ would be equivalent of accessing http://nagios.starp.bnl.gov/ if you were on stargw.starp.bnl.gov.

References

Documents I found useful