VPN 连上后 Pi-hole 不拦截广告:五项检查
广告拦截需要的是查询,而不是路由。按顺序检查地址分配、53 端口、IPv6、推送的 DNS 和隧道前缀,并在第一个失败处停下。
某个客户端上仍在显示广告,Pi-hole 报告自身健康,隧道也已建立,于是直觉是把该客户端的流量绕经 Pi。这是错误的转向。过滤需要的是问题本身,而不是数据包;一个把查询送往运营商解析器的客户端,无论流量从哪里走都不会被过滤。按顺序做这份清单,并在第一个失败的步骤停下,因为在前一步坏掉时,后一步什么也告诉不了你。
1. 读客户端实际在用的解析器
问客户端,而不是问路由器。在 Linux 上,resolvectl status 会打印每个链路的 DNS 服务器以及哪个链路胜出;单独的 /etc/resolv.conf 通常是指向 127.0.0.53 的存根。在 Windows 上,ipconfig /all 按适配器列出 DHCP 下发的服务器;在 macOS 上,scutil --dns 按作用域显示同样的内容。如果答案不是 Pi-hole 的地址,那就是分配问题,第 2 步正是它所在的地方。
即使系统解析器设置正确,还有两件事能击败它。带自有安全 DNS 的浏览器(Chrome 的“Use secure DNS”、Firefox 的 network.trr.mode)通过 HTTPS 把查询发往公共解析器,从不打开 53 端口。而客户端本机上的 VPN 客户端可能在隧道接口上安装一个带 ~. 路由域的 DNS 服务器,使该链路对每个名字都具有权威性。
测试应用实际走的路径,而不是调试工具走的路径。resolvectl query example.com 显示哪个链路作了答,getent hosts example.com 走的 NSS 路径与应用一致;在 macOS 上,dscacheutil -q host -a name example.com 读取 scutil --dns 列出的作用域解析器。裸 dig 做不到这一点:它读 /etc/resolv.conf,跳过按链路选择,因此它可能从 Pi-hole 得到答案,而每个应用都在用被推送的解析器,这正是这份清单要抓的故障。测试某个特定服务器时仍用 dig @192.168.1.53。
2. 确认是谁在分发答案
分配来自任何应答 DHCP 的东西。在 dnsmasq 上是一行,dhcp-option=6,192.168.1.53;在路由器的网页界面里,是 LAN 的 DHCP 段中的 DNS 字段。客户端必须先续租才能看到它。开了好几天的设备仍握着旧租约,而手动填写解析器的设备会一直握着它。在测试客户端上续租,而不是假设它已经传播。
IPv6 是被漏掉的那一半。路由器通告可以携带自己的 DNS 服务器(RDNSS),在 OpenWrt 上就是 odhcpd 的 ra_dns 选项;偏好 IPv6 的客户端会使用那台服务器,而你刚设置的 DHCPv4 选项就闲置了。如果你还没准备好提供 IPv6 DNS,只清除 DNS 通告:odhcpd 里的 ra_dns,或任何应答路由器通告的组件里的 RDNSS 选项。关掉整条通告还会把 IPv6 寻址从 LAN 上撤掉,这比你打算做的改动大得多;而只清掉 DNS 选项能保留寻址,并让缺失的 RDNSS 在抓包里现形。
3. 证明查询能到达 Pi-hole
从 Pi 上发查询不能证明任何关于网络路径的事。要从客户端发起,并读响应码:
dig @192.168.1.53 example.com
dig @192.168.1.53 doubleclick.net
超时指向过滤或路由;REFUSED 指向访问规则;SERVFAIL 指向 Pi-hole 后面的上游解析器;一个正确却从未出现在查询日志里的答案,意味着客户端问的是别的东西。检查 UDP 和 TCP 53,因为大答案和某些解析器会回落到 TCP;Pi-hole 跑在容器里时两者都要发布。只覆盖 UDP 的映射会产生间歇性故障,看起来像丢包。
随后 Pi-hole 的监听模式决定它到底答不答。在仅本地模式下,dnsmasq 会忽略不直接连接在子网上的来源的查询,而位于另一台路由器之后或经隧道到来的客户端正是这个样子:客户端超时,查询从未进日志。仍然以 REFUSED 出现的查询,则是被访问列表或防火墙规则拒绝的。放行所有来源是常见修法,再配一条把 53 端口限制在自己子网内的防火墙规则。
4. 检查隧道是替换还是吞掉了答案
两种症状都藏在这一种情形里。当路由器自己就是 VPN 客户端时,推送的 DNS 选项会在隧道建立的瞬间覆盖 LAN 设置:OpenVPN 服务器推送 dhcp-option DNS,WireGuard 配置自带 DNS 条目。要在隧道建立时读路由器的实际解析器,而不是在此之前,也不要相信 LAN 页面上的说法。
另一个故障是可达性,而不是替换。如果隧道的允许前缀包含你的 LAN 子网,路由器就有一条把发往 192.168.1.53 的查询送进隧道的路由,而对面没有回程路由:隧道虽然健康,查询却超时。反过来,当 Pi-hole 位于隧道不承载的子网时,情况亦然。在再次碰 DNS 之前,把地址与隧道的允许前缀、路由器的路由表对照一遍。
选一个修法,不要两个都上。要么让隧道远离 DNS——OpenVPN 上用 pull-filter ignore “dhcp-option DNS”,WireGuard 的 AllowedIPs 里不放 LAN 前缀——,要么接受推送的解析器,并放弃那台路由器后面客户端的本地过滤。无论选哪个,都写在配置旁边,否则下一个改它的人会不小心把决定翻回去。
5. 重连之后再测一遍
从客户端查询,确认该客户端出现在 Pi-hole 的日志里,然后解析一个应该被拦截的域名,检查拿到的是拦截答案而不是真实答案。重连隧道并再跑同样两条查询,因为一个只能撑到下次重连的解析器设置,正是在错误层级上做修改的常见结果。
留一条不依赖隧道的备用解析路径,让 Pi-hole 故障时退化成不过滤的浏览,而不是完全不能上网。测试两次通过之前,保留原来的 DHCP 和解析器取值;在晚饭时把全家的网弄断的改动,比一分钟内能回滚的改动难查得多。