附近的 DNS 答案看起来像是问题已经解决,直到连接落到别处。地理 DNS 和 anycast 都试图缩短路径,而它们依据的信息并不相同。两者都不保证你到达的网关可达、不保证它呈现客户端预期的身份,也不保证节点切换时会话仍然存活。下面的检查把这四个问题分开。

检查解析器实际报告的是谁的位置

应答拓扑敏感查询的权威服务器通常看到的是发问的递归解析器地址,而不是输入域名之人的地址。集中式解析器可能离它服务的客户端很远,答案反映的是解析器而不是客户端。这不是缺陷;除非提供额外信息,协议本来就如此。 EDNS Client Subnet 规范 同时描述了这一限制和可选的补救办法。

当每个参与方都支持并启用时,EDNS Client Subnet 可以携带来源网络信息。它是可选的,会带来隐私与缓存方面的影响,也不等于可以假定任何公共解析器都会报告精确的用户位置。围绕它做设计之前,先询问解析器运营方。

dig +short vpn.example.com @1.1.1.1
dig +short vpn.example.com @9.9.9.9
dig +subnet=203.0.113.0/24 +short vpn.example.com

检查 anycast 路由做了什么决定

在 anycast 中,多个位置为同一个服务地址宣告可达,网络把每个客户端送到其路由偏好的节点。这个选择由 BGP 和周边策略决定,而不是地图上的距离。拓扑邻近通常也不等于更好的往返性能,因为看起来短的路径可能拥塞或不对称。你得到的地址并不能说明是哪个节点应答的。

从服务侧或从点名该位置的响应来确认实际服务的节点,而不是只看 DNS 答案。traceroute -n 198.51.100.20 显示你走过的路径,却仍然不点名路径末端的这台主机,所以要把它读成路径而不是答案。

检查节点切换后会话是否存活

DNS 查询很短,VPN 会话很长,两者对状态的要求完全不同。路由可能在会话中途发生迁移,一旦如此,后续数据包可能到达从未见过你隧道的节点。能否存活取决于实现方式:共享状态、有意重连,还是有文档记录的失败。仅凭地址,这些一个都提供不了。

观察这次迁移,而不是假定它。路径变化时 sudo tcpdump -ni any -c 20 esp 会显示外层数据包,而对端地址突然改变就是迁移正在发生。为你运行的那套实现明确定义预期行为,然后通过强制路径变化来测试。如果设计假定了会话复制,就点名该机制,并点名它不覆盖的失败情形。

检查身份是否仍然匹配

每个入口点都必须呈现客户端配置所预期的身份。为服务名签发的证书可以跨节点使用,而按节点签发的证书会迫使客户端接受新东西,用户学会一路点过警告就发生在这里。让预期身份与配置和入口模型保持一致。

从一个到达了非常规节点的客户端去检查,因为按节点证书变得可见的正是这种情况。openssl s_client -connect vpn.example.com:443 -servername vpn.example.com 会打印证书链和名称,那正是客户端比较的内容。然后决定是收窄配置,还是更换证书模型。

检查记录,而不只是第一个答案

评估三种情形,并为每种写下四项事实:正常入口、某个位置不可用,以及约定好的恢复情形。四项事实分别是所用的解析器、返回的地址、实际服务的位置,以及一次新的应用结果。这份记录把 DNS 选择问题与网关问题、路由问题或会话状态问题区分开。没有它,这四者在客户端看来一模一样。

dig +short vpn.example.com
getent hosts vpn.example.com
curl -s -o /dev/null -w "%{http_code}
" https://vpn.example.com/health

要从有代表性的接入网络运行这些情形,而不只是从办公室,因为整个问题就在于不同客户端实际到达哪个入口点。一个网络只是一个样本。

相关文章