基于nftables的透明网桥实现NAT端口转发的可行方案咨询
我太懂你现在的纠结了:用两块10G网卡(eth1、eth2)搭了透明网桥br0,eth0单独用来管理Ubuntu 22.04主机,已经能用nftables搞定包过滤,但想在这个网桥环境里加端口转发,试了规则要么没效果,要么数据包直接“失踪”对吧?
首先得明确核心问题:透明网桥是二层转发设备,而NAT是纯三层的IP地址/端口修改操作,nftables目前确实没有直接在网桥二层链里做三层NAT的原生支持。你之前写的inet表nat规则能触发,但数据包没按预期从eth2出去,是因为网桥的二层转发逻辑和主机的三层路由逻辑是完全分开的——被DNAT修改后的数据包进入了主机的三层协议栈,而没有回到网桥的转发路径里。
下面给你两个实操性强的workaround,优先推荐第一个,完全用nftables就能搞定:
方案一:让主机作为三层转发节点配合网桥实现NAT
这个思路是把需要做端口转发的数据包从网桥引到主机的三层栈,用nftables完成NAT后,再让主机把数据包转发到目标设备,最后原路返回。具体步骤如下:
先开启主机的IP转发功能
这是三层转发的前提,临时生效可以执行:echo 1 > /proc/sys/net/ipv4/ip_forward要永久生效的话,编辑
/etc/sysctl.conf,把net.ipv4.ip_forward的值改成1,然后执行sysctl -p加载配置。配置网桥表规则,标记并引导需要NAT的数据包到主机三层栈
创建bridge表的prerouting链,把目标是你要转发的IP+端口的数据包打上标记,然后强制让主机的三层栈处理这些包:table bridge nat_bridge { chain prerouting { type filter hook prerouting priority -300; policy accept; # 匹配需要转发的流量,给数据包打标记0x1 iif eth1 ip daddr 10.100.10.9 tcp dport 50080 counter meta mark set 0x1 # 让打了标记的数据包进入主机的三层协议栈处理 meta mark 0x1 pkttype set host counter } }配置inet表的NAT规则,完成DNAT和SNAT
这里不仅要做DNAT修改目标地址,还要加SNAT确保返回包能正确回到原客户端:table inet nat { chain prerouting { type nat hook prerouting priority -199; policy accept; # 针对打了标记的数据包做DNAT meta mark 0x1 ip daddr 10.100.10.9 tcp dport 50080 counter dnat to 10.100.10.10:80 } chain postrouting { type nat hook postrouting priority 100; policy accept; # 对目标主机的返回包做SNAT,伪装成原目标IP ip saddr 10.100.10.10 counter snat to 10.100.10.9 } }配置过滤规则允许标记的流量通过
确保主机的input和forward链允许这些打了标记的数据包通行:table inet filter { chain input { type filter hook input priority 0; policy accept; meta mark 0x1 accept } chain forward { type filter hook forward priority 0; policy accept; meta mark 0x1 accept } }
这套规则的逻辑是:网桥收到需要转发的数据包后,打上标记并交给主机三层栈,inet表完成DNAT后主机把包转发到目标设备,返回包再通过SNAT伪装成原目标IP,最后回到网桥转发回原端口(eth1)。
方案二:XDP/BPF替代方案(非首选)
你提到不想编译C代码,其实现在有很多BPF工具不需要编译内核模块,比如用BCC或者bpftrace可以用脚本语言编写BPF程序,比如用bpftrace写简单的NAT规则。不过这个方案的学习成本比nftables高不少,如果你已经在熟悉nftables,优先用方案一更稳妥。
最后补充下你之前尝试的问题
你之前写的bridge表规则里修改了以太网目的MAC,但没有把数据包引到三层栈,导致DNAT后的数据包留在主机里没被转发出去,按照方案一的步骤调整后,应该就能解决这个问题。
备注:内容来源于stack exchange,提问作者kallquk

