我一直在超级用户上浏览答案,但是没有找到可以解决我的用例的答案。
我想链接两个命令来让我通过隧道进行隧道传输:
ssh -L 22:hostB:22 user@hostA
和
ssh -L 10002:localhost:10001 user@localhost
现在我做的是输入第一个命令,打开另一个终端,然后输入第二个命令。如果我这样做
ssh … && ssh …
仅当我“退出”第一个连接后,第二个命令才会执行。
由于 hostA 上的服务器配置,jumphost 等更高级的 ssh 选项不起作用,因此我很确定这是唯一可以像这样访问 10001 端口的选项。
有没有办法将这两个命令链接到一个别名中,或者使用一些带有超时的简单 bash 脚本等?
答案1
我认为你不能仅仅ssh -L 10002:hostB:10001 user@hostA
因为
- 要么监听 B 的任何东西
10001
都只绑定到本地接口, - 或者 B 上的防火墙不允许
hostB:10001
A 连接, - 管他呢。
ssh … && ssh …
仅当我“退出”第一个连接后,第二个命令才会执行。
是的,这是正常的。在下一个命令运行之前,前面的部分&&
必须终止并返回退出状态(或者不终止,取决于状态)。
这将几乎立即运行第二个ssh
但它有缺陷:
(ssh…&)&&ssh…
这是有缺陷的,因为第一个ssh
对第二个没有影响。第二个可能在第一个建立隧道之前就运行了。询问密码也可能有问题。
对于此用例ssh
有-f
选项。从man 1 ssh
:
-f
请求ssh
在命令执行之前进入后台。[…]
因此基本命令可能是:
ssh -fNL 22:hostB:22 user@hostA && ssh -NL 10002:localhost:10001 user@localhost
-N
是否要使用第二个取决于您ssh
。现在的诀窍是,第一个ssh
进程在询问密码(如果适用)并建立第一个隧道后分叉到后台,因此暂时有两个“第一个”ssh
进程。后台进程开始处理隧道;前台进程退出,这样就可以&&
工作了。
您可能需要-o ExitOnForwardFailure=yes
。如果无法建立第一个隧道,则第一个隧道ssh
将失败退出,第二个隧道将无法运行。改进的命令是:
ssh -fNL 22:hostB:22 \
-o ExitOnForwardFailure=yes \
user@hostA &&
ssh -NL 10002:localhost:10001 user@localhost
请注意,当第二个ssh
退出时,第一个(在后台)仍然存在。优点是您可以ssh
稍后运行第二个。缺点是如果您不想让它保留,则需要手动终止第一个。考虑到这一点,请考虑以下方法:
ssh -fNL 22:hostB:22 \
-o ExitOnForwardFailure=yes \
user@hostA
ssh -NL 10002:localhost:10001 user@localhost
请注意,没有&&
。现在,如果旧的第一个隧道ssh
仍在运行,那么新的隧道将失败,因为它无法绑定到端口。ssh
无论如何,第二个隧道都会运行,它将使用第一个隧道,而不管隧道有多旧。
可能会发生没有旧版本的情况ssh
,并且第一个ssh
版本会因某种原因失败。如果是这样,第二个版本将运行,并且可能会失败(连接被拒绝),因为端口上没有任何监听22
。
但您可能不想留下“空闲”ssh
进程,您可能希望在第二个ssh
进程退出后自动终止它。显然killall ssh
可能会造成附带损害。让我们找一些更微妙的东西:
-M
将ssh
客户端置于“主”模式以进行连接共享。[…][…]
-O ctl_cmd
控制主动连接多路复用主进程。[…][…]
-S ctl_path
指定用于连接共享的控制套接字的位置 […]
#!/bin/sh
socket=/tmp/hostA.socket
ssh -fNL 22:hostB:22 \
-o ExitOnForwardFailure=yes \
-MS "$socket"
user@hostA &&
{
ssh -NL 10002:localhost:10001 user@localhost
ssh -S "$socket" -O exit dummy
}
第二个命令ssh
终止后,第三个命令将通知第一个命令退出。在第三个ssh
命令中,重要的是套接字,而不是主机名;您仍然需要提供一些主机名,因此dummy
。第一个命令ssh
将在退出前删除套接字,应该不会留下任何垃圾。
但仍有几个问题值得担心:
- 如果脚本中断,则第一个
ssh
会保留。为了以防万一,要么删除,要么在最开始&&
发送(它可能是第零个)。-O exit
ssh
如果第一个
ssh
突然死机,套接字可能会保留下来。这将使任何新的第一个失败。一个好主意可能是像这样ssh
扩展零个:ssh
ssh -S "$socket" -O exit dummy || rm "$socket"
/tmp/hostA.socket
可能不是套接字的最佳位置。在命令行选项中ssh_config
,等效于。看看-S
ControlPath
man 5 ssh_config
对此有如下评论:建议
ControlPath
用于机会性连接共享的任何文件至少包括%h
、%p
和%r
(或备选%C
)并放置在其他用户不可写入的目录中。/run/user/$UID
如果您的操作系统支持的话,则可能存在“其他用户不可写的目录” (我相信这是systemd
的事情)。