Ban*_*uin 17 openssh ssh-tunneling shell-script port-forwarding
当为远程转发动态分配端口(又名-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_SERVER
和PULSE_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_SERVER
和PULSE_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
,55710
和6010
; 杯,脉冲和X,分别。
6010
使用 标识为 X 端口DISPLAY
。
41273
是杯子端口,因为lpstat -h localhost:41273 -a
返回0
。55710
是脉冲端口,因为pactl -s localhost:55710 stat
返回0
. (它甚至会打印我客户的主机名!)(要进行集合减法 Isort -u
并存储上述命令行的输出并用于comm
进行减法。)
Pulseaudio 让我可以识别客户端,并且出于所有意图和目的,这可以作为分离需要分离的 SSH 会话的锚点。但是,我还没有找到一种方法来配合41273
,55710
并6010
以同样的sshd
过程。netstat
不会将该信息透露给非 root 用户。我只在我想阅读-
的PID/Program name
列中得到一个2339/54
(在这个特定的例子中)。很近 ...
采取两个(请参阅历史记录,了解从服务器端执行scp 的版本并且更简单),这应该可以做到。其要点是这样的:
首先,在远程端进行设置,您需要在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
在远程端执行!
然后在本地,一个脚本片段将
脚本片段:
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。