把 Pi 当作 VPN 客户端:一个管理器还是自写脚本?
在 Raspberry Pi 上比较 NetworkManager 配置与手写客户端脚本,涵盖全隧道、DNS、IPv6 以及隧道断开时的行为。
只需要保护自身出站流量的 Raspberry Pi 有两种现实方案:让网络管理器掌管隧道,或者保留一个自己掌管隧道的脚本。两者都能承载流量。它们失败的位置不同,而凌晨两点你能远程诊断的那个故障,才是值得选择的。请从归属权、涉及 IPv6 和 DNS 时“全隧道”到底意味着什么,以及隧道断开那一刻会发生什么这三方面来比较它们。
一条隧道的两个主人
方案 A 是系统网络管理器里的一个配置:导入的 OpenVPN 配置,或原生 WireGuard 连接,由启动时拉起以太网和 Wi-Fi 的同一个服务激活。方案 B 是你自己的一个单元,它直接运行客户端、修改解析器配置,并安装自己的路由和防火墙规则。两者都成立;区别在于谁有权改动路由表。
故障之所以不同,是因为主人不同。在 A 中,无法激活的配置会让接口保持在线、隧道处于断开状态,而管理器的日志会说明原因。在 B 中,只成功一半的脚本会留下路由,它们会存活于创建它们的进程结束之后。同时运行两者是最糟的结果:两个进程都在写默认路由,启动时两处解析器改动互相竞争,还有两套密钥要轮换。
每条隧道只选一个主人,并在测试任何东西之前停用另一个。两个主人也意味着当隧道在线却什么都解析不了时,有两个地方要看。这一条规则就能避免 Pi VPN 讨论帖里描述的大部分混乱状态。
导入会创建什么,不会创建什么
在 Raspberry Pi OS Bookworm 及更高版本中,默认的网络配置工具是 NetworkManager,因此配置文件是客户端隧道的自然归宿。导入需要装好对应的插件;没有插件时导入会失败并提示不受支持的连接类型,这正是停下来安装插件的诚实信号。
nmcli connection import type openvpn file ./provider.ovpn
nmcli connection show
nmcli --ask connection up id provider-name
导入只是保存了一份配置。它既不证明对端可达,也不保证文件里的每条指令都经受住了转换。请回读生成的连接,并检查承载身份与信任的部分:证书、远端主机与端口,以及密码是被询问还是被保存。
用 --ask 激活,可以在共享机器上把口令留在 shell 历史之外。对于无人值守的 Pi,你最终会希望配置在启动时自动上线,这与手动上线一次是两件不同的决定。
全隧道,在两种方案下比较
全隧道是关于路由的声明,而不是关于意图的声明,两种方案的表达方式不同。配置方案要求管理器安装默认路由及其解析器设置;脚本方案则照脚本说的做,包括什么都不做。两种方案都不会覆盖 IPv6,除非明确要求,而未被触及的 IPv6 默认路由会在 IPv4 看起来完美无缺时,欣然把流量带出隧道。
ip -4 route get 198.51.100.23
ip -6 route show default
resolvectl query example.org
用文档地址做路由查询,并换成一个你获准探测的主机。第一条命令显示 Pi 去往普通目的地会走哪个接口;第二条显示是否还存在 IPv6 默认路由;第三条证明是哪个解析器作答。在相信任何关于全隧道的说法之前,请把三条都读一遍。
如果服务商的说明承诺全隧道,但 IPv6 默认路由仍然存在,那这台 Pi 在泄漏。请在上行链路上关闭 IPv6,或让配置去声明它,并有意地做决定,而不是事后才发现。
故障开放与故障关闭
隧道断开时,失去默认路由的路由表只是退回物理上行链路,流量便以明文继续。这就是故障开放,除非有东西阻止,否则它是两种方案的默认行为。大多数人是在几周后读防火墙日志时才发现这一点。
故障关闭会在隧道断开时夺走你的远程访问,因此需要为管理路径留一个刻意的例外。一种做法是只允许 VPN 对端地址走到隧道之外:
sudo nft add table inet vpnkill
sudo nft 'add chain inet vpnkill out { type filter hook output priority 0; policy accept; }'
sudo nft add rule inet vpnkill out oifname != "tun0" ip daddr != 203.0.113.24 reject
请诚实地比较运维成本。配置文件通常无法表达这条规则,所以故障关闭会把你推向脚本方案,或推向一个独立于隧道安装规则的小单元。无论如何,都要把拒绝记录一周:静默的拒绝看起来与坏掉的服务一模一样,而当用户报告某个站点打不开时,你会需要那个计数器。
为无头 Pi 做选择
如果镜像已经在运行 NetworkManager,隧道不需要任何花哨的东西,而你又希望它自动跟随上行链路,那就选配置方案。如果你需要策略路由、按目的地选择的多条隧道,或者必须在隧道上线前就存在的故障关闭规则,那就选脚本方案,并拥有它的每一部分,包括启动顺序和清理。把两种方案混在一起,比任何单独一种都贵。
在两者之间迁移主要是顺序问题:先停用旧主人,确认没有残留的路由或解析器条目,然后启用新的,并重启两次。一次重启可能掩盖第二次才会暴露的竞态。最后要测的是 Pi 而不是隧道:解析一个名字,用 IPv4 取点东西,如果保留了 IPv6 就用它再取一次,然后断开隧道,确认你所选的行为真的发生了。ip link 里的接口是测试的开始,而不是结束。