隧道回来了,客户端显示已连接,浏览器却还在切断前那个页面上转圈。VPN 重连恢复的是一条路径,它并不恢复应用和远端在旧路径上积累的连接状态。这是两件需要修复的不同事情,把它们混为一谈只会让你毫无理由地回到 VPN 设置。

一个测试能把两者干净地分开,而且只要十秒。在旧请求还挂着的时候,向同一目的地发送一个新请求。

重连之后什么还在,什么不在了

隧道是一组路由、密钥和地址,重连可以把这三样都恢复,而每一条已经建立的流依然是死的。操作系统之所以让套接字保持打开,是因为应用请求过它,而那个套接字的超时可能还在几分钟或几小时之后。在下面,对端已经忘记了这条流,一条转换表项已经过期,或者路径现在经过一个从未见过握手的端点。重连流程里没有任何一步会走到应用的套接字去修复它们。

有些客户端加了一个钩子,在重连时丢弃过期的连接,大多数并不这样做。这里的行为随客户端、平台和版本而不同。

远端也把你忘了

远端保存着自己的状态:一个 TLS 会话、一个 TCP 控制块,也许还有一条把你的地址和端口绑到这条流上的映射表项。路径上的转换设备即使在隧道很快重建时,也可能在空档里让那条映射过期。映射一旦消失,到达的包属于服务器不再认识的流,而回应可能是客户端永远看不到的重置。应用把这种情况报成卡住而不是报错,因为它们还在等一个永远不会应答的套接字。

服务器一侧的空闲超时会按自己的时间表做同样的事,这就是为什么会话可以在没有任何网络事件的情况下死掉。一条空闲时间超过服务器预期的连接,在你注意到隧道移动之前就已经消失了。

新请求正是把两者分开的那个测试

在旧请求还挂着的时候,向同一主机发送一个新请求,并观察两者。如果新请求成功,那么隧道、路由和名称解析都在工作,故障只限于那条旧的流。如果新请求同样失败,那就是路径出了问题,应用的会话状态只是误导。

测试期间让任何直连流量的拦截保持原样。一个靠意外回退路径成功的请求,对隧道什么也证明不了。

用新连接确认,而不是旧连接

下面的命令会显示仍然打开的套接字、发起一个新请求,并检查会话表里是否还有这条流的表项。在重连前后各跑一次第一条,好让差别看得见。把主机和端口换成你自己的服务。

ss -tanp | grep -E ':(443|8443)'
curl -sS -o /dev/null -w '%{http_code} %{time_connect}\n' --max-time 8 https://intranet.example.com/
conntrack -L 2>/dev/null | grep 8443
dig +short intranet.example.com

一条已建立却不跑流量的套接字就是要找的特征,而返回状态码的新请求说明路径是活的。连接表回答是否还有东西把这条流映射着,那正是客户端看不到的部分。解析器查询覆盖了页面加载的另一半,因为名称失败和连接卡住在浏览器里看起来一模一样。四个输出合起来,就能把过期的会话和坏掉的隧道分开。

记下中断的时刻、重连的时刻,以及旧请求和新请求之间的差别。这份记录能把含糊的抱怨变成别人可以据以行动的报告。

相关文章