日志显示服务器推送了该选项,隧道也已建立,但内部名称仍然解析失败。直接向解析器查询能拿到应答,说明服务器没问题,可每个应用仍在询问本地路由器。到达客户端的只是一个提议,而不是对解析器的修改;负责名称解析的组件在 Linux、Windows 和 macOS 上各不相同。一旦把选项与其所有者分开,四类故障几乎覆盖所有报告:没有 hook、两个 hook 相互竞争、分离 DNS 按设计工作,或短名称需要的是后缀而不是服务器。

推送的选项只是提议,不是安装

在服务器上,两条指令描述的是意图,而不是效果:

push "dhcp-option DNS 10.8.0.1"
push "dhcp-option DOMAIN corp.example"

客户端并不会得到配置好的解析器。它得到的是环境变量, foreign_option_1 以及后续变量,每推送一行就有一个,而必须有某个本地程序读取它们并重写操作系统的解析器状态。在 Linux 上默认没有任何程序这样做,所以即使服务器正确、客户端已连接,仍会通过 LAN 解析名称。

两端之间有三个环节:推送必须到达,客户端必须暴露它,hook 必须应用它。中间环节看客户端日志,最后环节看解析器状态,切勿反过来,因为一行日志只能证明前两个。

确认推送已到达并找出所有者

先确认启动方式:裸 openvpn 命令、systemd 实例、NetworkManager 还是厂商客户端。这决定了 hook 能放在哪里。在装有 systemd-resolved 的主机上, resolvectl status 会列出每个链路的 DNS 服务器和搜索域,而隧道链路并不总叫 tun0。

如果推送的地址出现在日志里却不在任何链路上,说明集成缺失。如果它在链路上而查询仍从别处发出,问题在于 哪些查询会选择该链路。NetworkManager dns=dnsmasq 中的 /etc/NetworkManager/NetworkManager.conf 文件会启动一个本地转发器,故意实现分离 DNS:VPN 域内的名称走 VPN 解析器,其余保留原路径。当你期待全部流量迁移时,这种行为看起来像故障;用 nmcli connection show "My VPN"检查每条连接的设置,其中 ipv4.dns 和 ipv4.dns-search 保存着设备所报告值背后的实际配置,然后再判断它坏了。如果确实所有查询都该走 VPN 解析器,再从 dns=dnsmasq 的 NetworkManager.conf 中删掉它并重启 NetworkManager,这会把解析交回连接设置;否则保留分离 DNS,改为修正推送的域列表。

Linux:把选项交给解析器

发行版软件包为此提供了 hook,并且必须允许客户端运行它。在没有 systemd-resolved 的主机上,典型配置是:

script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf

使用 systemd-resolved 时,较旧的 resolv.conf 脚本可能什么都改不了,因为 stub 解析器按链路保存状态。 update-systemd-resolved 辅助工具记录了一种 hook 形式,它使用 up-restart 和 down-pre,这在接口比单次连接尝试活得更久时很重要。无论选哪种,状态必须由一个组件拥有。在 NetworkManager 旁边再放一个无关的 hook,会让同一链路有两个写入者,结果就取决于顺序而不是你的配置。

还有一种情况:NetworkManager 完全忽略推送的选项,直到把服务器地址填入连接的 IPv4 设置,之后解析器状态才显示它在使用中。值得在重写脚本之前先试这一步,因为它把问题从 shell hook 移到已经承载隧道的连接配置文件。

Windows 与 macOS 应用的是不同部分

Windows 客户端确实会设置 VPN 适配器的 DNS 服务器,但 Windows 仍按跃点数选择接口,也仍需后缀来展开短名称。要确认的是适配器状态,而不是配置文件:

Get-DnsClientServerAddress -InterfaceAlias "My VPN"
Get-DnsClientNrptPolicy

如果地址缺失,用 Set-DnsClientServerAddress -InterfaceAlias "My VPN" -ServerAddresses 10.8.0.1设置它,然后把后缀加入适配器的 IPv4 属性,使 fileserver 变成 fileserver.corp.example。整台机器范围的 SearchList 值位于 Tcpip\Parameters 下时也有同样效果。长名称能解析而裸名不能,说明解析器已经正确,问题在搜索列表。修复顺序是:推送 DOMAIN 让客户端学到后缀,把该后缀加入适配器或连接的搜索列表,并作为临时措施把名称固定在 hosts,或 Windows 上的 LMHOSTS 中;NetBIOS 浏览不跨越路由器,所以办公室里能找到短名称的广播永远到不了隧道客户端。

block-outside-dns block-outside-dns 是 Windows 客户端的行为,不是可移植的配置行,把它复制到 Linux 或 macOS 配置里并不会在那边重现。如果 Windows 上公网名称可用而内部名称失败,请把高级设置里的适配器顺序与 ipconfig /all对比。在 macOS 上,每个服务的解析器顺序由系统决定,因此用 scutil --dns 查看哪个域会询问哪个解析器,并把任何单一的报告式配置修复视为依赖客户端而非通用。

同一台笔记本上的虚拟机和这些毫无关系。请给客户机自己的内部域解析器条目,或让虚拟机管理器的 NAT 通过主机转发其查询,而这只有在主机隧道在线时才正确。

历史是第三个原因。旧配置里的 DNS 服务器或后缀设置能在重连后残留,所以当适配器看起来正确而查询仍失败时,把活动适配器与 Get-VpnConnection 比较,并在改动服务器之前清除或覆盖旧条目。

证明查询去了哪里

一次查找期间的抓包就能定论: tcpdump -ni eth0 port 53 显示查询走在物理接口上,说明它们离开了隧道,解析器从未被切换。 dig @10.8.0.1 name.corp.example 随后区分剩下两种情况。若直接查询有应答,解析器可用而客户端没用它;若超时,解析器经隧道不可达,任何本地改动都没用。

如果仍在使用的解析器是服务商分配的,隧道加密了流量,但名称对该服务商可见。这是泄漏问题而非功能问题,同一个抓包就能回答:物理接口上的查询走向了隧道之外。

可达性还包括 VPN 主机上的防火墙,它必须放行来自地址池的 UDP 和 TCP 53。被丢弃的端口看起来正像推送坏了,只从隧道网络内部应答的解析器也一样。断开并重连一次后再抓一次包,因为首次连接出现、第二次消失的设置是 hook 的生命周期缺陷,而不是缺少选项。

保持单一所有者并按顺序验证

根据猜测的隧道状态重写适配器 DNS 的脚本,会重复配置文件里已有的配置,而且猜测第一次出错时就静默失败。优先使用连接自身的 DNS 设置或一个有文档的 hook,并记录你选了哪个,免得下一个人再加第二个。

用四项检查闭环:推送地址出现在隧道链路上、内部名称能作为完全限定名解析、同一名称去掉后缀也能解析、以及重连一次后两项仍成立。一次抓包就能告诉你四项里哪项失败。 OpenVPN 2.6 手册 列出了 dhcp-option 以及脚本指令及其平台说明。

相关文章