Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

WireGuard 导致 DNS 失效问题复盘

部署 WireGuard 后出现 ping IP 通但 ping 域名不通的问题,根因是 wg-quick 脚本自动将全局 DNS 路由域 ~. 绑定到 wg0 网卡,导致 DNS 请求被劫持到不通的链路。本文从问题现象、排查过程、底层原理到预防方案做完整复盘。

实际 WireGuard 配置

出问题的 wg0.conf 如下:

1
2
3
4
5
6
7
8
9
10
11
[Interface]
Address = 10.7.0.4/24
DNS = 223.5.5.5, 223.6.6.6
PrivateKey = xxx

[Peer]
PublicKey = xxx
PresharedKey = xxx
AllowedIPs = 10.7.0.0/24
Endpoint = ENDPOINT:PORT
PersistentKeepalive = 25

注意关键点:AllowedIPs = 10.7.0.0/24,说明该 WG 隧道仅用于访问 10.7.0.0/24 内网段,并非全局代理。但配置了 DNS = 223.5.5.5, 223.6.6.6,这直接导致了后续的 DNS 劫持问题。

问题现象

WireGuard 启用后:

  • ping 223.5.5.5 正常
  • ping www.baidu.com 失败

结论:网络层连通,DNS 解析链路异常。

排查过程

查看系统 DNS 状态

1
resolvectl status

输出关键信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (enp3s0)
Current Scopes: DNS
Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 223.5.5.5
DNS Servers: 223.5.5.5 8.8.8.8

Link 4 (wg0)
Current Scopes: DNS
Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 223.5.5.5
DNS Servers: 223.5.5.5 223.6.6.6
DNS Domain: ~.

关键发现:Link 4 (wg0) 中的 DNS Domain: ~. 表示所有 DNS 查询都通过 wg0 接口走(全局作用域),而 enp3s0 的 DNS 被降级为非全局。

定位根因

WireGuard 安装启用后,wg-quick 脚本检测到配置文件中的 DNS = 223.5.5.5, 223.6.6.6,为防止 DNS 泄漏,自动执行:

1
2
resolvectl dns wg0 223.5.5.5 223.6.6.6
resolvectl domain wg0 '~.' # 强制所有 DNS 走 wg0

wg0 隧道的 AllowedIPs = 10.7.0.0/24 并未包含 223.5.5.5,DNS 请求包无法被正确路由,对端也不会转发公共 DNS 请求,导致解析失败。

临时修复

1
2
3
4
# 移除 wg0 的全局接管
sudo resolvectl domain wg0 ''
# 将全局 DNS 接管权还给物理网卡 enp3s0
sudo resolvectl domain enp3s0 '~.'

底层原理

Linux DNS 解析机制的演进

传统模式下直接修改 /etc/resolv.conf 写入 nameserver 8.8.8.8,但全局配置无法应对多网卡、多 VPN 场景——VPN 连接时覆盖文件,断开时忘记恢复,导致断网。

现代 Linux 发行版引入 systemd-resolved 服务来解决此问题:

  • 系统级 DNS 缓存和 DNS 路由服务
  • 允许为每个网卡(Link)独立配置 DNS 服务器和域名规则
  • 应用程序发起 DNS 查询时发给 127.0.0.53systemd-resolved),由它决定使用哪个网卡的 DNS 去解析

resolvectl 核心命令

resolvectlsystemd-resolved 的命令行管理工具:

命令 用途
resolvectl status 查看各网卡 DNS 配置
resolvectl query <域名> 测试 DNS 解析路径及耗时
resolvectl dns <网卡> <IP> 动态修改网卡 DNS 服务器
resolvectl domain <网卡> <域名> 动态修改网卡域名路由规则
resolvectl revert <网卡> 还原网卡 DNS 配置

stub 模式

输出中 resolv.conf mode: stub 表示 /etc/resolv.conf 内容为 nameserver 127.0.0.53,传统应用把 DNS 请求发给本地 systemd-resolved,再由它按路由规则转发。此时直接修改 /etc/resolv.conf 无效,重启会被覆盖。

路由域 ~. (核心难点)

systemd-resolved 中 Domain 分两种:

  • 搜索域(无 ~ 前缀):如 example.com,仅做后缀补全,ping host 会尝试解析 host.example.com
  • 路由域(带 ~ 前缀):如 ~example.com,告诉系统”只有匹配该域的 DNS 请求才通过此网卡解析”

~. 的特殊含义. 是 DNS 根域,~. 匹配所有域名。当 wg0 配置了 DNS Domain: ~. 时,它宣告自己是全局默认 DNS 出口,所有 DNS 请求都必须交由 wg0 处理。

为什么 WireGuard 会导致 DNS 失败

结合本次实际配置分析:AllowedIPs = 10.7.0.0/24 仅允许内网流量走 WG 隧道,但 DNS = 223.5.5.5, 223.6.6.6wg-quick 将全局 DNS 劫持到 wg0,而 DNS 服务器的 IP 不在 AllowedIPs 范围内,导致 DNS 请求无处可去。

