手机连上 Pi 的热点,拿到了地址,页面却仍然打不开。Pi 上跑着热点帮手、Pi-hole 和 VPN 客户端,每个都认为自己拥有路径的一部分。这类搭建失败,多半不是因为某个组件坏了,而是因为两个组件在做同一件事。调整其中任何一个之前,先为每个功能指定一个属主。

为每个功能指定一个属主

这里有四个功能,每个都需要且只需要一个属主。下表列出功能、可行的属主,以及两个服务争抢时的故障表现。

FunctionWorkable ownerDuplicate-owner symptom
Client addressesOne DHCP server, such as the hotspot helperCompeting offers and changing leases
DNS receptionPi-hole on port 53Bind failure or queries answered by the wrong process
Forwarding and NATOne firewall managerDouble translation and broken return paths
VPN routingOne client configurationRoute flapping and traffic escaping outside the tunnel

重叠通常是一步步装出来的。热点帮手自带一份 dnsmasq,Pi-hole 要占用 53 端口,VPN 客户端又添加路由。每一步单看都合理,合起来就得到一台机器:后启动的服务悄悄改变了先启动服务的行为。

在启用其他东西之前,把属主写下来。搭建笔记里放一张小表,就能避免后来的安装接管已经有人负责的功能。

选一个网段,并让它远离隧道

选定热点网段、Pi 在该网段的地址、DHCP 地址池,以及客户端获得的解析器。让这个网段与上游 LAN 和 VPN 隧道网段都不重叠,因为重叠的子网会把路由问题变成地址转换工程。与隧道重叠的网段会让 Pi 变成两个相同前缀之间的意外路由器。把网段和地址池写进热点配置,而不是留给默认值。

如果热点由 NetworkManager 的共享模式管理,在添加 DHCP 服务器之前先检查实际生效的连接。共享模式会启动自己的帮手并开启转发,所以它已经是该接口地址规划的属主。仅仅因为可用就再加一个服务器,得到的是互相竞争的分配,而不是冗余。

IPv6 要有意识地决定。许多配置里,路由通告和 DHCPv6 来自与 DHCPv4 不同的帮手;在 VPN 策略只覆盖 IPv4 时继续开着 IPv6,会给客户端一条绕过隧道的路。要么规划 IPv6 一侧,要么在热点接口上关掉它,并写下理由。

53 端口上到底是谁在应答

改配置之前,先列出所有监听 53 端口的进程。在同时跑本地解析器存根、Pi-hole 和共享帮手的 Pi 上,监听列表是判断谁在应答查询的唯一可靠地图:

sudo ss -lunpt '( sport = :53 )'
ip -4 addr show
ip route

Pi-hole 的接口指南说明,只限定一个接口的接收模式仍然可能使用通配套接字,并丢弃从别处到达的请求。这与只绑定选定接口不是一回事,而当另一个 DNS 服务必须共用这台机器时,后者才是本来要用的安排。

两个进程通常无法绑定同一个地址和端口,尽管 SO_REUSEPORT 可以让共享绑定成功;即便如此,通配监听者也可能截走本应交给更窄服务的请求。为想要的角色选择文档写明的模式,并按预期顺序重启服务。

把 Pi-hole 的管理页面放在热点网络之外。客户端需要的是解析器,而不是能改动解析器配置的网页界面或 API。

两段 DNS,各自一套策略

客户端查询从手机走到 Pi,另一次查询从 Pi 走到它的上游解析器。匹配转发过来的热点流量的路由规则,不会自动作用于第二段,因为这部分流量是 Pi 在本地生成的。如果 VPN 应当承载 DNS,本地路径就需要自己的策略。

当心环路。把 Pi-hole 的上游指向 Pi 自己提供的地址,无论直接还是通过端口转发,都会产生一直转到超时的查询。给它一个真实的上游,或为它答不了的域名设置条件转发。

决定隧道断开时会发生什么。回落到本地解析器能让浏览继续,但可能把域名泄漏到 VPN 之外;阻断则保持策略完整,把故障变成看得见的错误。两种选择都站得住,但必须主动选择,而不是继承默认值。

验证路径和断线时的行为

分层测试:客户端拿到地址,客户端通过 Pi 解析域名,Pi 通过预期的上游解析,应用完成一次请求。ping 通说明不了 DNS,解析成功也说明不了转发。

dig @192.168.70.1 example.com
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/
ip -4 route get 1.1.1.1

然后停掉隧道再重复一遍。预期行为应在几秒内出现,而不是手动重启 Pi-hole 或热点服务之后。故障期间检查客户端的解析器设置,因为备用公共解析器在策略期待失败时仍会照常应答。

转发和 NAT 只留一个属主,并从客户端验证,而不是从 Pi 本机验证。成功离开 Pi 的请求仍可能在回程被丢弃,而客户端是唯一能看到整条路径的地方。

相关文章