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-钩子函数在内核的初始化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/luky_zhou123/article/details/118929103
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/420852

相关文章

IDEA下"File is read-only"可能原因分析及"找不到或无法加载主类"的问题

《IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题》:本文主要介绍IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题,具有很好的参... 目录1.File is read-only”可能原因2.“找不到或无法加载主类”问题的解决总结1.File

PyTorch中cdist和sum函数使用示例详解

《PyTorch中cdist和sum函数使用示例详解》torch.cdist是PyTorch中用于计算**两个张量之间的成对距离(pairwisedistance)**的函数,常用于点云处理、图神经网... 目录基本语法输出示例1. 简单的 2D 欧几里得距离2. 批量形式(3D Tensor)3. 使用不

Dubbo之SPI机制的实现原理和优势分析

《Dubbo之SPI机制的实现原理和优势分析》:本文主要介绍Dubbo之SPI机制的实现原理和优势,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Dubbo中SPI机制的实现原理和优势JDK 中的 SPI 机制解析Dubbo 中的 SPI 机制解析总结Dubbo中

C#继承之里氏替换原则分析

《C#继承之里氏替换原则分析》:本文主要介绍C#继承之里氏替换原则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#里氏替换原则一.概念二.语法表现三.类型检查与转换总结C#里氏替换原则一.概念里氏替换原则是面向对象设计的基本原则之一:核心思想:所有引py

MySQL 字符串截取函数及用法详解

《MySQL字符串截取函数及用法详解》在MySQL中,字符串截取是常见的操作,主要用于从字符串中提取特定部分,MySQL提供了多种函数来实现这一功能,包括LEFT()、RIGHT()、SUBST... 目录mysql 字符串截取函数详解RIGHT(str, length):从右侧截取指定长度的字符SUBST

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

PostgreSQL 序列(Sequence) 与 Oracle 序列对比差异分析

《PostgreSQL序列(Sequence)与Oracle序列对比差异分析》PostgreSQL和Oracle都提供了序列(Sequence)功能,但在实现细节和使用方式上存在一些重要差异,... 目录PostgreSQL 序列(Sequence) 与 oracle 序列对比一 基本语法对比1.1 创建序

C++类和对象之初始化列表的使用方式

《C++类和对象之初始化列表的使用方式》:本文主要介绍C++类和对象之初始化列表的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C++初始化列表详解:性能优化与正确实践什么是初始化列表?初始化列表的三大核心作用1. 性能优化:避免不必要的赋值操作2. 强

SpringIOC容器Bean初始化和销毁回调方式

《SpringIOC容器Bean初始化和销毁回调方式》:本文主要介绍SpringIOC容器Bean初始化和销毁回调方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录前言1.@Bean指定初始化和销毁方法2.实现接口3.使用jsR250总结前言Spring Bea

Spring实现Bean的初始化和销毁的方式

《Spring实现Bean的初始化和销毁的方式》:本文主要介绍Spring实现Bean的初始化和销毁的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Bean的初始化二、Bean的销毁总结在前面的章节当中介绍完毕了ApplicationContext,也就