I want to create a Linux shell (bash-) script which creates an SSH tunnel, runs a local command which uses that tunnel and finally closes the tunnel and the surrounding SSH connection.
To make this less difficult to explain consider there is a local SSH configuration to a host called 'remoteserver' containing a local private key without a password, so
ssh remoteserver -L 4444:targetserver:5555
would directly open a connection to the remote server and create a tunnel from the local port 4444 to a target server. And consider the local command would be localclient --port 4444
, how would a script look like that opens the tunnel, executes the local command and closes the tunnel after the local client application is finished?
As it should be possible to keep other parallel ongoing SSH connections, I don't want something like sudo killall ssh
.
You can try something like
TIMEOUT=60 # seconds
ssh remoteserver -L 4444:targetserver:5555 sleep $TIMEOUT &
localclient --port 4444
The tunnel will close itself automatically after $TIMEOUT seconds. Note that using the &
is only valid with passwordless connections. Otherwise you need to use the -f
flag of SSH.
Alternatively,
ssh -N remoteserver -L 4444:targetserver:5555 &
sshpid=$!
localclient --port 4444
kill $sshpid
will kill the tunnel just after localclient
executes. Note that this will not work with the -f
flag because the process double forks.
An ssh
-only solution (does not work for me)
The second suggestion of @damienfrancois is adquate, but
for some reason I did not like the idea of having to kill
something.
Also, I could not believe ssh
has no built-in mechanism for this.
And indeed it has. This is how things should work:
ssh remoteserver -o "PermitLocalCommand yes" -o "LocalCommand localclient --port 4444" \
-L 4444:targetserver:5555 -N
But for some reason, this did not work as expected for me:
I first tried with something simple like w
for the LocalCommand
but the -N
made the command hang, so I used sleep 0
instead,
which appeared to work.
But when I inserted my actual network command, the result ran
extremely slowly. Instead of finishing in less than one second,
it took 40 seconds before even the start message appeared.
I terminated the command after 20 minutes.
A pipe solution (works for me)
After some experimentation, this is what I now use:
ssh remoteserver -L 4444:targetserver:5555 keepalive.sh \
| (sleep 0.5; localclient --port 4444)
where keepalive.sh
on the remote side is this:
while true; do
sleep 1
echo "keepalive"
done
The sleep 0.5
is needed to make (sort of) sure
the tunnel is set up before localclient
starts
(and for a far-away or slow remoteserver
, you may need a longer wait).
The tunnel is terminated as soon as localclient
finishes
because the shell terminates the pipe as soon as the
second command closes its standard input.
Is this better than @damienfrancois's solution? Depends.
The need for keepalive.sh
is a clear drawback, but
the way in which the tunnel is cleared away no matter
how and why localclient
terminates is cleaner.
I need this in a Makefile
context and like the fact it fits on
a single line.