Question (TL;DR)
When assigning ports dynamically for remote forwarding (a.k.a. -R
option), how can a script on the remote machine (for instance sourced from .bashrc
) determine which ports were chosen by OpenSSH?
Background
I use OpenSSH (on both ends) to connect to our central server, that I share with multiple other users. For my remote session (for now) I would like to forward X, cups and pulseaudio.
The most trivial is forwarding X, using the -X
option. The allocated X address is stored in the environmental variable DISPLAY
and from that I can determine the corresponding TCP port, in most cases anyways. But I hardly ever need to, because Xlib honors DISPLAY
.
I need a similar mechanism for cups and pulseaudio. The basics for both services exist, in the form of the environmental variables CUPS_SERVER
and PULSE_SERVER
, respectively. Here are usage examples:
ssh -X -R12345:localhost:631 -R54321:localhost:4713 datserver
export CUPS_SERVER=localhost:12345
lowriter #and I can print using my local printer
lpr -P default -o Duplex=DuplexNoTumble minutes.pdf #printing through the tunnel
lpr -H localhost:631 -P default -o Duplex=DuplexNoTumble minutes.pdf #printing remotely
mpg123 mp3s/van_halen/jump.mp3 #annoy co-workers
PULSE_SERVER=localhost:54321 mpg123 mp3s/van_halen/jump.mp3 #listen to music through the tunnel
The problem is setting CUPS_SERVER
and PULSE_SERVER
correctly.
We use port forwardings a lot and therefore I need dynamic port allocations. Static port allocations are not an option.
OpenSSH has a mechanism for dynamic port allocation on the remote server, by specifying 0
as bind-port for remote forwarding (the -R
option). By using the following command, OpenSSH will dynamically allocate ports for cups and pulse forwarding.
ssh -X -R0:localhost:631 -R0:localhost:4713 datserver
When I use that command, ssh
will print the following to STDERR
:
Allocated port 55710 for remote forward to 127.0.0.1:4713
Allocated port 41273 for remote forward to 127.0.0.1:631
There is the information I want! Ultimately I want to generate:
export CUPS_SERVER=localhost:41273
export PULSE_SERVER=localhost:55710
However the "Allocated port ..." messages are created on my local machine and sent to STDERR
, which I can't access on the remote machine. Oddly enough OpenSSH does not seem to have means to retrieve Information about port forwardings.
How do I fetch that information to put it into a shell script to adequately set CUPS_SERVER
and PULSE_SERVER
on the remote host?
Dead Ends
The only easy thing I could find was increasing verbosity of the sshd
until that information can be read from the logs. This is not viable as that information discloses a lot more information than is sensible to make accessible by non-root users.
I was thinking about patching OpenSSH to support an additional escape sequence which prints a nice representation of the internal struct permitted_opens
, but even if that is what I want, I still can't script accessing the client escape sequences from the server side.
There must be a better way
The following approach seems very unstable and is limited to one such SSH session per user. However, I need at least two concurrent such sessions and other users even more. But I tried ...
When the stars are aligned properly, having sacrificed a chicken or two, I can abuse the fact that sshd
is not started as my user, but drops privileges after successful login, to do this:
get a list of port numbers for all listening sockets that belong to my user
netstat -tlpen | grep ${UID} | sed -e 's/^.*:\([0-9]\+\) .*$/\1/'
get a list of port numbers for all listening sockets that belong to processes my user started
lsof -u ${UID} 2>/dev/null | grep LISTEN | sed -e 's/.*:\([0-9]\+\) (LISTEN).*$/\1/'
All ports that are in the first set, but not in the second set have a high likelyhood to be my forwarding ports, and indeed subtracting the sets yields
41273
,55710
and6010
; cups, pulse and X, respectively.6010
is identified as the X port usingDISPLAY
.41273
is the cups port, becauselpstat -h localhost:41273 -a
returns0
.55710
is the pulse port, becausepactl -s localhost:55710 stat
returns0
. (It even prints the hostname of my client!)
(To do the set substraction I sort -u
and store the output from the above command lines and use comm
to do the substraction.)
Pulseaudio lets me identify the client and, for all intents and purposes, this may serve as an anchor to separate SSH sessions that need separating. However, I haven't found a way to tie 41273
, 55710
and 6010
to the same sshd
process. netstat
won't disclose that information to non-root users. I only get a -
in the PID/Program name
column where I would like to read 2339/54
(in this particular instance). So close ...
netstat
won't show you the PID for processes you don't own or that are kernel-space. For example