...making Linux just a little more fun!

Tunnel Tales 2

By Henry Grebler


In a previous article, Tunnel Tales 1 I described how to use SSH tunnels and a third machine to provide network access from one machine to a second machine which was otherwise not directly accessible. Today's scenario is quite different.

We want convenient access to a machine which can only be reached by navigating a chain of intermediate machines.

Whereas the earlier task could be accomplished with a single command, the current task is far more formidable and requires more powerful magic.

This article will assume that you are very familiar with SSH. I will not repeat points I made in the earlier article.

Keywords: SSH tunnels, expect

The Scenario

Networks come in all shapes and sizes. I'm sure that the original network was designed. I guess that, over time, a machine was added here, another was removed there - much like a well loved suit might be modified as its owner ages and changes shape.

By the time I arrived, the network was looking quite convoluted. It was easy enough to get to the newer machines. But some of the legacy machines required some serious tap-dancing before they could be reached.

Diagram 1
target	the machine I need to work on
jump1	an intermediate machine
jump2	another intermediate machine
laptop	my laptop

If I only needed to get to the target machine once or twice, I would just ssh from my laptop to jump1; then again from there to jump2; and finally from there to the target machine.

But I knew that I would be visiting target many times over the next week or two. And further, and more interestingly, I would need to transfer files between my laptop and the target machine.

Again, for transferring files, most people would suggest in exasperation to just transfer them from one machine to the next until they reached the required destination.

Analysing the Task

This task provides an educational "compare and contrast" with the task of the earlier article.

The First Step

I invoke the following command on my laptop:

	ssh -L 9922:localhost:9822 jump1

The command says to establish an SSH connection to jump1. "While you're at it, I want you to listen on a port numbered 9922 on localhost (ie the laptop). If someone makes a connection to that port, connect the call through to port 9822 on jump1."

Why 9922? The port number is arbitrary, but I use the form XX22 to remind me that this relates to SSH.

Why 9822? It seems that this port number is as arbitrary as 9922, but that's not entirely true. We'll examine this a little later.

So far we have not achieved much.

The Second Step

The previous command landed me on jump1, where I now issue the following command:
	ssh -L 9822:localhost:9722 jump2

You should be able to work out what this command does. Of course this time, localhost means jump1.

The port number on the left (in this case 9822) must be the same as the one on the right for the preceding command.

Before I explain more, I'll just add one more command.

All Three Steps

By now, the last step should be obvious. (It isn't. There's one final wrinkle.) To make the subsequent analysis easier to follow, I'll list all three commands and then discuss.

	ssh -L 9922:localhost:9822 jump1
	ssh -L 9822:localhost:9722 jump2
	ssh -L 9722:localhost:22 target

Diagram 2

The three commands get me to the target machine, where I can do whatever work I need to do. That's one effect. The side-effect is more interesting.

Quite often, when I visit a machine, I like to run several sessions, not just a single session. To start a second session, I could use a similar set of ssh commands (with or without the -L option). Or, on my laptop, I could just go:

	ssh -p 9922 localhost

The reference to port 9922 on localhost connects me to port 9822 on jump1, which automatically on-connects me to port 9722 on jump2, which automatically on-connects me to port 22 on jump2.

The individual tunnels combine to provide me with a "super-tunnel".

Diagram 3


Getting there automatically

That's all you need to improve your life substantially when you encounter a similar scenario.

What's that? You think that there is still too much typing? You want more?

Oh, all right.

Here's a fairly long expect script:

#!/usr/local/bin/expect -f
#	ssh_tunnel.exp - ssh to a remote machine via intermediate machines

set timeout -1

	set HOSTS [list jump1 jump2 target]
	set PORTS [list 9922 9822 9722 9622 9522 9422 9322 9122 9022]

# The port of the last machine must be 22

	set jj [llength  $HOSTS]
	lset  PORTS $jj 22

	set i 0
	foreach HOST $HOSTS {
		puts "HOST= $HOST PORT= [lindex $PORTS $i]"
		set i [expr {$i + 1}]

	send_user "\n"

#	Procedure to get to a machine

proc gotomachine {lport rport host} {
	send_user "Getting on to machine $host ... "
	send -- "ssh -L $lport:localhost:$rport $host\r"
	log_user 0
	expect -exact "Starting .bash_profile"
	expect -exact "Finished .bash_profile"
	expect -exact "-bash"
	send -- "env | grep SSH_CONNECTION\r"
	log_user 1
	send_user "done.\n"

match_max 100000

	set dollar "$"
	spawn bash
	log_user 0
	expect -exact "-bash"
	send  -- "unset HISTFILE\r"
	expect -exact "-bash"
	send  -- "unset ignoreeof\r"
	expect -exact "-bash"
	send  -- "PS1='\nYou need one more exit to get back "
	send  -- "to where you started\nUse ^D. $ '\n"
	expect -exact "started" 
	log_user 1
	set i 0
	foreach HOST $HOSTS {
		set lport [lindex $PORTS $i]
		set i [expr {$i + 1}]
		gotomachine $lport [lindex $PORTS $i] $HOST

	puts "
Houston, this is Tranquility Base. The eagle has landed. 
You should now be able to get to this machine ($HOST) directly

	ssh -p [lindex $PORTS 0] localhost

To disconnect the tunnel, use the following repeatedly:
	puts {	[ "$SSH_CONNECTION" = '' ] || exit }
	puts "
Good luck!


Tying up Loose Ends

When I developed the solution on my machine I was under the misapprehension that I had no choice but to use different port numbers. As I wrote this article, I said that ports only have to be unique on a single machine - and then corrected myself and said they only have to be unique on a single interface.

This opens the possibility of a simplification of the script ssh_tunnel.exp - at the expense of setting up some virtual interfaces on my single machine. If I were doing this from scratch now, that's what I would do.

It gets very confusing constantly connecting back to a single machine. That accounts for the large number of lines dealing with disconnecting the tunnel. I was scared I would exit too often and blow my xterm away.

Risks and Analysis

This is a nice safe use of expect. As usual, I've set up certificates on all relevant machines, so no paswords are necessary.


You should now have the tools to navigate conveniently across any chain of machines.

Read with the previous article, this article should have given you enough information to handle the earlier scenario without "cheating".

You should be able to extrapolate from these articles to almost any configuration of machines.


Talkback: Discuss this article with The Answer Gang


Henry has spent his days working with computers, mostly for computer manufacturers or software developers. His early computer experience includes relics such as punch cards, paper tape and mag tape. It is his darkest secret that he has been paid to do the sorts of things he would have paid money to be allowed to do. Just don't tell any of his employers.

He has used Linux as his personal home desktop since the family got its first PC in 1996. Back then, when the family shared the one PC, it was a dual-boot Windows/Slackware setup. Now that each member has his/her own computer, Henry somehow survives in a purely Linux world.

He lives in a suburb of Melbourne, Australia.

Copyright © 2010, Henry Grebler. Released under the Open Publication License unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.

Published in Issue 177 of Linux Gazette, August 2010