在发行版里,按地址 ping 是通的,装包和 git 拉取却不通,而且故障正好出现在隧道连上之后。停掉的只是域名解析,这说明问题在 DNS 而不在连通性。在这里,重装软件包是最流行的第一个错误动作。WSL2 有两条互相独立的路径能造出完全相同的症状,而它们需要不同的修法。

发行版为什么要留着自己的 resolver

发行版内部并不使用 Windows 的 DNS 客户端。WSL2 在启动时自建一个 resolver 文件,指向 Windows 为工具虚拟机在虚拟交换机上发布的那个 stub 地址。

这套安排在普通的网络变动下是稳定的,因为它只写一次、反复复用。但只要隧道客户端在工具虚拟机启动之后改写主机的网卡列表,这套安排就不再正确。

发行版内部没有任何东西盯着这次改动,于是文件继续指向一个可能已经不在路径上的地址。接下来的查询就只能等待、超时,或者从一个从未听说过内网区域的服务器那里拿到答案。从内部看,resolver 干脆不再应答,所有域名一起失败。

先两侧同时取证

在改动任何一侧之前,先记录发行版看到的样子,再记录 Windows 看到的样子。正是这两个视角合起来,才能区分出故障的类型。

在发行版里,打印 resolver 文件、路由表,以及一次内网查询和一次公网查询:

ip -4 route
cat /etc/resolv.conf
getent hosts intranet.corp.example
getent hosts example.com

在 Windows 上,按网卡列出 DNS 服务器,并列出带跃点值的 IPv4 路由表。VPN 客户端通常会给 WSL2 所在的子网装一条路由,而它的跃点值决定这部分流量是走进隧道还是留在本地。

Get-NetAdapter | Get-DnsClientServerAddress
route print -4

跃点值还是过期文件

第一种故障是过期文件。resolver 是按启动那一刻的网卡状态写的,而隧道改变了这个状态。它的 stub 地址现在可能已经不可达,也可能指向一个对内网区域一无所知的服务器。

第二种是路由问题。当 VPN 添加一条覆盖 WSL2 子网的路由,且跃点值低于主机的链路路由时,发往工具虚拟机的报文就会进入隧道。于是超时的不只是 DNS,而是每一个 WSL2 服务。

区分这两种情况的测试只需要一行。如果公网 resolver 这样的数字地址在域名失败时仍然能 ping 通,说明路径完好,问题在 DNS。这正是值得改动 resolver 的情形,而不值得改动的情形也随之清楚了。如果数字测试也失败,那就是工具虚拟机丢了路由,任何 resolver 文件都救不回来。

遇到路由问题,就把隧道网卡上的自动跃点去掉,给它一个高于 WSL2 链路路由的数值:

Get-NetAdapter | Where-Object InterfaceDescription -Match VPN | Set-NetIPInterface -InterfaceMetric 5500
wsl.exe --shutdown

关机这一步很关键。改跃点值并不会重写正在运行的工具虚拟机,所以必须先重建发行版,改动才会生效。

把文件接管过来,而且是有意为之

如果路由正确、只有内网域名失败,那责任在 resolver,你可以把生成器停掉。这是有维护成本的刻意改动,不是随手套用的临时办法。

在发行版里创建 /etc/wsl.conf,写入关闭自动生成的那一节,然后自己写 resolver 文件,先写公司服务器,再写一个公网备用:

[network]
generateResolvConf = false
search corp.example
nameserver 10.88.4.53
nameserver 9.9.9.9

顺序很重要。公网 resolver 回答不了内网域名,备用项只为上外网准备。文件一旦归你,就没有任何东西再重新生成它,所以公司服务器一搬家,就变成要你自己察觉的故障。

先查域名,再查软件包

用系统 resolver 验证一个内网域名和一个公网域名,然后重跑刚才失败的操作。包管理器有时会用自己的 resolver,这也是为什么能用的 getent 可能与坏掉的安装并存。

如果你要自动化这次改写,就让它幂等:没有隧道时保留已有的文件,而不是写一份空的服务器列表。

getent hosts intranet.corp.example
resolvectl status | head -20
getent hosts example.com

从发行版内部看,内网查询可用、公网查询也可用,才是值得记录的结果。主机上那个绿色的 VPN 图标,两样都证明不了。

相关文章