2. Netfilter 框架概述

Netfilter 是 Linux 内核中一个高度模块化的包处理框架,它允许内核模块在网络协议栈的关键路径上注册回调函数,从而对经过的数据包进行过滤、修改、跟踪或丢弃。本章将深入剖析 Netfilter 的架构设计、核心钩子点、钩子函数注册机制以及优先级控制,为后续开发连接跟踪模块奠定基础。

2.1 Netfilter 架构设计

Netfilter 框架位于 Linux 网络协议栈的 IP 层(IPv4/IPv6),它通过在内核网络路径中插入一系列检查点(钩子点),将数据包处理流程从内核固定逻辑扩展为可编程的模块化流水线。其核心设计思想是 “钩子 + 优先级” 的回调机制。

架构核心组件:
  • 钩子点(Hook Point): 协议栈中预定义的 5 个固定位置(NF_INET_PRE_ROUTING 等)。
  • 钩子函数(Hook Function): 内核模块注册的回调,返回 NF_ACCEPT、NF_DROP 等判决。
  • 优先级(Priority): 同一钩子点上多个函数的执行顺序,值越小优先级越高。
  • nf_hook_ops 结构体: 注册钩子的核心数据结构,包含钩子函数指针、协议族、钩子点、优先级。
Netfilter 内核架构总览 Netfilter 内核架构总览 用户空间(iptables / nftables 用户态工具) 内核空间 Netfilter 框架 网络协议栈(IP 层)数据包流向 PRE_ROUTING LOCAL_IN FORWARD LOCAL_OUT POST_ROUTING nf_hook_ops 链表(按优先级排序) 连接跟踪模块 (conntrack) 过滤模块 (iptable_filter) NAT 模块 (iptable_nat)
设计要点: Netfilter 的钩子机制是 同步 的——每个钩子函数必须返回一个判决结果(如 NF_ACCEPT),协议栈才会继续传递数据包。这种设计保证了包处理的可预测性,但也要求钩子函数必须高效,避免阻塞。

2.2 五大钩子点(Hook Point)详解

Netfilter 在 IPv4 协议栈中定义了 5 个标准钩子点,分别对应数据包在不同路由决策阶段的位置。理解每个钩子点的触发时机和典型用途,是正确注册钩子函数的前提。

钩子点枚举 数值 触发位置 典型用途
NF_INET_PRE_ROUTING 0 数据包进入协议栈后,路由决策之前 连接跟踪、DNAT、原始包过滤
NF_INET_LOCAL_IN 1 路由决策后,数据包发往本机 本机入站过滤、连接跟踪状态检查
NF_INET_FORWARD 2 路由决策后,数据包需要转发 转发过滤、带宽控制、连接跟踪
NF_INET_LOCAL_OUT 3 本机进程发送数据包,路由决策之前 本机出站过滤、SNAT、连接跟踪
NF_INET_POST_ROUTING 4 路由决策后,数据包离开本机前 SNAT、出站包修改、连接跟踪确认
Netfilter 钩子点与路由决策流程 钩子点与路由决策流程 网络入口 PRE_ROUTING 路由 LOCAL_IN 本机进程 FORWARD 本机进程 LOCAL_OUT 路由 POST_ROUTING 网络出口
常见误区: LOCAL_OUT 钩子点位于路由决策 之前,而 POST_ROUTING 位于路由决策 之后。这意味着在 LOCAL_OUT 中修改数据包的目的地址(如 REDIRECT)会影响路由决策结果,而在 POST_ROUTING 中修改则不会。

2.3 钩子函数注册与优先级

内核模块通过 nf_register_net_hook() 或 nf_register_hook() 函数向 Netfilter 框架注册钩子。注册的核心是填充 struct nf_hook_ops 结构体,其中 priority 字段决定了同一钩子点上多个钩子函数的执行顺序。

2.3.1 nf_hook_ops 结构体

struct nf_hook_ops {
    struct list_head        list;       // 内核链表节点,用于挂接到钩子点链表
    nf_hookfn               *hook;      // 钩子函数指针,核心回调
    struct net_device       *dev;       // 绑定的网络设备(NULL 表示所有设备)
    void                    *priv;      // 私有数据指针
    u_int8_t                pf;         // 协议族,如 AF_INET、AF_INET6
    unsigned int            hooknum;    // 钩子点编号(0-4)
    int                     priority;   // 优先级,值越小优先级越高
};

2.3.2 优先级定义与标准值

Netfilter 预定义了一组优先级常量,位于 <linux/netfilter_ipv4.h> 中。内核模块和 iptables 扩展通常使用这些常量来确保正确的执行顺序。

优先级常量 数值 典型使用者
NF_IP_PRI_FIRST INT_MIN 最高优先级,极少使用
NF_IP_PRI_CONNTRACK_DEFRAG -400 连接跟踪分片重组
NF_IP_PRI_RAW -300 raw 表(NOTRACK 目标)
NF_IP_PRI_SELINUX_FIRST -225 SELinux 安全策略
NF_IP_PRI_CONNTRACK -200 连接跟踪核心
NF_IP_PRI_MANGLE -150 mangle 表
NF_IP_PRI_NAT_DST -100 DNAT(目标地址转换)
NF_IP_PRI_FILTER 0 filter 表(默认过滤)
NF_IP_PRI_SECURITY 50 security 表
NF_IP_PRI_NAT_SRC 100 SNAT(源地址转换)
NF_IP_PRI_SELINUX_LAST 225 SELinux 最后检查
NF_IP_PRI_LAST INT_MAX 最低优先级

2.3.3 钩子函数注册示例

以下代码展示了一个最小化的 Netfilter 钩子模块,它在 NF_INET_PRE_ROUTING 点注册一个钩子函数,打印经过的每个数据包信息。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>

// 钩子函数:打印数据包源/目的 IP
static unsigned int my_hook_func(void *priv,
                                 struct sk_buff *skb,
                                 const struct nf_hook_state *state)
{
    struct iphdr *iph = ip_hdr(skb);
    if (iph) {
        printk(KERN_INFO "Netfilter: packet from %pI4 to %pI4\n",
               &iph->saddr, &iph->daddr);
    }
    return NF_ACCEPT;  // 放行数据包
}

// 定义钩子操作结构体
static struct nf_hook_ops my_hook_ops = {
    .hook     = my_hook_func,
    .pf       = AF_INET,
    .hooknum  = NF_INET_PRE_ROUTING,
    .priority = NF_IP_PRI_FILTER,  // 与 filter 表同优先级
};

// 模块初始化
static int __init my_module_init(void)
{
    int ret = nf_register_net_hook(&init_net, &my_hook_ops);
    if (ret) {
        printk(KERN_ERR "Failed to register hook\n");
        return ret;
    }
    printk(KERN_INFO "Netfilter hook registered\n");
    return 0;
}

// 模块退出
static void __exit my_module_exit(void)
{
    nf_unregister_net_hook(&init_net, &my_hook_ops);
    printk(KERN_INFO "Netfilter hook unregistered\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
钩子函数注册与优先级排序 钩子函数注册与优先级排序 NF_INET_PRE_ROUTING 钩子点 conntrack (DEFRAG) 优先级: -400 conntrack (CORE) 优先级: -200 mangle (PREROUTING) 优先级: -150 DNAT (NAT_DST) 优先级: -100 自定义钩子 (示例) 优先级: 0 SELinux (LAST) 优先级: 225 优先级值越小,执行顺序越靠前(链表头部 → 尾部)

2.3.4 钩子函数返回值

钩子函数必须返回一个 unsigned int 类型的判决值,Netfilter 框架根据返回值决定数据包的后续处理路径。

返回值 含义 行为
NF_ACCEPT 接受 数据包继续沿协议栈正常路径传递
NF_DROP 丢弃 立即释放 skb,不再传递
NF_QUEUE 排队 将数据包发送到用户空间处理(如 libnetfilter_queue)
NF_STOLEN 窃取 钩子函数接管数据包所有权,框架不再处理
NF_REPEAT 重复 让当前钩子点重新执行(需谨慎使用,防死循环)
开发建议: 在自定义钩子函数中,除非明确需要丢弃或重定向数据包,否则应返回 NF_ACCEPT。返回 NF_DROP 时务必记录日志(如 printk_ratelimit),以便调试。使用 NF_QUEUE 时需确保用户空间有对应的监听程序,否则数据包会丢失。

2.4 本章小结