Linux网络 Netfilter之钩子函数注册

文章详细阐述了Linux内核中网络钩子函数的注册过程,包括nf_hook_ops结构体的字段解释,nf_register_net_hooks和nf_register_net_hook的函数作用,以及钩子函数在协议族和钩子点的组织方式。重点在于理解钩子函数的注册和链表插入机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【推荐阅读】

浅谈linux 内核网络 sk_buff 之克隆与复制

深入linux内核架构--进程&线程

了解Docker 依赖的linux内核技术

通过注册流程代码的分析,能够明确钩子函数的注册流程,理解存储钩子函数的数据结构,如下图(点击图片可查看原图);

废话不多说,开始分析;

nf_hook_ops是注册的钩子函数的核心结构,字段含义如下所示,一般待注册的钩子函数会组成一个nf_hook_ops数组,在注册过程中调用nf_register_net_hooks将所有规则加入到指定的钩子点;

1 struct nf_hook_ops {
 2     struct list_head    list;
 3 
 4     /* User fills in from here down. */
 5     nf_hookfn        *hook; /* 钩子函数 */
 6     struct net_device    *dev; /* 设备 */
 7     void            *priv; /* 私有数据 */
 8     u_int8_t        pf; /* 协议族 */
 9     unsigned int        hooknum; /* 钩子点 */
10     /* Hooks are ordered in ascending priority. */
11     int            priority; /* 优先级 */
12 };

钩子函数nf_hookfn的原型为:

1 typedef unsigned int nf_hookfn(void *priv,
2                    struct sk_buff *skb,
3                    const struct nf_hook_state *state);

nf_register_net_hooks在注册多个钩子函数时使用,它对多个函数顺序调用nf_register_net_hook进行注册,并且在注册失败时进行回滚;

1 int nf_register_net_hooks(struct net *net, const struct nf_hook_ops *reg,
 2               unsigned int n)
 3 {
 4     unsigned int i;
 5     int err = 0;
 6 
 7     /* 循环注册钩子函数 */
 8     for (i = 0; i < n; i++) {
 9         err = nf_register_net_hook(net, ®[i]);
10         /* 失败 */
11         if (err)
12             goto err;
13     }
14     return err;
15 
16 err:
17     /* 注销本次已注册的钩子函数 */
18     if (i > 0)
19         nf_unregister_net_hooks(net, reg, i);
20     return err;
21 }

多个钩子函数在注册之后,是以多个nf_hook_entry实例的链表的形式存在的,其成员如下;

1 struct nf_hook_entry {
2     struct nf_hook_entry __rcu    *next; /* 下一节点 */
3     nf_hookfn            *hook; /* 钩子函数 */
4     void                *priv; /* 私有数据 */
5     const struct nf_hook_ops    *orig_ops; /* 钩子操作 */
6 };

nf_register_net_hook为钩子函数注册的主流程,首先找到钩子点函数的入口,然后根据优先级将当前注册的钩子函数插入到链表中;

1 int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
 2 {
 3     struct nf_hook_entry __rcu **pp;
 4     struct nf_hook_entry *entry, *p;
 5 
 6     if (reg->pf == NFPROTO_NETDEV) {
 7 #ifndef CONFIG_NETFILTER_INGRESS
 8         if (reg->hooknum == NF_NETDEV_INGRESS)
 9             return -EOPNOTSUPP;
10 #endif
11         if (reg->hooknum != NF_NETDEV_INGRESS ||
12             !reg->dev || dev_net(reg->dev) != net)
13             return -EINVAL;
14     }
15 
16     /* 找到钩子点链表头部 */
17     pp = nf_hook_entry_head(net, reg);
18     if (!pp)
19         return -EINVAL;
20 
21     /* 分配钩子入口结构 */
22     entry = kmalloc(sizeof(*entry), GFP_KERNEL);
23     if (!entry)
24         return -ENOMEM;
25 
26     /* 初始化 */
27     nf_hook_entry_init(entry, reg);
28 
29     mutex_lock(&nf_hook_mutex);
30 
31     /* Find the spot in the list */
32     /* 找到钩子应该插入的位置 */
33     for (; (p = nf_entry_dereference(*pp)) != NULL; pp = &p->next) {
34         if (reg->priority < nf_hook_entry_priority(p))
35             break;
36     }
37 
38     /* 插入钩子点 */
39     rcu_assign_pointer(entry->next, p);
40     rcu_assign_pointer(*pp, entry);
41 
42     mutex_unlock(&nf_hook_mutex);
43 #ifdef CONFIG_NETFILTER_INGRESS
44     if (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)
45         net_inc_ingress_queue();
46 #endif
47 #ifdef HAVE_JUMP_LABEL
48     static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);
49 #endif
50     return 0;
51 }

