为 OpenSSH RemoteForward 确定动态分配的端口

Ban*_*uin 17 openssh ssh-tunneling shell-script port-forwarding

问题 (TL; DR)

当为远程转发动态分配端口(又名-R选项)时,远程机器上的脚本(例如来自.bashrc)如何确定 OpenSSH 选择了哪些端口?


背景

我使用 OpenSSH(两端)连接到我与多个其他用户共享的中央服务器。对于我的远程会话(目前),我想转发 X、cups 和 pulseaudio。

最简单的是使用-X选项转发 X。分配的 X 地址存储在环境变量中,DISPLAY并且在大多数情况下,我可以从中确定相应的 TCP 端口。但我几乎不需要这样做,因为 Xlib 尊重DISPLAY.

我需要一个类似的杯子和脉冲音频机制。两种服务的基础都存在,分别以环境变量CUPS_SERVER和的形式存在PULSE_SERVER。以下是使用示例:

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
Run Code Online (Sandbox Code Playgroud)

问题是设置CUPS_SERVERPULSE_SERVER正确。

我们经常使用端口转发,因此我需要动态端口分配。静态端口分配不是一种选择。

OpenSSH 有一种在远程服务器上动态端口分配的机制,通过指定0为远程转发的绑定端口(-R选项)。通过使用以下命令,OpenSSH 将为杯和脉冲转发动态分配端口。

ssh -X -R0:localhost:631 -R0:localhost:4713 datserver
Run Code Online (Sandbox Code Playgroud)

当我使用该命令时,ssh会将以下内容打印到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
Run Code Online (Sandbox Code Playgroud)

有我想要的资料!最终我想生成:

export CUPS_SERVER=localhost:41273
export PULSE_SERVER=localhost:55710
Run Code Online (Sandbox Code Playgroud)

但是,“已分配的端口...”消息是在我的本地计算机上创建并发送到STDERR我无法在远程计算机上访问的。奇怪的是,OpenSSH 似乎没有办法检索有关端口转发的信息。

如何获取该信息,把它变成一个shell脚本适当地设置CUPS_SERVERPULSE_SERVER远程主机上?


死胡同

我能找到的唯一简单的事情是增加详细sshd信息,直到可以从日志中读取该信息。这是不可行的,因为该信息披露的信息比非 root 用户可以访问的信息要多得多。

我正在考虑修补 OpenSSH 以支持一个额外的转义序列,它打印出内部结构的一个很好的表示permitted_opens,但即使这是我想要的,我仍然无法编写从服务器端访问客户端转义序列的脚本。


一定会有更好的办法

以下方法似乎非常不稳定,并且仅限于每个用户一个这样的 SSH 会话。但是,我需要至少两个并发这样的会话,其他用户甚至更多。但我试过...

当星星正确对齐时,牺牲了一两只鸡,我可以滥用sshd不是作为我的用户启动的事实,而是在成功登录后放弃特权,这样做:

  • 获取属于我的用户的所有侦听套接字的端口号列表

    netstat -tlpen | grep ${UID} | sed -e 's/^.*:\([0-9]\+\) .*$/\1/'

  • 获取属于我的用户启动的进程的所有侦听套接字的端口号列表

    lsof -u ${UID} 2>/dev/null | grep LISTEN | sed -e 's/.*:\([0-9]\+\) (LISTEN).*$/\1/'

  • 第一组中但不在第二组中的所有端口都很有可能成为我的转发端口,并且确实减去这些组会产生 41273,557106010; 杯,脉冲和X,分别。

  • 6010使用 标识为 X 端口DISPLAY

  • 41273是杯子端口,因为lpstat -h localhost:41273 -a返回0
  • 55710是脉冲端口,因为pactl -s localhost:55710 stat返回0. (它甚至会打印我客户的主机名!)

(要进行集合减法 Isort -u并存储上述命令行的输出并用于comm进行减法。)

Pulseaudio 让我可以识别客户端,并且出于所有意图和目的,这可以作为分离需要分离的 SSH 会话的锚点。但是,我还没有找到一种方法来配合41273557106010以同样的sshd过程。netstat不会将该信息透露给非 root 用户。我只在我想阅读-PID/Program name列中得到一个2339/54(在这个特定的例子中)。很近 ...

hyd*_*yde 1

