SSH – Recording Dynamically Allocated Port Number with Remote Port Forwarding

port-forwardingssh-tunneling

In the SSH documentation, for remote port forwarding, it says:

If the port argument is ‘0’, the listen port will be dynamically allocated on the server and reported to the client at run time. When used together with -O forward the allocated port will be printed to the standard output.

How can I get/use this port number?

I can see it printed on stdout, but I would like to record it in a file.


I have a few computers that sit behind NAT firewalls, where they all connect to a central server, and setup a tunnel so I can SSH back to them.

I currently specify my own port number (e.g. 12345) to do this:

ssh -R 12345:localhost:22 1.2.3.4

And that works, as I can connect to 1.2.3.4, and use:

ssh -p 12345 localhost

But I don't want to hard code a port number for each computer, because keeping a record is a pain; and when the connection eventually fails, the re-connection sometimes doesn't work (because the port is still "in use").

Instead, I can use:

ssh -R 0:localhost:22 1.2.3.4

Where it does report:

Allocated port 41191 for remote forward to localhost:22

But how do I record that port number?

I'm assuming I'm missing something obvious on how to access it.

Could I do something in a shell script that can write the port number to a file?


One option, which doesn't work when you have several servers doing this, would be to check the listening ports:

sudo netstat -tpln | grep "127.0.0.1" | grep sshd

While it seems a bit wasteful, I could get the port number first, disconnect, and then use it:

port=`ssh -R 0:localhost:22 1.2.3.4 exit 2>&1 | grep 'Allocated port' | awk '/port/ { print $3 }'`;

ssh 1.2.3.4 record-port.sh "my-name" "${port}";

ssh -NTC -o ConnectTimeout=10 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -R "${port}:localhost:22" 1.2.3.4;

Another option, which is probably worse, is to randomly select the port myself, and record it before attempting to use it:

while true; do

  port=$(((RANDOM %= 1000) + 2000));

  ssh 1.2.3.4 record-port.sh "my-name" "${port}";

  ssh -NTC -o ConnectTimeout=10 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -R "${port}:localhost:22" 1.2.3.4;

  sleep 3;

done

Best Answer

You connect a master connection first without port forwarding:

ssh -NMS /path/to/socket user@server

/path/to/socket is up to you. A socket will be created there. In a script you want to use -f.

Then you use -S /path/to/socket -O … to control the master connection. E.g. you can check if it still works:

ssh -S /path/to/socket -O check placeholder

placeholder doesn't matter, it's /path/to/socket what identifies the master connection you want to control.

Now you can request port forwarding:

ssh -S /path/to/socket -O forward -R 0:localhost:22 placeholder

The command will tell you the port. It will exit right away, this means you were able to capture the output. Nothing is lost. Repeat the command and it will tell you the same port again instead of forwarding a second port to the same destination. So let's save the output to a variable:

port="$(ssh -S /path/to/socket -O forward -R 0:localhost:22 placeholder)"

And that's it, now you can use the variable.

For completeness, this is how you cancel the forwarding:

ssh -S /path/to/socket -O cancel -R 0:localhost:22 placeholder

although you don't need to cancel explicitly before terminating the master connection. You terminate it like this:

ssh -S /path/to/socket -O exit placeholder

Not all the above commands are necessary in a most basic script. You need these:

ssh -fNMS /path/to/socket user@server
port="$(ssh -S /path/to/socket -O forward -R 0:localhost:22 placeholder)"
   # anything you want, now you know the port
ssh -S /path/to/socket -O exit placeholder

possibly with some logic to handle connection refused etc.

Related Question