把一次失败的查询读成隧道崩溃的证据是很有诱惑力的,尤其当这个名字十分钟前还能用。但应答说的不是这回事。NXDOMAIN 是一次完整的 DNS 事务,其中解析器声明被查询的名字不存在;而隧道断开通常只留下沉默、超时,或对着错误服务器的解析尝试。不用 wireshark 的版本就是一次查询、一个应答方、一个应答码,这个三元组值得在任何配置改动之前记录下来。

应答实际上确认了什么

DNS 把客户端常常合并成一次失败的结果区分开来。不存在的名字会给出带 NXDOMAIN 码的否定应答,而存在但没有所请求类型记录的名字会给出带空回答段的成功应答。两者对应用来说都像是没用的东西,但只有一种意味着名字是错的。

这个区别决定了修复应该落在哪里。有 A 记录的主机上缺少 AAAA 记录,并不是主机不存在;为此重签证书或重建隧道,只会浪费掉唯一可用的线索。区分还决定了后续形态:命名问题在区域里修,类型问题则靠请求正确的记录,或者接受这个地址族确实不存在。

迷思:私有名字缺失就意味着隧道失败

这个迷思之所以活着,是因为两件事常常一起出现。客户端连上 VPN,操作系统保留着它原先信任的解析器,而它现在需要的内部名字被问到了一台从没听过私有区域的服务器。那台服务器诚实而否定地回答了。隧道确实在传包;应答只是关于一个应答方并不提供服务的命名空间。

分离视野的配置让这件事变得平常:同一个区域名在公网和内部有不同内容,只有内部解析器知道私有记录。因此拿到一个看起来权威的否定应答,证明的是哪一个解析器作了回答,而不是接口的状态。

两个配置细节决定结果。隧道必须下发或选中内部解析器,客户端必须把这个选择应用到正在查询的名字上。一条按域规则写出的后缀与你输入的记录略有出入,就会把查询送到公网路径,应答正确但毫无用处。

用正确的名字问正确的解析器

先记录应答方,再记录结论。一次不显示哪台服务器作答的查询,无法区分策略问题与命名空间问题,而两者的修法互不相干。

resolvectl query vpn.internal.example
resolvectl status | grep -A3 'DNS Servers'
dig +search app.intranet.example A
dig @10.8.0.1 app.intranet.example A

前两行显示本地解析器作了什么决定、会去问谁。+search 查询复现的是应用在追加搜索后缀时的行为,往往这才是真正的查询,而不是敲进诊断工具的那条。最后一行绕过客户端策略直接问内部服务器;如果它能成功而普通查询失败,说明区域没问题,查询的路由有问题。

否定缓存解释了修复之后的延迟

否定应答按设计会被缓存,RFC 2308 说明了否定缓存寿命如何从区域的授权记录推导出来。因此修正记录或解析器规则并不保证客户端立刻改变,反复尝试一个仍返回缓存应答的查询也证明不了新东西。只有换一个应答方、换一个名字,或清空缓存,才会产生新的证据。

先清空本地缓存,再向同一个应答方重复同一条查询。在使用本地解析器服务的系统上那是一条命令;在其他系统上是重启服务或某个平台工具,具体用哪个工具,远不如之后重复一次相同的测试重要。

把名字和路径分开

一旦名字解析到预期的私有地址,DNS 作为话题就结束了,接下来要问的是应用路径。能回答查询却拒绝连接的主机是另一个问题;在正确应答之后还继续调解析器,正是事件攒下三处无关改动的典型方式。在假设两者一致之前,把解析出的地址和服务真正监听的地址对比一下。

留下记录:查询、应答方、应答码和解析出的地址。这份记录也是让事后报告可核验的东西,而且它是诊断与“隧道好像很慢”这类故事之间的差别。

相关文章