从办公室克隆能用,用手机热点克隆同一个仓库也能用,而从酒店网络克隆同一个仓库却会一直挂起,直到打开 VPN。SSH 握手完成,会话却在第一次真正传输时安静下来。在客户端配置里加一行,就能同时治好 git 和 ssh。当一项客户端设置一次修好多个工具时,故障出在客户端发送的内容里,而不是它发送所经过的链路上。

把症状读成路由差异

能治好问题的隧道,很少是真正的修复。它换掉了路由、运营商和中间设备,而笔记本仍然在生成一模一样的包。如果一条路径错误处理了某个头部,另一条却原样放行,差异就在中间设备的策略里,而不在客户端。这里的隧道只是证人,不是解法。

会话死亡的位置能进一步缩小范围。名称解析或密钥交换失败的情况,看起来不是这样。一个通过认证、打印出 banner,然后在第一次大块写入时冻结的会话,已经证明路由、命名和凭据都没问题。

客户端给每个 SSH 套接字盖上一个 QoS 字节

从 7.8 起,每个 OpenSSH 连接都会在 IP 头部携带一个值,路由器可用它来给流量排优先级。终端会话拿一个值,批量复制拿另一个,其理论是打字的人比文件传输更值得优待。大多数网络完全忽略这个提示,所以它多年无人察觉。少数 Wi-Fi 驱动、移动运营商和 VPN 集中器会据此行动,其中一些还行动得很糟。

当这个值在流中途被改写或丢弃时,连接不会失败,而是停止推进。重传增加,窗口崩塌,应用什么也不报告,因为没有任何东西报错。正是这种沉默,让人们把症状描述成卡住而不是失败。

先用 ssh -V 确认客户端版本。7.8 及以上默认带有这种行为。

把这件事与 MTU 和 DNS 分开的三条命令

在修改任何配置文件之前,值得先排除两种相似故障。MTU 故障会让小交互通过,却在第一个大包上冻结;而名称故障根本建立不了会话。

ssh -o IPQoS=none -v [email protected]
ssh -G [email protected] | grep -i ipqos
sudo tcpdump -ni any -vv "tcp port 22"

第一条命令在关闭标记的情况下重做失败的操作,一旦成功,就是现有的最强单项证据。第二条打印客户端实际会应用到该目标地址的配置,能抓出你早已忘记的文件里的旧条目。第三条在传输卡住时显示双向的 TOS 字节,有意思的模式是坏路径上有值、好路径上没有。

抓包要在笔记本上做,而不是在服务器上,因为设置值的是发送方,任何一个方向都可能改动它。用同一次抓包比较可用与不可用的路径,差异无需猜测就会显现。

把例外写进一个文件,一次只针对一个主机

例外应该放在它所适用的主机旁边,并用注释说明它为什么存在。

Host gitlab.example.internal
    IPQoS none
    # direct route rewrites the marking; the tunnel route passes it

作用范围比取值本身更重要。通配条目会在所有地方关掉这种行为,丢掉一个在规矩网络里有用的提示,还让后人说不清它是什么时候出现的。按主机写的条目可读、可回退,网络修好后删掉也很容易。个人绕行用用户文件,整个机群都需要同一例外时用受管文件。

要讲清楚例外不覆盖什么。它只作用于 SSH,所以 HTTPS 远程、软件包镜像和浏览器都在它之外,其中任何一个出问题都需要各自的证据。经过堡垒机的链路上,每一跳都用客户端配置,而卡住的那一跳才值得划定范围。

与网络负责人闭环

把发现作为四条事实交出:客户端版本、测试过的路径、使用的 VPN 客户端,以及标记由哪个方向携带。这四条足够网络负责人复现这种处理,并判断它是否有意为之。

如果例外治不好卡顿,就删掉它。扛过一次失败实验的绕行会变成传说,下一个人要花一小时证明它毫无作用。如果它确实治好了卡顿,那条路径对其他所有经过它的工具仍然是错的,修复该由运营中间设备的人负责。

客户端上的改动是有保质期的权宜之计。把那个期限写进注释。

相关文章