nf_hook_entry_head的作用为查找钩子点函数入口,从这个函数中,我们可以看到,钩子函数存放位置为net->nf.hooks[pf] + hooknum;

1 static struct nf_hook_entry __rcu **nf_hook_entry_head(struct net *net, const struct nf_hook_ops *reg)
 2 {
 3     if (reg->pf != NFPROTO_NETDEV)
 4         return net->nf.hooks[reg->pf]+reg->hooknum;
 5 
 6 #ifdef CONFIG_NETFILTER_INGRESS
 7     if (reg->hooknum == NF_NETDEV_INGRESS) {
 8         if (reg->dev && dev_net(reg->dev) == net)
 9             return ®->dev->nf_hooks_ingress;
10     }
11 #endif
12     return NULL;
13 }

进一步查看net结构,其成员为struct netns_nf nf;

1 struct net {
 2 
 3 #ifdef CONFIG_NETFILTER
 4     struct netns_nf        nf;
 5     struct netns_xt        xt;
 6 #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
 7     struct netns_ct        ct;
 8 #endif
 9 #if defined(CONFIG_NF_TABLES) || defined(CONFIG_NF_TABLES_MODULE)
10     struct netns_nftables    nft;
11 #endif
12 #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
13     struct netns_nf_frag    nf_frag;
14 #endif
15     struct sock        *nfnl;
16     struct sock        *nfnl_stash;
17 #if IS_ENABLED(CONFIG_NETFILTER_NETLINK_ACCT)
18     struct list_head        nfnl_acct_list;
19 #endif
20 #if IS_ENABLED(CONFIG_NF_CT_NETLINK_TIMEOUT)
21     struct list_head    nfct_timeout_list;
22 #endif
23 
24 };

进一步查看netns_nf结构,其中有如下成员,struct nf_hook_entry __rcu *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; 可见,其钩子函数入口形式为hooks[协议族][钩子点],在二维数组的每个节点都对应着一个钩子函数链表,内部多个nf_hook_entry通过优先级从小到大排列;

1 struct netns_nf {
 2 #if defined CONFIG_PROC_FS
 3     struct proc_dir_entry *proc_netfilter;
 4 #endif
 5     const struct nf_queue_handler __rcu *queue_handler;
 6     const struct nf_logger __rcu *nf_loggers[NFPROTO_NUMPROTO];
 7 #ifdef CONFIG_SYSCTL
 8     struct ctl_table_header *nf_log_dir_header;
 9 #endif
10     struct nf_hook_entry __rcu *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
11 #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV4)
12     bool            defrag_ipv4;
13 #endif
14 #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
15     bool            defrag_ipv6;
16 #endif
17 };

协议的定义如下:

1 enum {
 2     NFPROTO_UNSPEC =  0,
 3     NFPROTO_INET   =  1,
 4     NFPROTO_IPV4   =  2,
 5     NFPROTO_ARP    =  3,
 6     NFPROTO_NETDEV =  5,
 7     NFPROTO_BRIDGE =  7,
 8     NFPROTO_IPV6   = 10,
 9     NFPROTO_DECNET = 12,
10     NFPROTO_NUMPROTO,
11 };

IPv4钩子点的定义如下:

