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 之前还是之后取决于内核版本与挂载方式,实际部署需以本机内核行为为准。
选择 family 的实用原则:纯 IPv4 且规则简单可用 ip;需要双栈统一管理用 inet;要过滤 ARP 用 arp;要过滤桥接流量用 bridge;要在最早时机丢包用 netdev。不要为了“看起来统一”把二层过滤硬塞进 inet 表,那样看不到以太网头。

二、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-300conntrack 之前,做 notrack
mangle-150修改包头、打标记
dstnat-100prerouting 上的 DNAT
filter0常规过滤
security50与 SELinux 等安全模块配合
srcnat100postrouting 上的 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 掉。

nftables family · hook · priority 关系图 family(表属性) ip(仅 IPv4) ip6(仅 IPv6) inet(v4+v6) arp(ARP 报文) bridge(二层桥) netdev(最早入口) 决定“看哪张协议栈” 并限定可用 hook 集合 hook(链属性) prerouting:路由前,DNAT/早期过滤 input:发给本机,保护本地服务 forward:转发路径,网关过滤 output:本机发出,限制主动连接 postrouting:出栈前,SNAT/MASQ ip/ip6/inet/bridge 支持五个 hook arp 仅 input/output;netdev 仅 ingress/egress priority(同 hook 内排序) raw = -300(conntrack 之前) mangle = -150(改包头/打标记) dstnat = -100(prerouting DNAT) filter = 0(常规过滤) security = 50(安全模块) srcnat = 100(postrouting SNAT) 数值越小越先执行;同值顺序不保证 priority 只在同一 hook 内比较 数据包路径: netdev ingress → prerouting → 路由决策 → input / forward → output → postrouting → 网卡 hook 的先后由协议栈路径决定,与 priority 无关;priority 只解决同一 hook 上多条链的排序。

五、常见误区与排障要点

  • 把 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 规则。