netfilter分析1-钩子函数在内核的初始化

2023-11-23 23:30

本文主要是介绍netfilter分析1-钩子函数在内核的初始化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Linux内核中网络防火墙是通过NF_HOOK宏调用钩子函数进行报文处理,本文基于内核版本4.4对钩子函数的初始化流程进行描述。

以过滤本地报文的钩子函数为例。本地报文过滤钩子函数调用宏:
NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,

       net, NULL, skb, skb->dev, NULL,

       ip_local_deliver_finish);

这个宏在文件/include/linux/netfilter.h中定义:

static inline intNF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,struct net_device *in, struct net_device *out,int (*okfn)(struct net *, struct sock *, struct sk_buff *)){return NF_HOOK_THRESH(pf, hook, net, sk, skb, in, out, okfn, INT_MIN);}static inline intNF_HOOK_THRESH(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk,struct sk_buff *skb, struct net_device *in,struct net_device *out,int (*okfn)(struct net *, struct sock *, struct sk_buff *),int thresh){int ret = nf_hook_thresh(pf, hook, net, sk, skb, in, out, okfn, thresh);if (ret == 1)ret = okfn(net, sk, skb);return ret;}static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook,struct net *net,struct sock *sk,struct sk_buff *skb,struct net_device *indev,struct net_device *outdev,int (*okfn)(struct net *, struct sock *, struct sk_buff *),int thresh){struct list_head *hook_list = &net->nf.hooks[pf][hook];if (nf_hook_list_active(hook_list, pf, hook)) {struct nf_hook_state state;nf_hook_state_init(&state, hook_list, hook, thresh,pf, indev, outdev, sk, net, okfn);return nf_hook_slow(skb, &state);}return 1;}

最终通过链表头struct list_head *hook_list = &net->nf.hooks[pf][hook]这个链表头实际就是所谓的挂载点,后面钩子函数通过链表的形式挂载到这个链表头下

例如过滤本地报文通过遍历执行钩子函数挂载点net->nf.hooks [NFPROTO_IPV4][NF_INET_LOCAL_IN]上的所有钩子函数来实现对报文的过滤处理。

net->nf.hooks [NFPROTO_IPV4][NF_INET_LOCAL_IN]挂载点钩子函数是通过定义在:
net/ipv4/netfilter/iptable_netfilter.c文件中定义的iptable_filter_init进行初始化的

static int __init iptable_filter_init(void){int ret;ret = register_pernet_subsys(&iptable_filter_net_ops);if (ret < 0)return ret;/* Register hooks */filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook);if (IS_ERR(filter_ops)) {ret = PTR_ERR(filter_ops);unregister_pernet_subsys(&iptable_filter_net_ops);}return ret;}

该函数中通过xt_hook_link初始化钩子函数,该函数定义在文件:

net/netfilter/x_tables.c

struct nf_hook_ops *xt_hook_link(const struct xt_table *table, nf_hookfn *fn){unsigned int hook_mask = table->valid_hooks;uint8_t i, num_hooks = hweight32(hook_mask);uint8_t hooknum;struct nf_hook_ops *ops;int ret;ops = kmalloc(sizeof(*ops) * num_hooks, GFP_KERNEL);if (ops == NULL)return ERR_PTR(-ENOMEM);for (i = 0, hooknum = 0; i < num_hooks && hook_mask != 0;hook_mask >>= 1, ++hooknum)                 {if (!(hook_mask & 1))continue;ops[i].hook     = fn;ops[i].pf       = table->af;ops[i].hooknum  = hooknum;ops[i].priority = table->priority;++i;}ret = nf_register_hooks(ops, num_hooks);if (ret < 0) {kfree(ops);return ERR_PTR(ret);}return ops;
}

参数table是对应的filter表,为了不偏离主题,本文只对钩子函数初始化用到的部分进行简单说明,不对表内容展开详细描述,filter表初始化申明如下:

static const struct xt_table packet_filter = {.name = "filter",.valid_hooks = FILTER_VALID_HOOKS,.me = THIS_MODULE,.af = NFPROTO_IPV4,.priority = NF_IP_PRI_FILTER,};

.priority = NF_IP_PRI_FILTER,对钩子函数优先级进行设置。

.valid_hooks = FILTER_VALID_HOOKS,钩子函数会在以下几个点进行挂载:

#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \

    (1 << NF_INET_FORWARD) | \

    (1 << NF_INET_LOCAL_OUT))

参数fn是要注册的钩子函数指针,定义如下:

static unsigned intiptable_filter_hook(void *priv, struct sk_buff *skb,const struct nf_hook_state *state){if (state->hook == NF_INET_LOCAL_OUT &&(skb->len < sizeof(struct iphdr) ||ip_hdrlen(skb) < sizeof(struct iphdr)))/* root is playing with raw sockets. */return NF_ACCEPT;return ipt_do_table(skb, state, state->net->ipv4.iptable_filter);}