1 enum nf_inet_hooks {
2     NF_INET_PRE_ROUTING,
3     NF_INET_LOCAL_IN,
4     NF_INET_FORWARD,
5     NF_INET_LOCAL_OUT,
6     NF_INET_POST_ROUTING,
7     NF_INET_NUMHOOKS
8 };

注册流程到此为止;

<think>好的,我现在需要回答用户关于在Linux内核中注册钩子函数的问题。首先,我需要回忆一下相关的知识。用户提到了引用[1],里面提到了Linux系统调用钩子和中断描述符表,这可能涉及到系统调用钩子注册方法。另外,引用[3]提到了sk_buffer结构,这可能和网络钩子有关。 首先,我应该确定用户问的是哪种类型的钩子函数注册,因为Linux内核中有多种钩子机制,比如系统调用钩子网络钩子(如Netfilter)、文件操作钩子等。用户的问题比较笼统,可能需要分情况讨论。 然后,我需要回忆不同钩子注册方法。比如系统调用钩子通常通过修改系统调用表来替换函数指针,而Netfilter钩子使用nf_register_net_hook等函数。同时,还需要注意内核版本的不同,因为方法可能有所变化,比如旧版本可能直接替换系统调用表,而新版本可能有更安全的机制或者需要使用kprobes。 接下来,我需要组织回答的结构,分点说明不同的钩子类型及其注册步骤。每个步骤需要给出具体的函数或代码示例,比如系统调用钩子可能需要获取系统调用表的地址,替换对应的函数指针,并保存原函数。同时要提到注意事项,比如内核内存保护(如CR0写保护)的处理,以及模块的安全卸载。 另外,用户提供的引用[1]中的链接可能涉及到系统调用钩子的具体实现,需要确保描述的方法符合该引用中的内容。同时,引用[3]中的sk_buffer结构可能在网络钩子中使用到,说明Netfilter钩子处理数据包时涉及的结构。 最后,生成相关问题时要涵盖不同钩子类型、安全机制、调试工具和实际应用场景,这样用户可以根据需要进一步探索。 需要确保回答中的LaTeX格式正确,例如函数名或变量用$...$包裹,代码块用反引号标注。同时,引用标识要正确添加,比如在提到系统调用钩子时引用[^1],在Netfilter部分引用[^3]。 现在整理这些思路,形成结构清晰的中文回答,并附上相关问题。</think>在Linux内核中注册钩子函数的具体实现方式取决于目标子系统,常见的钩子注册方法包括系统调用钩子Netfilter网络钩子、文件操作钩子等。以下是三种典型场景的实现方法: ### 1. 系统调用钩子注册 通过修改系统调用表实现,适用于内核版本<=4.xx: ```c // 1. 获取系统调用表地址 unsigned long *sys_call_table = (unsigned long*)kallsyms_lookup_name("sys_call_table"); // 2. 保存原始函数指针 static orig_open = (orig_open_t)sys_call_table[__NR_open]; // 3. 关闭写保护 write_cr0(read_cr0() & (~0x10000)); // 4. 替换函数指针 sys_call_table[__NR_open] = (unsigned long)hooked_open; // 5. 恢复写保护 write_cr0(read_cr0() | 0x10000); ``` 该方法通过修改CR0寄存器关闭内存写保护后直接替换系统调用表项。 ### 2. Netfilter网络钩子 用于拦截网络数据包,通过$nf_register_net_hook()$注册: ```c static struct nf_hook_ops nfho = { .hook = hook_func, .pf = NFPROTO_IPV4, .hooknum = NF_INET_PRE_ROUTING, .priority = NF_IP_PRI_FIRST }; nf_register_net_hook(&init_net, &nfho); ``` 钩子函数原型: ```c unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 处理sk_buffer结构[^3] return NF_ACCEPT; } ``` ### 3. Kprobes动态钩子 更安全的内核跟踪机制: ```c static struct kprobe kp = { .symbol_name = "sys_open", .pre_handler = handler_pre, }; register_kprobe(&kp); ``` 预处理函数: ```c static int handler_pre(struct kprobe *p, struct pt_regs *regs) { printk("拦截sys_open调用\n"); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值