同一台服务器上的两个客户端互传文件,传输成功,而指向隧道的每一条转发规则都报告零数据包。规则没有坏,链也没有放错位置。开启对等转发后,数据包在服务器进程内部交付,主机协议栈从不成为路由器,因此内核防火墙没有可统计的对象。理解这一分工,决定了你是保留这条捷径,还是重建经过内核的路径。

跟踪一个数据包穿过服务器

在路由式隧道中,守护进程把每个客户端解密后的数据包交给隧道设备。主机从这里为它选路,转发钩子随之执行,与隧道设备匹配的规则就能统计或丢弃该流量。多数防火墙示例假定的正是这条路径。

Forwarding pathWho decidesWhat the host firewall sees
Kernel routing between clientsHost routing table and forward rulesEvery inner packet, with interface names
Peer forwarding inside the server processThe VPN daemon's own peer tableOnly the outer encrypted transport

捷径把主机从中间拿掉。一个会话解密后的数据包在守护进程内部直接交给另一个会话,内层数据包从不出现在隧道设备上,也不由主机选路。封装后的流量仍然经过物理网卡,因此外部抓包看起来很忙,而内层规则毫无动静。在隧道设备上抓包什么也看不到,这让期望在那里观察传输的人颇感意外。这种不对称是从守护进程外部识别捷径的最快线索。

判定实际走了哪条路径的计数器测试

给本应生效的规则挂上计数器,让这条规则本应拦截的两个客户端之间跑一次传输,然后读取计数器。

sudo nft list ruleset
sudo nft add rule inet filter forward iifname "tun0" oifname "tun0" counter accept
sudo nft list chain inet filter forward

如果传输成功而计数器仍是零,说明内核从未处理过这些数据包,再怎么改规则也不会变。用同样的测试对照服务器自身上的某个服务,因为那条流确实经过主机协议栈,它的计数器会动。正是两项结果的对比给出了答案。

有两个细节能避免误读。写在链标签里的网卡名本身不过滤任何东西,因为匹配发生在规则内部的网卡字段上。而桥接的 2 层设计又是另一回事:那里客户端共享同一个广播域,因此发现对等节点是正常现象,而不是路由故障。

交换机不是访问控制列表

这就是花掉半天的那部分。对等转发只是一个开或关的开关,开着的时候守护进程就是唯一的转发点。主机防火墙无法表达谁可以访问谁,按客户端拆分的文件也不会把开关变成策略矩阵。

如果需要按对等方制定规则,选择只有两个:放弃捷径,或者放弃把主机防火墙当作控制手段。关掉它会恢复内核转发,而这需要服务器开启 IP 转发,并为想要的流写出显式规则。保持开启则意味着策略活在守护进程的配置里,实践中就是全体对等方放行或一个都不放行。

各自拥有地址池和端口的多个服务器进程是可用的中间方案。每个进程只承载一个访问决策,因此两个组可以在没有策略矩阵的情况下拥有不同规则。有意为之的话,主机可以在各地址池之间选路,此时防火墙就能看到这些流,因为它们经过主机。代价是额外的端口、额外的证书,以及每组一份配置。

选择转发点

当防火墙就是策略引擎、需要按流记录,或者计费很重要时,选择内核转发。代价是经过主机协议栈的吞吐量,以及更多需要维护的规则。好处是从计数器到流导出器的每一种现有工具又能描述这些流量了。小服务器上这份代价看不出来,繁忙的中转上就看得出来。

当对等流量要么全放行要么全禁止,且服务器按此规模配置时,选择进程内转发。代价是计数器、队列规则和连接跟踪不再描述内层流;只有日后无人依赖这些信息时,才可以接受。

无论选哪种,都把它写在防火墙规则旁边。一套看起来放行对等流量、实际上却由守护进程决定的规则,会误导下一个排查传输失败的人。只看规则的人会以为内核才是关口。

改动规则之前该核实什么

读服务器真正加载的配置,而不是你记得改过的那份文件,并确认对等选项是否存在。如果改过它,要确保服务重新加载了,而不只是把文件重写到了磁盘上。

然后用客户端到服务器的流再跑一遍测试,证明规则至少能匹配,并检查下发的路由,因为缺少路由从客户端一侧看和防火墙拦截一模一样。只有在弄清楚哪个转发点拥有决定权之后,才去调整规则顺序。

相关文章