3. nftables地址族与钩子
nftables 的规则不是“挂在哪都行”,它必须同时回答三个问题:处理哪一类协议栈的数据包(family)、在协议栈的哪个位置拦截(hook)、同一钩子上多条链谁先谁后(priority)。这三者共同决定了一条规则能否看到某个包、看到的是封装前还是解封装后的形态,以及它与其它链、其它框架(如 iptables-nft、conntrack、bridge 防火墙)的先后关系。理解本节内容,是后续写规则、排障和做性能优化的前提。
一、family:决定“看哪张协议栈的包”
family 是表(table)的属性,创建表时必须指定,之后不能更改。它限定了这张表能处理的数据包类型和可用的钩子集合。
- ip:仅 IPv4。可用 hook 为 prerouting、input、forward、output、postrouting。最常用于纯 IPv4 环境。
- ip6:仅 IPv6,钩子集合与 ip 相同。
- inet:同时处理 IPv4 和 IPv6,钩子集合与 ip 相同。这是现代部署的首选,一份规则同时覆盖双栈,避免 ip/ip6 两套规则重复维护。注意 inet 表里写
ip saddr只匹配 IPv4,写ip6 saddr只匹配 IPv6,需要双栈匹配时通常用meta nfproto或分别写两条规则。 - arp:处理 ARP 报文,可用 hook 只有 input、output。用于 ARP 欺骗防护、ARP 代理等。
- bridge:处理二层桥接流量,可用 hook 为 prerouting、input、forward、output、postrouting。它工作在网桥转发路径上,能看到以太网头,常用于桥接防火墙、VLAN 过滤。
- netdev:最早、最靠近网卡的入口钩子,可用 hook 只有 ingress(以及较新的 egress)。它不依赖路由或桥接决策,适合做 DDoS 早期丢弃、镜像、采样。netdev 的 ingress 在 tc ingress 之前还是之后取决于内核版本与挂载方式,实际部署需以本机内核行为为准。
二、hook:决定“在协议栈哪个位置拦截”
hook 是链(chain)的属性,表示这条链挂在协议栈的哪个处理点。base chain 必须指定 type 和 hook,regular chain 则通过 jump/goto 被调用,不直接挂 hook。
- prerouting:包刚进入协议栈、尚未做路由决策。此时可做 DNAT、早期过滤、标记。注意此处还看不到出接口的最终选择。
- input:路由判定为“发给本机”之后,交给本地上层协议之前。用于保护本机服务。
- forward:路由判定为“需要转发”之后,包从入接口到出接口之间。用于网关、路由器、NAT 转发过滤。
- output:本机产生的包,路由决策之后、发出之前。用于限制本机主动发起的连接。
- postrouting:包即将离开协议栈、写入网卡之前。SNAT、MASQUERADE 通常在这里。
hook 与 family 是组合关系:ip/ip6/inet 支持五个 hook;arp 只支持 input/output;bridge 支持五个 hook;netdev 只支持 ingress/egress。写错组合会在 nft add chain 时报错,例如给 arp 表加 forward 链是不允许的。
三、priority:决定同一钩子上的执行顺序
同一个 hook 上可以挂多条 base chain,priority 决定它们的先后。数值越小越先执行,可以是负数。nftables 为常见用途预定义了标准优先级名称,直接写名称比记数字更清晰:
| 优先级名称 | 数值 | 典型用途 |
|---|---|---|
| raw | -300 | conntrack 之前,做 notrack |
| mangle | -150 | 修改包头、打标记 |
| dstnat | -100 | prerouting 上的 DNAT |
| filter | 0 | 常规过滤 |
| security | 50 | 与 SELinux 等安全模块配合 |
| srcnat | 100 | postrouting 上的 SNAT |
关键点:priority 只在同一 hook 内比较,不同 hook 之间没有优先级关系,它们由协议栈路径天然决定先后。例如 prerouting 一定在 input/forward 之前,postrouting 一定在 output 之后。另外,priority 相同的链,执行顺序不保证稳定,工程上应避免依赖同优先级链的先后。
四、把三者串起来:一个双栈网关的示例
下面这段配置创建一个 inet 表,在 prerouting 做 DNAT、在 forward 做过滤、在 postrouting 做 SNAT,并额外用 netdev 在最早入口丢弃明显异常的包。示例参数为教学假设,实际部署请按自己的网段调整。
nft add table inet gw
nft add chain inet gw pre '{ type nat hook prerouting priority dstnat; policy accept; }'
nft add chain inet gw fwd '{ type filter hook forward priority filter; policy drop; }'
nft add chain inet gw post '{ type nat hook postrouting priority srcnat; policy accept; }'
# 把访问 203.0.113.10:8080 的流量转到内网 10.0.0.20:80
nft add rule inet gw pre tcp dport 8080 dnat to 10.0.0.20:80
# 放行已建立连接与内网出站,其余转发丢弃
nft add rule inet gw fwd ct state established,related accept
nft add rule inet gw fwd ip saddr 10.0.0.0/24 accept
# 内网出公网做源地址转换
nft add rule inet gw post ip saddr 10.0.0.0/24 oifname "eth0" snat to 203.0.113.10
# 最早入口丢弃来自黑洞地址的包
nft add table netdev early
nft add chain netdev early ing '{ type filter hook ingress device eth0 priority -400; policy accept; }'
nft add rule netdev early ing ip saddr 198.51.100.0/24 drop
观察与判断:用 nft list ruleset 确认链的 type、hook、priority 是否符合预期;用 nft list chain inet gw fwd 查看规则与计数器。测试时从内网主机访问 203.0.113.10:8080,若 DNAT 生效,服务端应看到源地址为网关内网地址、目的地址为 10.0.0.20;若 forward 链 policy 为 drop 且未放行 established,回包会被丢弃,表现为连接超时。此时应检查 ct state 规则是否在 forward 链中、以及 conntrack 是否在 raw 优先级之前被 notrack 掉。
五、常见误区与排障要点
- 把 priority 当成跨 hook 的顺序:prerouting 的 filter 链永远在 forward 的 raw 链之前,因为路径不同。排障时先确认包走到了哪个 hook,再谈优先级。
- 在 inet 表里写 ip6 规则却期望匹配 IPv4:inet 表不会自动把 ip 规则套用到 IPv6,双栈要分别写或使用 meta nfproto。
- DNAT 放在 filter 优先级:DNAT 应在 dstnat 优先级完成,否则后续 forward 链看到的仍是原目的地址,过滤规则会失效。
- netdev ingress 与 bridge 混用:netdev 看到的是进入网卡的原始帧,bridge 看到的是桥接处理后的帧,两者位置不同,不要互相替代。
- policy drop 后忘记放行 established:forward 链默认丢弃时,回包会被丢,表现为“请求发出但无响应”,应优先检查 ct state 规则。