我每隔几分钟就会从我的设备发出自动远程 SSH 端口转发请求:
ssh -y -f -N -R port:localhost:22 user@server \
-o ExitOnForwardFailure=yes -o ServerAliveInterval=60 \
-o StrictHostKeyChecking=no
如果端口转发成功,我重新启动设备并重置互联网连接,我无法连接到它或进行新的端口转发(这是正常的,因为我的 IP 已更改并且端口似乎已被占用)。我必须等待大约 1-2 小时,以便端口再次可用,或者我需要终止服务器上的进程。
我想在关闭设备之前从设备发送一个脚本来释放端口,以便重启后新的端口转发请求能够成功。
总结一下:如何从请求的设备远程取消端口转发,而不是终止服务器上的进程?
答案1
总结
sshd
在服务器上(在文件中)使用这些选项sshd_config
:
ClientAliveCountMax 3
ClientAliveInterval 15
客户端故事
我如何才能从请求的设备远程取消端口转发,而不是终止服务器上的进程?
简单的答案是:只需ssh
在客户端终止即可。即使你强制终止它,服务器也应该收到连接已终止的通知(因为内核负责完成这个工作)。
...如果通知到达服务器的话。我猜这就是问题所在。设备上的网络在ssh
终止之前以某种方式断开,并且没有办法通知服务器此特定 SSH 连接不再存在。
也许您可以重新设计客户端设置,以确保ssh
在对网络连接进行任何操作之前终止。我不知道您的客户端系统的详细信息,所以我不会告诉您确切可以做什么以及如何做。宽松的例子:单元及其依赖项(如果适用);和/或的systemd
包装器。reboot
shutdown
服务器端的故事
我假设 SSH 服务器是标准的sshd
。其默认配置 ( sshd_config
) 指定
TCPKeepAlive yes
TCPKeepAlive
指定系统是否应向另一端发送 TCP 保持活动消息。如果发送了这些消息,则连接中断或其中一台机器崩溃将会被正确注意到。但是,这意味着如果路由暂时中断,连接将会中断,有些人会觉得这很烦人。另一方面,如果不发送 TCP 保持活动消息,会话可能会无限期地挂在服务器上,留下“幽灵”用户并消耗服务器资源。
默认为
yes
(发送 TCP keepalive 消息),如果网络中断或客户端主机崩溃,服务器将会通知。这可以避免无限挂起的会话。
此机制并非 SSH 所特有。解释:
在Linux中,TCP keepalive参数为:
tcp_keepalive_intvl
tcp_keepalive_probes
tcp_keepalive_time
它们的默认值为:
tcp_keepalive_time = 7200
(秒)
tcp_keepalive_intvl = 75
(秒)
tcp_keepalive_probes = 9
(探测次数)这意味着 keepalive 进程在发送第一个 keepalive 探测之前会等待两个小时(7200 秒)的套接字活动,然后每 75 秒重新发送一次。如果
ACK
连续九次未收到响应,则将连接标记为断开。
(来源)。
这也解释了为什么您“必须等待大约 1-2 小时才能让端口再次空闲”。
如何更改这些值的示例(如果您有权限):
暂时地
echo 300 > /proc/sys/net/ipv4/tcp_keepalive_time
或者
sysctl -w net.ipv4.tcp_keepalive_time=300
通过编辑文件永久
/etc/sysctl.conf
添加:net.ipv4.tcp_keepalive_time=300
然后调用
sudo sysctl -p
以应用更改。
但这些是系统范围的设置。一般来说,任何更改都会影响超过sshd
。这就是为什么最好使用 SSH 特定的解决方案。再次来自man 5 sshd_config
:
ClientAliveCountMax
sshd(8)
设置可在不从客户端收到任何消息的情况下发送的客户端活动消息数(见下文) 。如果在发送客户端活动消息时达到此阈值,sshd
将断开客户端连接并终止会话。需要注意的是,客户端活动消息的使用与 有很大不同TCPKeepAlive
。[…] 当客户端或服务器依赖于了解连接何时变为非活动状态时,客户端活动机制非常有用。默认值为
3
。如果ClientAliveInterval
(见下文)设置为15
,并且ClientAliveCountMax
保留默认值,则无响应的 SSH 客户端将在大约 45 秒后断开连接。此选项仅适用于协议版本 2。
ClientAliveInterval
设置超时间隔(以秒为单位),如果在此间隔后未从客户端收到任何数据,
sshd(8)
则将通过加密通道发送一条消息以请求客户端做出响应。默认值为0
,表示这些消息不会发送到客户端。此选项仅适用于协议版本 2。
如果您只能sshd
在服务器上重新配置,那么在我看来这是最优雅的方式。让sshd_config
包含如下行:
ClientAliveCountMax 3
ClientAliveInterval 15
笔记:
-o ServerAliveInterval=60
您使用的选项与 类似ssh
;它允许客户端检测断开的连接。但它不会影响服务器。- 你可能会考虑
autossh
在客户端上。
返回客户端
假设你无法重新配置服务器和客户端。我知道你说过
而不是终止服务器上的进程
但在这种情况下,终止它可能是最好的选择。作为sshd
服务于特定连接的分支,只需终止正确的进程而不影响其余进程就足够了。要从客户端启动终止,您应该修改调用的方式ssh
。你说:
每隔几分钟:
ssh -f …
除此以外,您还可以运行类似于以下脚本的脚本,只需运行一次(例如通过@reboot
crontab)。它会sshd
首先尝试终止已保存的 PID(服务器上的),然后建立隧道并保存其sshd
PID。如果隧道无法建立或最终终止,则脚本会休眠一段时间并循环。
#!/bin/sh
port=12345
address=user@server
while :; do
ssh $address '[ -f ~/.tunnel.pid ] && kill `cat ~/.tunnel.pid` && rm ~/.tunnel.pid'
ssh -o ExitOnForwardFailure=yes -R ${port}:localhost:22 $address 'echo $PPID > ~/.tunnel.pid; exec sleep infinity'
sleep 60
done
笔记:
- 这是一个快速而粗糙的脚本,一个概念证明;可移植性不是我的首要任务。
- 为了清楚起见,我省略了您最初使用的一些选项。
- 如果运行该脚本两次,两个实例将一次又一次地终止彼此的隧道。
答案2
ssh
可以使用转义符(~
默认情况下位于行首)或~?
帮助取消转发
Supported escape sequences:
~. - terminate connection (and any multiplexed sessions)
~B - send a BREAK to the remote system
~C - open a command line
~R - request rekey
~V/v - decrease/increase verbosity (LogLevel)
~^Z - suspend ssh
~# - list forwarded connections
~& - background ssh (when waiting for connections to terminate)
~? - this message
~~ - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)
并将~#
列出连接,但实际上并不适合通过命令终止它们
The following connections are open:
#3 client-session (t4 r0 i0/0 o0/0 fd 8/9 cc -1)
可以通过~C
以下方式找到help
ssh> help
Commands:
-L[bind_address:]port:host:hostport Request local forward
-R[bind_address:]port:host:hostport Request remote forward
-D[bind_address:]port Request dynamic forward
-KL[bind_address:]port Cancel local forward
-KR[bind_address:]port Cancel remote forward
-KD[bind_address:]port Cancel dynamic forward
所以理论上你可以通过~C-KL22
打开命令行并LocalForward
通过端口号取消来取消。
目前尚不清楚这是否能解决您的问题;端口通常需要不到 1-2 小时的时间来清除TIME_WAIT
或类似...