Auto-closing SSH tunnels

10 Comments

In some of my older articles, at least where an SSH tunnel is involved, I keep mentioning a specific way of initializing such an encrypted tunnel, which results in the automatic closure of the tunnel after the job on the remote server is finished. Since this is my method of choice, I decided to provide some detailed explanation in this post, so that it can be used as a reference in future articles.

What is a tunnel

Assume that an SSH and a VNC server are up and running on the machine remote.example.org. The remote firewall does not allow direct connections to the VNC port – assume that this is TCP port 5901 – from the network, which is generally a good idea. In this case, we will connect to the remote VNC service from the local workstation through the remote SSH server, by using a feature of the SSH protocol, called local port forwarding or, simply, tunneling.

Such a tunnel can be initialized from the local workstation with the following:

[me@local]$ ssh -L 25901:127.0.0.1:5901 me@remote.example.org

When executed, the above statement has the following results:

  1. forwards the local port 25901 on the local workstation to port 5901 on the remote machine through the ssh connection. The port 25901 is an example and it could be any other available TCP port on the local machine. The result is that any request made on the local port 25901 is transfered to port 5901 on the remote machine through the encrypted SSH connection.
  2. connects us to the remote SSH server and logs our remote user “me” in.

In order to connect to the remote VNC service through that tunnel, it is required to keep the connection to the ssh server open and, from another local terminal, connect to the local port 25901 with the vnc client:

[me@local]$ vncviewer 127.0.0.1:25901:1

By closing the vnc client and by logging out of the remote shell, the tunnel is destroyed too.

A better way

The previous method is too far from being convenient in the everyday use. Things could be a lot better if we could just create the SSH tunnel and launch vncviewer in one line of code. This can be achieved by using the -f and -N flags when connecting to the remote SSH server:

[me@local]$ ssh -f -N -L 25901:127.0.0.1:5901 me@remote.example.org; \
              vncviewer 127.0.0.1:25901:1

The -f switch instructs the ssh client to fork the ssh session to the background. Therefore, we remain at our local terminal’s prompt, from where we can execute more commands on our local machine. In this case, with a single line of code, we created the tunnel and executed vncviewer. However, the -f switch has one requirement. It cannot be used on its own. It needs to be accompanied either by the -N switch or we have to execute a specific command on the remote machine.

In this case, the -f -N combination keeps the tunnel up and running in the background, but the drawback is that it runs forever, requiring us to explicitly kill the ssh process if the tunnel is not needed any more. Even if we close vncviewer, the following command confirms that the tunnel is still running, which was actually expected:

[me@local]$ ps ax | grep ssh | grep -v grep

The best way – Tunnels that auto-close

As it has been mentioned previously, instead of using the -f -N switch combination, we can just use -f alone, but also execute a command on the remote machine. But, which command should be executed, since we only need to initialize a tunnel?

This is when sleep can be the most useful command of all! In this particular situation, sleep has two advantages:

  1. it does nothing, so no resources are consumed
  2. the user can specify for how long it will be executed

How these help in auto-closing the ssh tunnel is explained below.

We start the ssh session in the background, while executing the sleep command for 10 seconds on the remote machine. The number of seconds is not crucial. At the same time, we execute vncviewer exactly as before:

[me@local]$ ssh -f -L 25901:127.0.0.1:5901 me@remote.example.org sleep 10; \
              vncviewer 127.0.0.1:25901:1

In this case, the ssh client is instructed to fork the ssh session to the background (-f), create the tunnel (-L 25901:127.0.0.1:5901) and execute the sleep command on the remote server for 10 seconds (sleep 10).

The difference between this method and the previous one (-N switch), basically, is that in this case the ssh client’s primary goal is not to create the tunnel, but rather to execute the sleep command for 10 seconds. The creation of the tunnel is some kind of side-effect, a secondary goal. If vncviewer was not used, the ssh client would exit after the 10 sec period, as it would have no more jobs to do, destroying the tunnel at the same time.

During the execution of the sleep command, if another process, vncviewer in this case, starts using that tunnel and keeps it occupied beyond the 10 sec period, then, even if the ssh client finishes its remote job (execution of sleep), it cannot exit because another process occupies the tunnel. In other words, the ssh client cannot destroy the tunnel because it would have to kill vncviewer as well. When vncviewer stops using the tunnel, then the ssh client exits too, as it has already accomplished its goal.

This way, no ssh processes are left running in the background.

Further Enhancements

Although this is not a general article about the SSH protocol, the following ssh client switches may prove useful in some situations.

The -C switch may be used in order to apply compression to the transfered data between the local workstation and the remote server. This will save some bandwidth, but will increase the cpu load.

Also, a specific encryption algorithm may be specified with the -c option. The blowfish cipher is a rather good choice, which offers good encryption and low cpu utilization.

