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

相关文章

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

MySQL 中的 CAST 函数详解及常见用法

《MySQL中的CAST函数详解及常见用法》CAST函数是MySQL中用于数据类型转换的重要函数,它允许你将一个值从一种数据类型转换为另一种数据类型,本文给大家介绍MySQL中的CAST... 目录mysql 中的 CAST 函数详解一、基本语法二、支持的数据类型三、常见用法示例1. 字符串转数字2. 数字

Python内置函数之classmethod函数使用详解

《Python内置函数之classmethod函数使用详解》:本文主要介绍Python内置函数之classmethod函数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 类方法定义与基本语法2. 类方法 vs 实例方法 vs 静态方法3. 核心特性与用法(1编程客

Python函数作用域示例详解

《Python函数作用域示例详解》本文介绍了Python中的LEGB作用域规则,详细解析了变量查找的四个层级,通过具体代码示例,展示了各层级的变量访问规则和特性,对python函数作用域相关知识感兴趣... 目录一、LEGB 规则二、作用域实例2.1 局部作用域(Local)2.2 闭包作用域(Enclos

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

MySQL count()聚合函数详解

《MySQLcount()聚合函数详解》MySQL中的COUNT()函数,它是SQL中最常用的聚合函数之一,用于计算表中符合特定条件的行数,本文给大家介绍MySQLcount()聚合函数,感兴趣的朋... 目录核心功能语法形式重要特性与行为如何选择使用哪种形式?总结深入剖析一下 mysql 中的 COUNT

MySQL 中 ROW_NUMBER() 函数最佳实践

《MySQL中ROW_NUMBER()函数最佳实践》MySQL中ROW_NUMBER()函数,作为窗口函数为每行分配唯一连续序号,区别于RANK()和DENSE_RANK(),特别适合分页、去重... 目录mysql 中 ROW_NUMBER() 函数详解一、基础语法二、核心特点三、典型应用场景1. 数据分

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

Python get()函数用法案例详解

《Pythonget()函数用法案例详解》在Python中,get()是字典(dict)类型的内置方法,用于安全地获取字典中指定键对应的值,它的核心作用是避免因访问不存在的键而引发KeyError错... 目录简介基本语法一、用法二、案例:安全访问未知键三、案例:配置参数默认值简介python是一种高级编