该函数最终通过ipt_do_table用filter表中的规则对报文进行处理。本文不对表处理进行描述。针对filter表,初始化是只注册了一个相同的钩子函数iptable_filter_hook,该钩子函数注册到了NF_INET_LOCAL_IN、NF_INET_FORWARD、NF_INET_LOCAL_OUT三个挂载点。

钩子函数先存储到struct nf_hook_ops *ops结构中的hook成员,该结构定义如下:

struct nf_hook_ops {struct list_head list;/* User fills in from here down. */nf_hookfn *hook;struct net_device *dev;void *priv;u_int8_t pf;unsigned int hooknum;/* Hooks are ordered in ascending priority. */int priority;};

然后nf_register_hooks函数通过结构中的list将钩子函数分别挂载到上面说的

net->nf.hooks [NFPROTO_IPV4][NF_INET_LOCAL_IN]、

net->nf.hooks [NFPROTO_IPV4][ NF_INET_FORWARD]、

net->nf.hooks [NFPROTO_IPV4][ NF_INET_LOCAL_OUT]、

钩子挂载点。

nf_register_hooks定义在文件/net/netfiler/core.c中

int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n){unsigned int i;int err = 0;for (i = 0; i < n; i++) {err = nf_register_hook(®[i]);if (err)goto err;}return err;err:if (i > 0)nf_unregister_hooks(reg, i);return err;}

进一步通过nf_register_hook对钩子函数进行注册,定义如下:

int nf_register_hook(struct nf_hook_ops *reg){struct net *net, *last;int ret;rtnl_lock();for_each_net(net) {ret = nf_register_net_hook(net, reg);if (ret && ret != -ENOENT)goto rollback;}list_add_tail(®->list, &nf_hook_list);rtnl_unlock();return 0;rollback:last = net;for_each_net(net) {if (net == last)break;nf_unregister_net_hook(net, reg);}rtnl_unlock();return ret;}

其中进一步通过nf_register_net_hook注册到net->nf.hooks[reg->pf][reg->hooknum]挂载点

int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg){struct list_head *hook_list;struct nf_hook_entry *entry;struct nf_hook_ops *elem;entry = kmalloc(sizeof(*entry), GFP_KERNEL);if (!entry)return -ENOMEM;//将nf_hook_ops转存到entty中,这里之所以转存是为了后面方便删除。entry->orig_ops = reg;entry->ops = *reg;hook_list = nf_find_hook_list(net, reg);if (!hook_list) {kfree(entry);return -ENOENT;}mutex_lock(&nf_hook_mutex);list_for_each_entry(elem, hook_list, list) {if (reg->priority < elem->priority)break;}list_add_rcu(&entry->ops.list, elem->list.prev);mutex_unlock(&nf_hook_mutex);#ifdef CONFIG_NETFILTER_INGRESSif (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)net_inc_ingress_queue();#endif#ifdef HAVE_JUMP_LABELstatic_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);#endifreturn 0;}

其中通过nf_find_hook_list找到挂载点,没错就是函数中的&net->nf.hooks[reg->pf][reg->hooknum],终于又遇见你。


static struct list_head *nf_find_hook_list(struct net *net,const struct nf_hook_ops *reg){struct list_head *hook_list = NULL;if (reg->pf != NFPROTO_NETDEV)hook_list = &net->nf.hooks[reg->pf][reg->hooknum];else if (reg->hooknum == NF_NETDEV_INGRESS) {#ifdef CONFIG_NETFILTER_INGRESSif (reg->dev && dev_net(reg->dev) == net)hook_list = ®->dev->nf_hooks_ingress;#endif}return hook_list;}

找到挂载点后就可以对钩子函数进行挂载了,挂载过程需要枷锁,然后挂载到对应优先级所在的位置,遍历执行钩子函数时是按优先级执行的,钩子函数中priority越小优先级越高,因此钩子函数也是按照优先级从低到高进行挂载。挂载过程很简单,如下所示:

mutex_lock(&nf_hook_mutex);list_for_each_entry(elem, hook_list, list) {if (reg->priority < elem->priority)break;}list_add_rcu(&entry->ops.list, elem->list.prev);mutex_unlock(&nf_hook_mutex);

到此钩子函数初始化就结束了,通过上面的分析可以得出钩子函数、filter表关系如下图:

该图表示针对NF_INET_LOCAL_IN、NF_INET_FORWARD、NF_INET_LOCAL_OUT挂载点注册了优先级为NF_IP_PRI_FILTER 的钩子函数:iptable_filter_hook,该钩子函数通过对将报文根据filter表中的规则进行处理。

这篇关于netfilter分析1-钩子函数在内核的初始化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/420852

相关文章

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程