你设置了 net.ipv4.conf.default.forwarding=1,重启了 VPN,可隧道依然丢弃本该由网关转发的数据包。配置文件里看着没错,接口却仍报告转发是关闭的。什么都没坏:你改的是今后新接口继承的值,而不是正在使用的隧道当下的值。

请在拥有这条隧道的主机上、在隧道真正所在的网络 namespace 里走一遍。容器或网络 namespace 可能持有该接口,而主机显示的是另一组数字。

把三个值放在一起读

先比较网关级的总开关、新接口的模板,以及你真正关心的那个接口。把 tun0 换成真实的入口接口,并一并读取返回方向的接口,因为转发是在数据包到达的那个点上判定的。

sysctl net.ipv4.ip_forward
sysctl net.ipv4.conf.default.forwarding
sysctl net.ipv4.conf.tun0.forwarding

常见的结果是 ip_forward = 0、default.forwarding = 1,以及 tun0.forwarding = 0。前两个对主机角色的说法不一致,第三个才反映接口的实际行为。这种组合与从未被配置成路由器、只拿到模板值的主机相符。三个数字并不互相矛盾,它们在回答三个不同的问题。写模板值同样决定不了主机的角色。

为什么真正会动的是全局开关

net.ipv4.ip_forward 是网关级的控制项。改它会作为同一次操作的一部分,一并更新每个接口的转发设置,所以一次写入就能让已有隧道开始转发。default.forwarding 只对之后初始化的接口播种,因此早已存在的隧道会保留它出生时的值。

内核文档把 ip_forward 标为特殊项,原因与此相关:修改它会把一批其他 IPv4 参数重置为主机或路由器的默认值。这使它成为一件钝器。把它作为确立网关角色的一部分设置一次没问题;把它当成调试小技巧反复切换,则可能悄悄改掉你花时间调好的配置。

按接口设值同样有它的道理。有意管理的网关可以在某个本就不该转发的接口上保持关闭,即便主机在其他地方照常转发。这正是要读活接口的原因,也是只看全局值不够的原因。把这种例外当成有意的决定来读,并写下那个接口为何不同;否则下一位工程师会好心地把它「修好」。

把设置持久化到正确的位置

一次成功的临时 sysctl -w 只能证明内核接受了这个值。它熬不过重启,也熬不过网络管理器在重连时重新套用自己的接口配置。把网关角色写进 sysctl 配置文件,或交给拥有这台主机的网络管理器,然后重启真正应用它的服务。

对隧道来说顺序很重要。如果管理器在开机时创建接口、之后再应用转发,接口可能继承旧的默认值。重建隧道的服务重启同样是一次创建事件,而 unit 看起来健康时这一点很容易被忽略。要检查顺序,而不只是最终的配置文件内容。

在 VPN 重连之后、以及再一次重启之后,重新读那三个值。新建的隧道就是新接口,而新接口取的是创建那一刻生效的默认值。如果这些事件之间数字发生了变化,说明还有你没识别出来的东西在应用配置。

不要用错的工具去修它

当转发看起来坏掉时,人们容易去清空防火墙或加上 masquerading。两者都不解决转发开关。转发状态、转发许可、到目的地的路由,以及回程路径,是四项彼此独立的要求,其中只有第一项是 sysctl。

也要用正确的流量来测。在网关本机上发起的请求根本不会走转发,那是本地生成的流量。真正要问的是:从 VPN 客户端或 LAN 主机到达的数据包会不会被接受并继续路由,所以测试必须从网关后面发起。

IPv6 的情况要单独处理

这里说的全是 IPv4。IPv6 转发、路由通告和 accept_ra 行为各有自己的控制项,把 ipv4 的路径抄进 ipv6 的设置里,只会得到一个看着像样、却毫无用处的文件。双栈网关需要把两套都认真配好。

最后再确认一件事:等数值对上之后,确认隧道的那条路由确实被选中,且回程路径存在。在一条没有路由的接口上打开转发,依然是黑洞,而这种故障从客户端一侧看完全一样。

相关文章