5

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 set up 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
3

2 Answers 2

4

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.

2
  • That's really clever, thanks... just wondering, is there a way to know when the connection (master or port forwarding) fails? so I can have a script that will reconnect... so far I've gone with ssh -o ConnectTimeout=10 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -fNTMS /path/to/socket user@server, to help keep the connection alive... but because it's not a traditional background process, I can't use wait, so maybe I need to do something like while ssh -S /path/to/socket -O check placeholder 2>/dev/null; [ $? -eq 0 ]; do echo 'alive'; sleep 3; done? May 5, 2020 at 16:08
  • 1
    @CraigFrancis There is just one connection, the master one. The ssh command that requests port forwarding only re-configures the master connection. If the request fails, you can tell right away by its exit status (and empty $port). Later only the master connection can fail because there is no other. Yes, the most straightforward approach is to check in a loop and start over in case the master connection dies. If you administer the central server, consider a VPN instead. May 5, 2020 at 17:17
-1

You can get the allocated port without executing the scripts on the remote server or creating socket locks, by having an -o ExitOnForwardFailure=yes option.

It waits for the tunnel to be established and thus you can have your "Allocated port ..." message right after this command is completed.

Please read more on this in roaima’s answer to a similar question.

2
  • @Kusalananda you're right, edited it Jan 7, 2023 at 2:43
  • 1
    That still doesn’t help since the linked answer (which your edit lost) is necessary to understand this answer. Jan 7, 2023 at 9:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .