nftables 表达式不解析报文头,仅读取内核已解析并缓存的字段(如IP、TCP端口等)进行匹配;支持链路层、网络层、传输层及ICMP/连接跟踪等字段的零拷贝核准,典型如tcp dport {22,80}、icmpv6 type nd-neighbor-solicit。

nftables 的 Expression 语法本身不用于“解包”报文头,它也不在内核中执行协议解析(如从 IP 包里剥出 TCP 头、再从中取端口)。真正做报文头解析的是 Linux 内核网络栈本身——当数据包到达 Netfilter 钩子点(如 input、forward)时,IP 层已解析完 IP 头,传输层信息(如 TCP/UDP 端口、ICMP 类型)也已由对应协议子系统填充到 sk_buff 结构的元数据中。
nftables 的 Expression 引擎作用是「读取」这些早已就绪的字段值,并做匹配或计算。它像一个高度结构化的“读取器+判断器”,而非“解析器”。
所以准确地说:
✅ nftables 表达式可核准(即匹配、验证)报文头字段(如源 IP、目的端口、TCP 标志位、ICMP 类型等);
❌ 它不负责解包——那是内核协议栈在收包软中断路径中完成的底层工作。
如何用 Expression 核准常见报文头字段
以下操作均在内核层级生效(无需用户态解析),字段访问零拷贝、无额外开销:
支持的头部字段类型(直接可用)
-
链路层:
meta oifname "eth0"、vlan id 100 -
网络层:
ip saddr 192.168.1.0/24、ip protocol tcp、ip6 daddr ::1 -
传输层:
tcp dport { 22, 80, 443 }、udp sport 53、tcp flags & (fin | syn) == syn -
ICMP/ICMPv6:
icmp type echo-request、icmpv6 type nd-neighbor-solicit -
连接跟踪元数据:
ct state established、ct original ip saddr 10.0.0.5
⚠️ 注意:所有字段名(如
saddr,dport,flags)都是内核预定义的符号,不是字符串匹配,不走正则、不查表,直接映射到sk_buff或nf_conn中对应内存偏移。
典型核准场景写法
# 核准 IPv4 TCP SYN 包(防扫描) nft add rule ip filter input ip protocol tcp tcp flags & (fin | syn | rst | ack) == syn counter accept # 核准 ICMPv6 邻居请求(仅允许本地链路) nft add rule ip6 filter input icmpv6 type nd-neighbor-solicit ip6 saddr fe80::/10 counter accept # 核准带特定 DSCP 值的 VoIP 流量(网络层 ToS 字段) nft add rule ip filter input ip tos 0xb8 counter accept # 0xb8 = EF (Expedited Forwarding) # 核准嵌套 VLAN(802.1ad)外层 VID=10,内层 VID=100 nft add rule bridge filter input vlan id 10 meta protocol 0x8100 @ll,96,16 == 0x64 counter accept
? 第四条用了
@ll,96,16——这是原始字节提取表达式:从链路层起偏移 96 bit(即第 12 字节),取 16 bit,用于读取 QinQ 内层 VLAN ID。这种写法绕过协议栈语义解析,直接访包内存,属于“半解包”能力,但需精确知道帧格式和字节位置,一般只用于特殊隧道或厂商私有封装。
表达式核准 ≠ 用户态解析:关键区别
| 项目 | nftables Expression | 用户态抓包(如 tcpdump/libpcap) |
|---|---|---|
| 执行位置 | 内核 Netfilter 钩子路径中(实时、无拷贝) | 用户空间,需 copy_from_user 拷贝整包 |
| 字段来源 | 已由内核协议栈解析并缓存(如 skb->transport_header) |
自行从原始字节流逐层解析(慢、CPU 密集) |
| 可靠性 | 依赖内核对协议的正确实现(如 TCP 校验和已验过) | 可能解析错误(如错判分片、选项长度) |
| 扩展性 | 新字段需内核支持(如 nft_meta_get() 注册新元数据) |
纯软件逻辑,任意定义解析规则 |
若真需自定义“解包”逻辑(极少数场景)
内核不开放裸包内存给 nftables 表达式随意遍历。但可通过以下方式间接支持:
- 使用
payload表达式 + 固定偏移(如@nh,20,16读 IPv4 源地址),适用于已知固定格式; - 编写自定义内核模块,注册新的
nft_expr_ops,提供专用解析函数(如解析 VXLAN 外层头),再通过nft register_expr()暴露为新 expression 关键字(如vxlan vni 12345); - 利用
ct helper或nf_log配合用户态服务做深度检测(此时已脱离 expression 范畴)。
? 实际生产中,99% 的头部核准需求(端口、协议、状态、ICMP 类型、DSCP、VLAN)都已有原生 expression 支持,无需手动解包。
不复杂但容易忽略:Expression 的威力不在“解”,而在“准”——它用声明式语法,在毫秒级内对千种字段组合做原子判定,这才是内核防火墙该有的样子。

















