badp
badp

Reputation: 11813

How do I properly open a SSH tunnel in the background?

I wish to tunnel a port over SSH from a remote host. I wish to implement this as an oclif plugin; I want the user experience to look like this:

laptop$ give-jupyter
http://localhost:4040/
laptop$ kill-jupyter
laptop$

...and that should be relatively straightforward; I just* need to maintain a pidfile, right? Something like:

import child_process from 'child_process';
const childProcess = child_process.spawn('ssh', [/* flags */], {detached: true, stdio: 'ignore'});
childProcess.unref();
writeToSomePidfile({childProcess.pid);

However, all of this is basically besides the point. The problem is I have to figure those flags out! Well, okay; this works:

laptop$ ssh machine -L 4040:localhost:4040
machine$ 

...however, it also opens a shell on the remote end. No problem, man ssh says that's what -L is for:

laptop$ ssh machine -LN 4040:localhost:4040

That's great, but it's now taking my user's shell hostage. Fine, let's just send the process to the background:

laptop$ ssh machine -LN 4040:localhost:4040 &
laptop$ f
f: file not found
laptop$ fg
^C

The background version of ssh enters a race condition with the shell over STDIN, and everything is positively terrible. Fine, man ssh says that's what -n is for:

laptop$ ssh machine -nLN 4040:localhost:4040 &
laptop$Job 1, 'ssh machine -nNL 40…' has ended

...well, that's just great: ssh now quits immediately, and so does the tunnel.

SSH mentions -f should enable some sort of background mode, but ssh -fnN doesn't do it either; ssh quits immediately still.

If I can't have nice things, maybe I can approximate them with a command that will run forever even with no STDIN. Server Fault suggests:

laptop$ ssh machine -nL 4040:localhost:4040 tail -f /dev/null &
laptop$

Still no good?! Fine:

laptop$ ssh machine -nL 4040:localhost:4040 sleep infinity &
laptop$

That seems to work at the low, low cost of one tiny process, a far sight better than other iterations I had tried while writing this questions, mostly involving yes...

Is there however a... less kludgy way to run an SSH tunnel in the background? Bonus points: I need this to work on OSX laptops too...

Upvotes: 14

Views: 4672

Answers (1)

Prav
Prav

Reputation: 2884

I'd personally create a file on the system that binds to the tunnel using -S control socket. Easier than dealing with PIDs for sure.

Open tunnel

ssh -M -S ~/jupyter-tunnel -o "ExitOnForwardFailure yes" -fN machine -L 4040:localhost:4040

Close tunnel

ssh -S ~/jupyter-tunnel -O exit machine

Upvotes: 10

Related Questions