[me@local]$ ssh -C -c blowfish -f -L 25901:127.0.0.1:5901 me@remote.example.org sleep 10; \
              vncviewer 127.0.0.1:25901:1

Finally, the -v flag can also be used for verbose output.

Auto-closing SSH tunnels by George Notaras is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Copyright © 2006 - Some Rights Reserved

George Notaras avatar

About George Notaras

George Notaras is the editor of the G-Loaded Journal, a technical blog about Free and Open-Source Software. George, among other things, is an enthusiast self-taught GNU/Linux system administrator. He has created this web site to share the IT knowledge and experience he has gained over the years with other people. George primarily uses CentOS and Fedora. He has also developed some open-source software projects in his spare time.

10 responses on “Auto-closing SSH tunnels

  1. sgt Permalink →

    One thing I don’t understand. If you write that direct connection between the client and VNC server is now allowed, if you do a local port forwarding how come it works then? Because you still use a direct connection, it’s just that you do it from a different source port.
    Is this only useful to circumvent local port restriction?

  2. George Notaras Post authorPermalink →

    Hello. The main benefit is that the connection to the remote server goes through an encrypted tunnel preventing any 3rd party from being able to record or analyze the VNC session. Also, using port forwarding through SSH, the remote VNC server can be hidden behind a firewall without opening any ports on the remote box.

  3. Rafael Torres Permalink →

    Excellent suggestion to auto-close ssh tunnel using sleep! Very clever way, it works like a charm! Thanks

  4. ksatta Permalink →

    I’ve been struggling with this for a long time… What an genious solution :)

  5. Bill Rayment Permalink →

    Setting up a ssh remote tunnel and shutting it down

    SSH remote tunnel using rsync to push data from remote computer to host computer (backup?)
    ***************
    #test.script run manually or from cron
    ssh -p 11122 -f -nT -R 1500:localhost:11122 Nonroot@remote.IP.Adress sleep 1h
    exit
    ***************
    Sets up a tunnel from the remote client to the host (computer running the script above) the tunnel will be available for about an hour
    Run manually or using Cron on the remote computer
    ##########
    # Rsync to remote tunnel which will stay open until compete
    rsync -avz -e ‘ssh -p 1500’ /source-files/ user@localhost:/destination
    #########

    Test to check tunnel existence on remote side
    run the rsync script – if it works tunnel up – if not tunnel is down
    netstat -tanp
    look for a port open at 1500
    if it exists tunnel is up

    ps aux | grep ssh

    You can get the id of the tunnel process and you can kill it.

  6. Ala'a Permalink →

    the trick is clever, but now it got much easier :)

    put the following (and maybe tweak the timing to your taste)

    host *
    ControlMaster auto
    ControlPath ~/.ssh/controls/%r@%h:%p
    ControlPersist 1h

    now the first one you open will act as the master and launch another one (aka no need for -f or -M), use “watch ‘ps aux | grep ssh'”, to see the lurking one

    now you can use the first as usual without needing to remember the ‘Master’ stuff. and fire other session as needed and exit them at will.

    and if you exit all ssh sessions, then the last one hidden will stay for 1 hour (based on the config above) and shutdown if not used within that period

    HIH

  7. Sarah Permalink →

    In your example:
    [me@local]$ ssh -f -N -L 25901:127.0.0.1:5901 me@remote.example.org; \
    vncviewer 127.0.0.1:25901:1
    The local port is the same as the port that is used in for vncviewer.

    You explain that this is because:
    “In order to connect to the remote VNC service through that tunnel, it is required to keep the connection to the ssh server open and, from another local terminal, connect to the local port 25901 with the vnc client.”

    I don’t understand why this is; could you please explain further?

    Say I want to send from machine A to machine C.
    C is running a service on port “server-port” (which is any available TCP port).
    The tunnel goes through machine B, which happens to be machine C, with port “middle-port” (again any available TCP port).

    Why does A have to send from the “server-port”? Why can’t A send from any available TCP port? Wouldn’t it get forwarded along anyways? I’ve tried running this command where A’s local port is different than C’s “server-port”, and the data is forwarded, but I don’t know how to test if it is being tunneled with ssh correctly.

    This explanation was REALLY helpful. Thanks for taking the time to do it!

  8. Matthew Permalink →

    I wanted to use this for rdesktop, but didn’t like that I’d have a time limit to enter my password; so I used control paths.

    function foordc {
    ssh -fNML 22$(printf “%02d” $1):192.168.1.$1:3389 -S ~/.foordc:$1 me@host;
    rdesktop -u me -d domain -p – -f 127.0.0.1:22$(printf “%02d” $1);
    ssh -S ~/.foordc:$1 -O exit localhost;
    }

  9. Oliver Permalink →

    Awesome solution for auto-closing SSH tunnels, thanks!