更一般地,解析失败通常因为:

  1. 路由不匹配AllowedIPs 没有包含 DNS 服务器 IP,导致 DNS UDP 包没有进入 WG 隧道
  2. 对端限制:WG 对端服务器屏蔽了对外部公共 DNS 53 端口的访问
  3. 物理网卡被降级wg0 抢走 ~. 后,物理网卡 DNS 变为非全局,wg0 DNS 不通时 systemd-resolved 不会及时回退

DNS 排查 SOP

遇到 “Ping IP 通,Ping 域名不通” 时:

Step 1:确认 DNS 路由状态

1
resolvectl status

查看哪个网卡拥有 ~.

Step 2:测试具体域名解析路径

1
resolvectl query www.baidu.com

Step 3:绕过系统直接测试 DNS 服务器

1
dig @223.5.5.5 www.baidu.com

resolvectl query 失败但 dig 成功,说明是 systemd-resolved 路由问题;若 dig 也失败,说明是网络层/防火墙拦截了 53 端口。

Step 4:抓包确认(终极手段)

1
2
sudo tcpdump -i enp3s0 port 53 -n
sudo tcpdump -i wg0 port 53 -n

最终解决方案

本次场景为仅通过 WG 访问 10.7.0.0/24 内网段,不需要 WG 处理 DNS,因此直接删除配置文件中的 DNS 字段即可一劳永逸:

1
2
3
4
5
6
7
8
9
10
11
[Interface]
Address = 10.7.0.4/24
# DNS = 223.5.5.5, 223.6.6.6 <--- 删除此行
PrivateKey = xxx

[Peer]
PublicKey = xxx
PresharedKey = xxx
AllowedIPs = 10.7.0.0/24
Endpoint = ENDPOINT:PORT
PersistentKeepalive = 25

重启服务后恢复:

1
sudo systemctl restart wg-quick@wg0

没有 DNS 字段,wg-quick 就不会调用 resolvectl 接管全局 DNS,物理网卡继续掌管 DNS 解析,问题彻底解决。

其他预防方案(不同场景参考)

场景一:仅内网互通(本文场景)

只需通过 WG 访问特定内网 IP,日常 DNS 走物理网卡。直接删除配置文件中的 DNS 字段(即上文最终方案),wg-quick 不会触碰 systemd-resolved,物理网卡继续掌管 DNS。

场景二:按域名分流(Split DNS)

访问 *.company.local 用 WG 隧道内 DNS,其他用本地 DNS。使用 PostUp / PostDown 精确控制

1
2
3
4
5
6
7
8
9
10
11
[Interface]
PrivateKey = xxxxx
Address = 10.0.0.2/24

PostUp = resolvectl dns %i 10.0.0.1; resolvectl domain %i '~company.local'
PostDown = resolvectl revert %i

[Peer]
PublicKey = xxxxx
Endpoint = x.x.x.x:51820
AllowedIPs = 10.0.0.0/24

%iwg-quick 内置变量,代表当前网卡名。只有 company.local 后缀的域名才走 wg0 的 DNS。

场景三:全局代理但 DNS 不通

所有流量走 WG,但配置 DNS = 8.8.8.8 后无法解析。确保 DNS 服务器 IP 被 AllowedIPs 包含

1
2
3
4
5
6
7
8
9
[Interface]
PrivateKey = xxxxx
Address = 10.0.0.2/24
DNS = 8.8.8.8

[Peer]
PublicKey = xxxxx
Endpoint = x.x.x.x:51820
AllowedIPs = 0.0.0.0/0, ::/0

若对端 WG 服务器不转发公网 DNS 请求,需显式放行:AllowedIPs = 0.0.0.0/0, ::/0, 8.8.8.8/32

场景四:使用 NetworkManager(推荐)

弃用 wg-quick,改用 NetworkManager 管理 WireGuard,它不会粗暴添加 ~.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 导入配置
sudo nmcli connection import type wireguard file /etc/wireguard/wg0.conf

# 不使用 WG 的 DNS(仅路由)
sudo nmcli connection modify wg0 ipv4.ignore-auto-dns yes

# 或 Split DNS
sudo nmcli connection modify wg0 ipv4.dns "10.0.0.1"
sudo nmcli connection modify wg0 ipv4.dns-search "~company.local"

# 应用并禁用 wg-quick
sudo nmcli connection up wg0
sudo systemctl disable --now wg-quick@wg0

防坑 Checklist

  1. 需要全局代理吗? 否 → 删掉 DNS = ...(本文场景);是 → 保留并检查对端是否允许 DNS 请求通过
  2. 需要解析内网域名吗? 是 → 删掉 DNS = ...,改用 PostUp + resolvectl domain %i '~域名'
  3. 受够了手动修复 DNS? 是 → 弃用 wg-quick,投入 NetworkManager 的怀抱