为什么使用 ProxyCommand 时 OpenSSH_8.4p1 会终止共享同一连接的其他会话?有什么方法可以防止这种情况发生吗?
注意:如果省略 ProxyCommand 参数,则此行为似乎不会发生。
重现步骤:
- 终止与本地主机的所有现有共享连接:
ssh -o ControlPath=/tmp/%C -O exit 127.0.0.1 2>/dev/null
ssh -o ControlPath=/tmp/%C -O exit localhost 2>/dev/null
- 在不同的终端中并行运行以下命令两次:
ssh -F none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ControlMaster=auto -o ControlPath=/tmp/%C -o ControlPersist=1d \
-o ProxyCommand='ssh -W %h:%p 127.0.0.1' \
localhost 'sleep 3600'
- 通过输入 control-C 以 SIGINT 中断第一个 ssh 进程。
预期行为
- 只有 SIGINT 进程才会终止。
- 其他进程继续运行,不受影响。
实际行为
- 两个进程均终止。
答案1
只有 SIGINT 进程才会终止。
“过程”是一个错误的前提。Ctrl+C 发送SIGINT
到前台进程团体终端. 一个进程组可能包含多个进程。
那么这是相关的:
ProxyCommand
指定用于连接服务器的命令。命令字符串延伸到行尾,并使用用户的 shellexec
指令执行,以避免 shell 进程延迟。
[…]
(来源)
该命令在与 main 相同的进程组中本地执行ssh
。在您的示例中,命令是,ssh …
但通常它可以是任何内容。无论如何,命令都无法识别,ControlMaster
并且ControlPersist
您将其用于 main ssh
。
当您按下Ctrl+时C,前台进程组中的每个进程都会获得SIGINT
。“主”进程ssh
退出,但不会影响 中的套接字,因为在之外的ControlPath
情况下,此套接字从一开始就由 处理ControlPersist
no
完后还有 ssh
进程是故意在其自己的进程组中生成的,这使其能够存活。在这种情况下,您可以称其为真正的主进程ssh
。
出现意外行为是因为 指定的命令ProxyCommand
在前台进程组中生成,并且它SIGINT
与ssh
您想要中断的 相处融洽。该命令对信号的反应就像它通常会做的那样。在您的情况下,命令在 时终止SIGINT
。并且由于该命令应该中继所有数据,因此主连接(真正的主)现在毫无用处。它与所有依赖进程ssh
一起终止。ssh
因此,原始版本ssh
会进行额外的工作以确保ssh
处理主连接(和套接字)仍然存在Ctrl+ C,但它不会对ProxyCommand
同样重要的指定命令执行此操作。我认为你可以称之为错误。
如果在中指定的命令ProxyCommand
是由免疫产生的,ssh
那么可能会更好它是进程组。我还没有对此进行足够彻底的分析。无论如何,目前情况并非如此,该命令是在按下Ctrl+时作为前台进程组的进程组中生成的C,因此它会得到SIGINT
。
一个解决方法是使命令不受 的影响SIGINT
。ProxyCommand
你不能trap
直接在里面使用,因为会自动ProxyCommand
使用exec
并且exec trap …
没有意义,也不会起作用。你还需要另一个 shell:
ssh -F none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ControlMaster=auto -o ControlPath=/tmp/%C -o ControlPersist=1d \
-o ProxyCommand='sh -c "trap \"\" INT; exec ssh -W %h:%p 127.0.0.1"' \
localhost 'sleep 3600'
我的测试表明,免疫SIGINT
不会阻止这个内部ssh
在时间到来时退出,并且主连接应该由于有限的ControlPersist
设置而终止。