隧道断开,终止开关触发,一个已完成四分之三的下载要么卡住,要么立即失败。这两种结果来自不同的执行选择,而不是不同程度的保护。被丢弃的数据包让连接继续等待,被拒绝的数据包则关闭连接并通知应用。决定要哪种行为是设计问题,而验证它是另一项工作。

丢弃、拒绝与重置并不相同

丢弃流量的防火墙规则会把数据包扔掉且不回任何响应,因此发送方会按自己的计时器不断重传。以 TCP 重置拒绝的规则会立即关闭连接,应用随即看到错误。多数终止开关选择丢弃,因为拒绝会暴露策略,也因为丢弃更接近网络已死的表现。

阻断也不等于关闭套接字。应用仍然持有一个处于已建立状态的描述符,只是缺少数据包。这就是为什么在操作系统坚称连接存在时,页面却能在加载指示器上停留数分钟。请阅读文档化的执行策略,而不要从应用的提示去推断,因为两种表现看起来都像网络中断。

断档期间应用会做什么

TCP 会以递增的延迟重传,Linux 默认的重试预算大约在十五分钟后耗尽,而不是几秒。在那之前,套接字既没有可用地打开,也没有关闭。带有自身 keepalive 或请求超时的应用往往更早放弃,这就是同一台机器上的两个程序在一次故障中表现不同的原因。

隧道恢复后,本地套接字没有变化,但路径可能已经变了。新的公网源地址会在服务器上产生不同的四元组,因此对端的有状态防火墙不再匹配该会话。即使客户端从未看到错误,连接也已经死了。

决定是否需要恢复

在两个契约之间做选择。要么会话必须熬过短暂断档,这需要稳定的地址和能保留状态的服务器;要么应用必须干净地重新连接,这需要可安全重复的重试逻辑。介于两者之间的任何做法都会带来最糟的结果:客户端重发服务器已经提交的内容,或者为没人看得见的会话报告成功。

稳定的地址是人们容易忽略的部分,因为落到另一个公网地址的重连会使服务器侧状态失效,即使客户端的套接字看起来健康。写下两个契约中哪一个适用于每个应用,因为它们很少全部吻合。

分别检查阻断与恢复

把防泄漏和会话存活当作两项属性,因为通过其中一项并不能说明另一项如何。存活的会话不能证明直连流量被阻断,死亡的会话也不能证明每条直连路径都被阻断。

ss -tnp state established '( dport = :443 )'
sudo conntrack -L -p tcp --dport 443
sudo tcpdump -ni any 'tcp port 443 and tcp[tcpflags] & tcp-syn != 0'

观察恢复时使用无害的测试操作,以免重复提交变成事故。记录你实际看到的故障形态:应用显示了什么、套接字是否存活、以及它何时自行恢复。

把预期行为写在执行规则旁边并注明日期。下一个修改该规则的人需要知道部署承诺的是哪一个契约。

相关文章