Pi 上的热点客户端无法通过 TUN VPN 访问家庭 LAN
拿到地址却打不开家庭共享的热点客户端,缺的是回程路由而不是网桥。追踪 TUN 路径、前缀归属和 NAT。
本地 Wi-Fi 和 Ethernet 客户端连上 Pi 的热点,从它的 DHCP 范围取得地址,并毫无怨言地加载互联网页面。随后,同样的客户端在打开家庭 LAN 上的文件共享或 Web 界面时超时,而 Pi 自己却能通过隧道抵达那个 LAN。无线层没有任何损坏。缺的是一份可路由的契约:热点前缀必须被带进 TUN 接口,而家庭一侧必须有东西知道如何把答复送回来。两半必须同时存在,单个数据包才能完成往返。
三个连接,三项不同的任务
Linux Ethernet 网桥按目的 MAC 地址转发帧。 OpenVPN 在 TUN 模式下承载 IP 数据包,根本没有 MAC 层,因此添加本地网桥永远不会把隧道登记为端口。 内核 TUN/TAP 文档 解释了这种区分:TAP 设备看起来像 Ethernet 接口,TUN 设备则不是。一个把两个本地端口桥接起来再路由的设计就是路由器,无论接口名怎么说。
这一区分决定了归属。客户端的子网、它们的默认网关以及该范围的 DHCP 权限都属于 Pi 的本地规划。家庭 DHCP 广播不会跨越被路由的隧道,任何网桥设置都改变不了这一点;把客户端指向家庭 DHCP 服务器是另一套有意的中继设计,有自己的失败模式。
也要把调制解调器挡在客户端网桥之外。PPP 连接不是换了标签的 Ethernet 设备,pppd 有文档记载的默认路由逻辑在 IPCP 协商之后运行,并在该链路断开时撤回自己的路由。复制 DHCP 模板并把每个 usb0 改名为 ppp0,并不能证明任何关于网关、度量或生命周期的事情。
先选好前缀,再改任何配置
重叠前缀比防火墙规则更耗夜晚,因为即使隧道健康,重叠也会把路由查找发到错误的接口上。先列出 Pi 上已有的东西,再把结果读成四个独立的池。
ip -4 addr show scope global
ip -4 route show table main
ip -4 route get 192.168.77.10
第一个池是运营商给调制解调器的 underlay 地址;它必须留在任何面向客户端的规划之外。第二个是服务器选择的隧道子网,常常是它自己的私有 /24 网段。第三个是热点网桥,第四个是你要抵达的家庭前缀。在这次走查中,热点拿到 172.28.90.0/24,网桥位于 172.28.90.1,家庭服务 LAN 保持 192.168.77.0/24,从而避开消费级路由器自带的 192.168.1.0/24 和 10.0.0.0/24 默认值以及 100.64.0.0/10 运营商范围。
在热点的 DHCP 范围里只宣告网桥地址作为网关,别的一概不给。绝不要把家庭地址作为网关发给热点客户端;它们没有通往该网段的链路层路径。
每个方向都在拥有它的一侧路由
Pi 需要一条经隧道通往家庭前缀的路由。服务器模式下的服务器通常会推送这条路由,所以先确认转发已开启,且内核能从 tun0 解析出家庭地址。
sysctl net.ipv4.ip_forward
ip route add 192.168.77.0/24 dev tun0
ip -4 route get 192.168.77.10
如果 ip route get 报出的仍是 underlay 接口,就停在那里;路径上再往后没有任何东西能修好一次错误的查找。
家庭设备没有通往 172.28.90.0/24 的路由,所以它们的答复走默认网关,死在路由器上。有两种受支持的修法。要么在家庭路由器上添加一条静态路由,把热点前缀指向 VPN 服务器的 LAN 地址;要么在服务器本身就是网关时,把客户端前缀登记进服务器自己的路由表。在 OpenVPN 的多客户端模式下,客户端专属目录承载这一关联:一个以客户端证书 common name 命名的文件,内含 iroute 172.28.90.0 255.255.255.0 以及把它装进服务器内核的对应 route 行。服务器上被猜出来的内核路由,并不等于告诉服务器哪个客户端拥有该前缀。
不要为了让这件事生效而把家庭网络的 DHCP 服务搬到 VPN 服务器上。替换整个房子的默认网关,比增加一条私有回程路由大得多,而且会把 DNS、地址保留以及每台忽略新租约的设备一起拖进故障。
打开转发,但别猜防火墙
转发开启并不意味着数据包被允许通过。从网桥到隧道的流量仍然会撞上过滤规则,默认 drop 的转发策略会丢弃热点流量,而 Pi 自己的流量照常工作,这恰好制造了上面那个分裂症状。
iptables -S FORWARD
iptables -t nat -S POSTROUTING
nft list ruleset
寻找从 br0 到 tun0 的接受规则以及对应的回复方向,还有可能改写源地址的 postrouting 规则。如果链策略是 drop 且没有规则匹配,就添加窄范围的接受并重新测试,而不是把策略翻成 accept。
地址转换是策略选择,不是必需。朝家庭 LAN 做源 NAT 会让每个热点客户端看起来都是 Pi 的隧道地址,这在家庭日志里丢掉了按客户端归属,并破坏了期望原始来源的服务。不做 NAT 的路由让客户端地址保持可见,但依赖上面那条回程路由。选一种,并对 DNS 和 IPv6 一视同仁,因为在坏的 IPv6 路径旁边,能用的 IPv4 路径在浏览器里仍被读成半坏。
先验证往返,再故意弄坏它
从热点客户端测试,绝不要从 Pi 测试,因为 Pi 自己的流量同时绕过去程和回程路径。当某个客户端试图抵达家庭服务时,同时观察两个接口。
tcpdump -ni br0 host 192.168.77.10
tcpdump -ni tun0 host 192.168.77.10
ping -c 3 192.168.77.10
把两份抓包放在一起读。在 br0 上出现、却从不在 tun0 上出现的请求,意味着 Pi 没有转发或路由指向了别处。在 tun0 上没有答复的请求,意味着家庭一侧没有回程路由,或者服务器过滤了数据包。在 tun0 上出现、却再也没在 br0 上重现的答复,意味着反向过滤规则缺失:隧道看起来完美,客户端却仍然什么都看不到。
然后故意弄坏路径。把调制解调器连接断开再拉起,重跑路由和抓包检查;一条能扛过隧道、却在重连时消失的路由属于连接配置,而不是一次性命令。确认隧道的传输仍然经 underlay 出去而不是经由它自己,这是接受被推送的默认路由却没有通往对端的 host 路由时的经典环路。
只有当热点客户端能解析家庭名称、打开 TCP 服务,并在一次调制解调器重连期间保持会话存活时,才接受这个设计。如果某个应用确实需要家庭网段上的 Ethernet 级发现,就选择受支持的 Layer 2 设计,而不是把本地网桥描述成已桥接再等广播出现。