nftables 为什么看不到 OpenVPN 客户端之间的流量
对等流量在 VPN 进程内部交付,所以转发计数器从不动。要统计它,就得在内核路径和进程路径之间做选择。
同一台服务器上的两个客户端互传文件,传输成功,而指向隧道的每一条转发规则都报告零数据包。规则没有坏,链也没有放错位置。开启对等转发后,数据包在服务器进程内部交付,主机协议栈从不成为路由器,因此内核防火墙没有可统计的对象。理解这一分工,决定了你是保留这条捷径,还是重建经过内核的路径。
跟踪一个数据包穿过服务器
在路由式隧道中,守护进程把每个客户端解密后的数据包交给隧道设备。主机从这里为它选路,转发钩子随之执行,与隧道设备匹配的规则就能统计或丢弃该流量。多数防火墙示例假定的正是这条路径。
| Forwarding path | Who decides | What the host firewall sees |
|---|---|---|
| Kernel routing between clients | Host routing table and forward rules | Every inner packet, with interface names |
| Peer forwarding inside the server process | The VPN daemon's own peer table | Only 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 转发,并为想要的流写出显式规则。保持开启则意味着策略活在守护进程的配置里,实践中就是全体对等方放行或一个都不放行。
各自拥有地址池和端口的多个服务器进程是可用的中间方案。每个进程只承载一个访问决策,因此两个组可以在没有策略矩阵的情况下拥有不同规则。有意为之的话,主机可以在各地址池之间选路,此时防火墙就能看到这些流,因为它们经过主机。代价是额外的端口、额外的证书,以及每组一份配置。
选择转发点
当防火墙就是策略引擎、需要按流记录,或者计费很重要时,选择内核转发。代价是经过主机协议栈的吞吐量,以及更多需要维护的规则。好处是从计数器到流导出器的每一种现有工具又能描述这些流量了。小服务器上这份代价看不出来,繁忙的中转上就看得出来。
当对等流量要么全放行要么全禁止,且服务器按此规模配置时,选择进程内转发。代价是计数器、队列规则和连接跟踪不再描述内层流;只有日后无人依赖这些信息时,才可以接受。
无论选哪种,都把它写在防火墙规则旁边。一套看起来放行对等流量、实际上却由守护进程决定的规则,会误导下一个排查传输失败的人。只看规则的人会以为内核才是关口。
改动规则之前该核实什么
读服务器真正加载的配置,而不是你记得改过的那份文件,并确认对等选项是否存在。如果改过它,要确保服务重新加载了,而不只是把文件重写到了磁盘上。
然后用客户端到服务器的流再跑一遍测试,证明规则至少能匹配,并检查下发的路由,因为缺少路由从客户端一侧看和防火墙拦截一模一样。只有在弄清楚哪个转发点拥有决定权之后,才去调整规则顺序。