Last Updated: Aug/13/2001
Mistakes/Corrections/suggestions to: rajeev@rajeevnet.com

Port Forwarding using SSH (openSSH) for TCP applications


       SSH and port forwarding are two different concepts. But SSH as a 'side effect' can implement port forwarding in such a way so that if you are able to open  SSH connection to remote machine the you have an ability to do port forwarding also. The term 'side effect' is important here, which means port forwarding done by SSH will remain active only till SSH connection remain active. This is valid for SSH1 and openSSH. SSH2 behaves differently though.
 
 
 

    [bramha] ------ ssh connection ------->[vishnu]
                          Fig: 1


Let's start with example:
Example 1:
----------------------------------------
    brahma% ssh -L 3456:hostname:80  [vishnu]
----------------------------------------

Which says a user on machine [brahma] is opening SSH connection, thus getting shell on host [vishnu]. As a side effect user is also setting up a port forwarding from local port 3456 on machine [brahma] , passing through SSH channel between [brahma] and [vishnu] and forwarding to port 80 on another machine <hostname>(w.r.t vishnu) .

Now we  see various options for  <hostname> and their significance. Let us define effective  TCP connection by tuple (client,client_port,server,server_port). Assuming SSH channel between client and server additionally.

  1. localhost: (ssh -L 3456:localhost:80 [vishnu] ), in this localhost actually refers to loopback interface of machine [vishnu]. So effective port forward connection would look like (brahma,3456,localhost@vishnu,80). This is much preferred way to port forwarding in above situation.
  2. [vishnu] : ( ssh -L 3456:[vishnu]:80 [vishnu] ). This defines effective port forward connection as (brahma,3456,visnu,80). This is equivalent as option 1. But you may need sometime. In case remote machine is using ACL control (such as tcpwrapper) based on hostnames.
  3. [brahma]: (ssh -L 3456:[brahma]:80 [vishnu]. This defines effective port forward connection as (brahma,3456,brahma,80). Ofcourse there is no point in connecting you own machine using encryption channel through remote machine. Although this is possible but sort of useless.


However option 3. may be pretty useful if we  use it little differently and consider another example.
 
 

    [rahu]                                                                                    [ketu]
        |                                                                                             |
        |                                                                                             |
        |                                                                                             |
        |                                                                                             |
       [brahma]--------------- ssh connection --------------[vishnu]
                                      Fig 2

Example 2:
---------------------------------
   brahma% ssh -L 3456:ketu:80 vishnu
---------------------------------

This is interesting. Now a user on [brahma] can effectively connect on ketu at port 80. (brahma,3456,ketu,80). In openSSH by default SSH port forwarding won't allow listening on  port forwarded port(3456) other than from  localhost(127.0.0.1). i.e after above ssh connection only user on brahma can connect to ketu using port forward connection. Openssh has an option -g, which will allow other interface to listen for port forwarding request also. So if we open ssh channel using -g option :

Example 3:
----------------------------------
   brahma% ssh -g -L 3456:ketu:80 vishnu
----------------------------------

that will allow machine [rahu] to use port forwarding between [brahma] and [vishnu]. Which means when [rahu] opens up real connection as (rahu,6789,brahma,3456), the effective connection would be (rahu,6789,ketu,80).
 
 

Let's see some other interesting examples:

Example 4:
---------------------------------------------------
  localuser@brahma% ssh -L 3456:localhost:80  remoteuser@vishnu
--------------------------------------------------

In case you are using different user account on remote machine vishnu.
 
Example 5:
---------------------------------------------------
  brahma% ssh -C -N -f -L 3456:localhost:80  remoteuser@vishnu
---------------------------------------------------
This will use compression (-C) good for slow links such as dial-up, ssh connection will go in background (-f) and ssh won't open Shell (-N) on remote machine [vishnu]. This is useful when you need only port forwarding. But this is good only for one connection over port forwarding. Because after one connection through port forward  base ssh connection will die and so the port forwarding. You can verify this by using command ' netstat -an | grep 3456' and 'ps -ef  | grep  ssh' output. SSH2 behaves different than openSSH in this case.


Example 6:
-------------------------------------------------------
  brahma% ssh -C -f -L 3456:localhost:80 remoteuser@vishnu sleep 300
-------------------------------------------------------

This will remedy above situation. Since base ssh connection will remain active for 300 seconds (wait till sleep command finish), thus port forward will also remain up during this period. Note: we are not using -N option as we need remote shell here to execute command 'sleep 300'. One such use of this  option is  to check your mails from your POP server using SSH tunnel. Since POP is sort of stateless (i.e every POP query disconnects, just like HTTP request after it completes it job. Example when you click on "Get Mail" in your POP client it opens TCP connection to POP server, download all new mails and close TCP connection. Next time you click on "Get Mail" it opens new TCP connection. So obviously we need port forwarding to be up for more than 1 connection.
 
Let's see some more tricks:

Example 7:
----------------------------------------------------------
  brahma% ssh -L 3456:localhost:7777 vishnu
     (After you login on vishnu)
     vishnu% ssh -L 7777:localhost:80   ketu
-----------------------------------------------------------

This is effectively same as Example 3. But using two different commands. May be useful when you have some port forwarding restrictions applied.


Example 8:
---------------------------------------------------------
 brahma% ssh -R 7777:localhost:80 vishnu
---------------------------------------------------------

This is using -R (remote port forwarding). i.e it will allow to listen on port 7777 on remote host [vishnu] and forward in reverse direction at port 80 on brahma. i.e in this case any user on machine vishnu can open effective connection as (vishnu,7777,brahma,80).
Example 9:
----------------------------------------------------------
  brahma% ssh -f  -C -R 7777:ketu:80 vishnu sleep 300
----------------------------------------------------------
This is pretty interesting. This will open SSH connection between [brahma] and [vishnu] with compression. But it is setting up port forwarding with effective connection  as (vishnu,7777,ketu,80).  So there is no port forwarding set on brahma at all here.
 
 
In all above examples we are saying effective connection such as (brahma,3456,vishnu,80). However if you analyze, real connection will flow like this.
 Suppose in Example 1. You run telnet and connect to port 3456 on brahma.

  brahma% telnet localhost 3456
  Trying 127.0.0.1...
  Connected to brahma.
   Escape character is '^]'.

tcpdump output for this connection: (Only few packets are shown)

1. On loopback interface of [brahma]:
----------------------------------------------------------------------------
23:33:46.483207   lo > brahma.33458 > brahma.3456 : S 2295119957:2295119957(0) win 32767 <mss 16396,sackOK,timestamp 4050242 0,nop,wscale 0> (DF)

23:33:46.483260   lo > brahma.3456 > brahma.33458: S 2298189676:2298189676(0) ack 2295119958 win 32767 <mss 16396,sackOK,timestamp 4050242 4050242,nop,wscale 0> (DF)

23:33:46.483291   lo > brahma.33458 > brahma.3456: . 1:1(0) ack 1 win 32767 <nop,nop,timestamp 4050242 4050242> (DF)
------------------------------------------------------------------------------

2A . On eth0  interface of [brahma]. This is real SSH connection (tunnel for port forward)
----------------------------------------------------------------------------
23:32:08.615695 > brahma.33450 > vishnu.ssh: P 1891177335:1891177431(96) ack 1925674155 win 10880 <nop,nop,timestamp 4040455 47624214> (DF) [tos 0x10]

23:32:08.779753 < vishnu.ssh > brahma.33450: . 1:1(0) ack 96 win 9480 <nop,nop,timestamp 47627035 4040455> (DF) [tos 0x10]
-----------------------------------------------------------------------------
 

2B. On eth0 interface of [vishnu]
-----------------------------------------------------------------------------
23:43:44.892766 eth0 < brahma.33450 > vishnu.ssh: P 1891183623:1891183719(96) ack 1925698715 win 18368 <nop,nop,timestamp 4109898 47687268> (DF) [tos 0x10]

23:43:44.892851 eth0 > vishnu.ssh > brahma.33450: . 1:1(0) ack 96 win 9480 <nop,nop,timestamp 47696480 4109898> (DF) [tos 0x10]
-----------------------------------------------------------------------------
 

3. on loopback interface of [vishnu]
---------------------------------------------------------------------------
23:42:12.546486   lo > localhost.33513 > localhost. http: S 2836683535:2836683535(0) win 32767 <mss 16396,sackOK,timestamp

23:42:12.546513   lo > localhost.http > localhost.33513: R 0:0(0) ack 2836683536 win 0 (DF)
---------------------------------------------------------------------------
 

So in above connection actually there were 3 real TCP connections participating.

  1. (localhost@brahma,33458,localhost@brahma,3346 ) :  Port number 33458 is an ephemeral port allocated by OS for every new TCP connection and it may differ each time for new connection. Port 3346 is what we have defined in ssh -L 3346:....  option. This TCP connection is over loopback interface.
  2. (brahma,33450,vishnu,ssh{22}): This is regular SSH connection between [brahma] and [vishnu]
  3. (localhost@vishnu,33513,localhost@vishnu,http{80} ): This is on loopback interface on [vishnu], when sshd on [vishnu] hand over data to port 80 at [vishnu]. Actually this is the localhost mentioned in ssh -L 3456:localhost:80 vishnu command.
In case of Example 3, these 3 connection may look like below.
  1. (rahu,33458,brahma,3346)
  2. (brahma,33450,vishnu,ssh{22})
  3. (vishnu,33513,ketu,80)


If you have read so far, then you understood that port forwarding is a very nice feature on SSH. This will allow users to manage their secure/encrypted tunnel themselves. There are other ways of port forwarding also such as using iptables, xinetd, stunnel etc. One limitation here is you need to have SSHD running on remote machine and user must have login/shell access to SSHD server. But this is better for security reasons also. You can control ACL and authentication based on SSH service.

 Port forwarding where gives convenience to users. A bad internal user can  misuse this feature and may deceive your Firewall rules by  setting up  port forwarding on his home machine using ssh (or other port forwarding technique)  in such a way that  third outsider user can access your corporate resources through port forward channel.



References:

[1] Openssh : http://www.openssh.com
[2] SSH: The Secure Shell, O'Reilly Book by Danie J. Barret & Richard E. Silverman.
[3] man page of ssh.