采取两个(请参阅历史记录,了解从服务器端执行scp 的版本并且更简单),这应该可以做到。其要点是这样的:

  1. 将环境变量从客户端传递到服务器,告诉服务器如何检测端口信息何时可用,然后获取并使用它。
  2. 一旦端口信息可用,将其从客户端复制到服务器,允许服务器获取它(在上面第 1 部分的帮助下),并使用它

首先,在远程端进行设置,您需要在sshd配置中启用发送环境变量:

sudo yourfavouriteeditor /etc/ssh/sshd_config
Run Code Online (Sandbox Code Playgroud)

找到包含的行AcceptEnv并将其添加MY_PORT_FILE到其中(Host如果还没有,则在右侧部分下添加该行)。对我来说,这条线变成了这样:

AcceptEnv LANG LC_* MY_PORT_FILE
Run Code Online (Sandbox Code Playgroud)

另请记住重新启动sshd才能生效。

此外,要使以下脚本正常工作,请mkdir ~/portfiles在远程端执行!


然后在本地,一个脚本片段将

  1. 为 stderr 重定向创建临时文件名
  2. 留下后台作业以等待文件包含内容
  3. 将文件名作为环境变量传递给服务器,同时将ssh stderr 重定向到该文件
  4. 后台作业继续使用单独的scp将 stderr 临时文件复制到服务器端
  5. 后台作业还会将标志文件复制到服务器以指示 stderr 文件已准备好

脚本片段:

REMOTE=$USER@datserver

PORTFILE=`mktemp /tmp/sshdataserverports-$(hostname)-XXXXX`
test -e $PORTFILE && rm -v $PORTFILE

# EMPTYFLAG servers both as empty flag file for remote side,
# and safeguard for background job termination on this side
EMPTYFLAG=$PORTFILE-empty
cp /dev/null $EMPTYFLAG

# this variable has the file name sent over ssh connection
export MY_PORT_FILE=$(basename $PORTFILE)

# background job loop to wait for the temp file to have data
( while [ -f $EMPTYFLAG -a \! -s $PORTFILE ] ; do
     sleep 1 # check once per sec
  done
  sleep 1 # make sure temp file gets the port data

  # first copy temp file, ...
  scp  $PORTFILE $REMOTE:portfiles/$MY_PORT_FILE

  # ...then copy flag file telling temp file contents are up to date
  scp  $EMPTYFLAG $REMOTE:portfiles/$MY_PORT_FILE.flag
) &

# actual ssh terminal connection    
ssh -X -o "SendEnv MY_PORT_FILE" -R0:localhost:631 -R0:localhost:4713 $REMOTE 2> $PORTFILE

# remove files after connection is over
rm -v $PORTFILE $EMPTYFLAG
Run Code Online (Sandbox Code Playgroud)

然后是远程端的片段,适合.bashrc

# only do this if subdir has been created and env variable set
if [ -d ~/portfiles -a "$MY_PORT_FILE" ] ; then

       PORTFILE=~/portfiles/$(basename "$MY_PORT_FILE")
       FLAGFILE=$PORTFILE.flag
       # wait for FLAGFILE to get copied,
       # after which PORTFILE should be complete
       while [ \! -f "$FLAGFILE" ] ; do 
           echo "Waiting for $FLAGFILE..."
           sleep 1
       done

       # use quite exact regexps and head to make this robust
       export CUPS_SERVER=localhost:$(grep '^Allocated port [0-9]\+ .* localhost:631[[:space:]]*$' "$PORTFILE" | head -1 | cut -d" " -f3)
       export PULSE_SERVER=localhost:$(grep '^Allocated port [0-9]\+ .* localhost:4713[[:space:]]*$' "$PORTFILE" | head -1 | cut -d" " -f3)
       echo "Set CUPS_SERVER and PULSE_SERVER"

       # copied files served their purpose, and can be removed right away
       rm -v -- "$PORTFILE" "$FLAGFILE"
fi
Run Code Online (Sandbox Code Playgroud)

注意:上面的代码当然没有经过非常彻底的测试,可能包含各种错误、复制粘贴错误等。任何使用它的人都更好地理解它,使用风险自负!我仅使用本地主机连接对其进行了测试,并且在我的测试环境中它对我有用。YMMV。