最近在一台 EndeavourOS 机器上排查网络问题,发现部分域名的解析结果明显异常:好几个不相关的域名全部解析到了同一个 IP,而且这个地址和网站真实的托管方毫无关系。这就是典型的 DNS 污染(DNS 缓存投毒)。本文记录问题的定位过程,以及分别在 Linux 和 Windows 上启用加密 DNS 的完整做法。
一、DNS 污染是怎么回事
DNS 查询绝大多数情况下走 UDP 53 端口,明文、无认证。UDP 本身是谁先应答就用谁的,攻击者只要抢在真实响应之前回一个伪造包,客户端就会采信伪造结果,真实响应到达时反而被丢弃。
于是当链路上存在会主动伪造响应的中间设备时(某些运营商网络、公共 Wi-Fi、被劫持的路由器都可能出现),明文查询的结果就不可信了。表现包括:
- 域名解析到与网站真实托管方完全无关的 IP;
- 多个不同域名解析到同一个 IP;
- IPv6 查询全部返回同一个固定地址;
- 访问站点超时,或被跳转到无关页面。
关键结论:这种情况下换任何明文 DNS 服务器(包括 8.8.8.8、1.1.1.1)都没用,因为污染发生在你到 DNS 服务器之间的链路上,跟服务器本身无关。换服务器只决定了"你问谁",而明文查询的应答在半路就被人抢答了。
解决思路只有一个:让查询内容加密,中间设备看不到你查了什么,也就无法伪造针对性的应答。主流方案是 DoH(DNS over HTTPS,走 443)和 DoT(DNS over TLS,走 853)。
二、先确认问题:怎么判断解析被污染
不要凭"网站打不开"下结论,先拿数据。最常用的是对比法:同一路径下问多个上游,再和加密通道的结果比对。
2.1 多上游对比
nslookup example.com 223.5.5.5
nslookup example.com 1.1.1.1
nslookup example.com 8.8.8.8如果不同上游返回的地址段完全对不上(可以拿返回的 IP 反查 whois,看归属是不是网站真实的托管方),基本可以判定链路上有抢答。注意这里 8.8.8.8、1.1.1.1 返回的结果同样可能是伪造的,因为查询本身还是明文 UDP。
2.2 用 DoH 的 JSON 接口拿到"干净对照组"
各大公共 DNS 都提供 HTTP JSON 接口,走 HTTPS 加密,中间设备无法抢答:
curl -s "https://120.53.53.53/dns-query?name=example.com&type=A" \
-H "accept: application/dns-json"
curl -s "https://223.5.5.5/resolve?name=example.com&type=A"返回体里 Answer 数组的 data 字段就是 A 记录。把这个结果和明文查询对比,差异一目了然。
2.3 一个容易被忽略的坑:上游自身的递归也可能被污染
这一点很重要:DoH/DoT 只保证"你到递归服务器"这一段是加密的,但递归服务器自己去查权威 DNS 时如果走的也是被污染的链路,它缓存里的答案照样是错的。我在实测中就遇到两个加密 DNS,一个返回正常地址段,另一个返回了明显伪造的地址。
所以部署前建议先用 2.2 的 JSON 接口逐个测一下候选上游,确认它对目标域名的递归结果是可信的,再把它设为系统 DNS。下面给出一个快速自测循环:
for h in 120.53.53.53 223.5.5.5; do
echo "== $h =="
curl -s "https://$h/dns-query?name=example.com&type=A" \
-H "accept: application/dns-json" | grep -o '"data":"[^"]*"'
done2.4 确认加密端口可达
DoT 走 TCP 853,个别网络会封非常用端口。部署前先握手测一下:
timeout 4 openssl s_client -connect 1.1.1.1:853 </dev/null 2>&1 | grep "Verify return code"能拿到证书验证输出说明 853 可用。DoH 走 443,一般不用担心。
三、方案选型
| 方案 | 端口 | 特点 | 适用 |
|---|---|---|---|
| DoT | TCP 853 | 单一端口,容易被针对性封禁 | Linux(systemd-resolved 原生支持) |
| DoH | TCP 443 | 与 HTTPS 流量混在一起 | Windows 11(原生支持)、浏览器 |
我最终的选择:Linux 上用 systemd-resolved 走 DoT(系统自带,零额外依赖),双上游配置;Windows 11 用原生 DoH。两条链路部署后互相独立,都不影响已有的网络配置。
四、Linux:systemd-resolved 启用 DoT
环境:EndeavourOS(Arch 系),NetworkManager 管理网络,systemd-resolved 已安装但未启用。Ubuntu 20.04+ 默认已启用 systemd-resolved,跳过启用步骤即可,其余相同。
4.1 配置 resolved
编辑 /etc/systemd/resolved.conf:
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 120.53.53.53#dot.pub
FallbackDNS=8.8.8.8#dns.google
DNSOverTLS=yes
Cache=yes
CacheFromLocalhost=no
DNSStubListener=yes几个要点:
IP#主机名语法:#后面的主机名用于 TLS 证书校验(SNI),不是随便写的。cloudflare-dns.com是 1.1.1.1 的 DoT 域名,dot.pub是腾讯的。需要系统装有ca-certificates。DNSOverTLS=yes表示只走 DoT,不回退明文,杜绝漏网。- 多上游不是坏事:主上游 853 被封时还有备份。
4.2 阻止 NetworkManager 注入明文 DNS
这是最容易踩的坑。NetworkManager 会把 DHCP 下发的 DNS(也就是运营商的明文 DNS)作为每链接(per-link)DNS 推给 resolved,而 per-link DNS 的优先级高于 resolved.conf 里的全局 DNS——结果是 DoT 配了但根本没被用上,查询还是从运营商明文走。
新建 /etc/NetworkManager/conf.d/10-dns-none.conf:
[main]
dns=nonedns=none 让 NetworkManager 完全不参与 DNS 下发,resolv.conf 由我们自己管理。
4.3 启用并切换 resolv.conf
sudo systemctl enable --now systemd-resolved
# 把 resolv.conf 切到本地 stub
sudo rm -f /etc/resolv.conf
sudo ln -s ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved
sudo nmcli connection reload
sudo nmcli general reloadnmcli reload 只是重载配置,不会断网,SSH 远程操作也安全。
4.4 一键脚本
把上面的步骤整合成脚本,幂等可重复执行:
#!/usr/bin/env bash
set -euo pipefail
[ "$(id -u)" -eq 0 ] || { echo "请用 sudo 运行"; exit 1; }
TS=$(date +%Y%m%d-%H%M%S)
backup() { [ -f "$1" ] && cp -a "$1" "$1.bak.$TS" || true; }
backup /etc/resolv.conf
backup /etc/systemd/resolved.conf
cat > /etc/systemd/resolved.conf <<'EOF'
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 120.53.53.53#dot.pub
FallbackDNS=8.8.8.8#dns.google
DNSOverTLS=yes
Cache=yes
CacheFromLocalhost=no
DNSStubListener=yes
EOF
mkdir -p /etc/NetworkManager/conf.d
cat > /etc/NetworkManager/conf.d/10-dns-none.conf <<'EOF'
[main]
dns=none
EOF
systemctl enable --now systemd-resolved
rm -f /etc/resolv.conf
ln -s ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
systemctl restart systemd-resolved
nmcli connection reload 2>/dev/null || true
nmcli general reload 2>/dev/null || true4.5 验证
resolvectl status确认输出里 Current DNS Server 是配置的 DoT 上游,且没有 per-link 的 DNS Servers(DHCP 的应该消失了)。
resolvectl query github.com把解析到的 IP 与 2.2 节 DoH JSON 接口的结果比对,一致即正常。还可以确认流量真的走了 853:
sudo ss -tunap | grep ':853'能看到与上游 IP 的 ESTABLISHED 连接。
4.6 回滚
sudo systemctl disable --now systemd-resolved
sudo rm /etc/NetworkManager/conf.d/10-dns-none.conf
sudo cp /etc/resolv.conf.bak.<TS> /etc/resolv.conf
sudo nmcli general reload4.7 没有 systemd 的发行版
Void、Alpine 等无 systemd 的系统可以用 smartdns(支持 DoH/DoT 上游、多线路测速)或 dnscrypt-proxy(支持 DNSCrypt + DoH),思路相同:本地起一个监听 53 的服务,上游全部走加密通道,再把 /etc/resolv.conf 指向 127.0.0.1。
五、Windows 11:原生 DoH
Windows 11 和 Windows Server 2022 起系统原生支持 DoH,不需要装任何软件。
5.1 命令行方式(推荐)
管理员 PowerShell,先给目标 DNS 服务器注册 DoH 模板:
netsh dns add encryption server=1.1.1.1 dohtemplate=https://cloudflare-dns.com/dns-query autoupgrade=yes udpfallback=no
netsh dns add encryption server=1.0.0.1 dohtemplate=https://cloudflare-dns.com/dns-query autoupgrade=yes udpfallback=no
netsh dns show encryptionautoupgrade=yes:该服务器支持 DoH 时自动升级为加密查询;udpfallback=no:加密不可用时不回退明文,避免静默降级成可污染的查询。
然后把网卡的 DNS 改成这个服务器:
# 查接口索引
Get-NetAdapter
# 按名字设置,例如以太网
Set-DnsClientServerAddress -InterfaceAlias "以太网" -ServerAddresses 1.1.1.1,1.0.0.1清一下缓存让旧记录失效:
Clear-DnsClientCache5.2 GUI 方式
设置 → 网络和 Internet → 当前连接(WLAN/以太网)→ 硬件属性 → DNS 服务器分配 → 编辑:
- 选"手动",开启 IPv4;
- 首选 DNS 填
1.1.1.1,其他 DNS 填1.0.0.1; - DNS 加密首选选"仅加密(DNS over HTTPS)";
- 保存后执行
ipconfig /flushdns。
如果下拉里没有 DoH 选项,说明该 IP 还没注册模板,先执行 5.1 的 netsh dns add encryption 即可。
5.3 验证
# 查看 DoH 服务器配置与状态
Get-DnsClientDohServerAddress
# 系统解析器(走 DoH)查询
Resolve-DnsName github.com
# 确认没有进程还在对外发 53 明文查询
netstat -ano | findstr ":53 "Get-DnsClientDohServerAddress 输出中 AutoUpgrade 为 True、状态列正常即可。任务管理器的资源监视器里也能看到与 1.1.1.1:443 的持续连接。
注意 Resolve-DnsName -Server 1.1.1.1 这种指定服务器的写法会绕过系统解析器直接发明文查询,不适合用来验证 DoH,验证时省略 -Server 参数。
5.4 Windows 10 的替代
Windows 10 没有 DoH 注册表,两个选择:
- 浏览器内置 DoH:Chrome/Edge 在"设置 → 隐私和安全 → 安全"里开启"使用安全 DNS"并选择自定义提供商;Firefox 在"设置 → 隐私与安全 → DNS over HTTPS"里开启。只保护浏览器内的解析,但对大多数场景已经够用;
- 第三方本地 DoH 代理(如 smartdns、dnscrypt-proxy 的 Windows 版),监听 127.0.0.1:53,网卡 DNS 指向它。
六、收尾与注意事项
- 缓存要清理:切换后旧污染记录可能还在各级缓存里,Linux 用
resolvectl flush-caches,Windows 用ipconfig /flushdns,浏览器也要重启。 - 部分程序绕过系统 DNS:某些客户端硬编码 8.8.8.8 直发明文查询,系统层加密管不到它们,只能逐个在应用内设置。
- 整网方案:家里多台设备不想逐台配置的话,在路由器(OpenWrt 等支持 DoH/DoT 上游的固件)上统一处理,下游设备无感。
- 先测上游再上线:再次强调 2.3 节的结论,加密只保护你到递归的那一段,上游递归自身返回的结果要预先用 JSON 接口验证过。
至此两台设备的解析都恢复了正常:解析结果与 DoH JSON 接口完全一致,明文 53 上再也没了抢答的影子。整个过程不动防火墙、不改默认路由,任何一步出问题都可以按文中的回滚步骤退回原状。