Netfilter 框架
来源:互联网 发布:阿里云对象存储oss 编辑:程序博客网 时间:2024/06/08 00:33
通俗的说,netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上登记了一些处理函数进行处理(如包过滤,NAT等,还有用户自定义的函数)
1. Netfilter 框架
- 框架图
- 钩子定义(注册点) kernel/linux/include/linux/netfilter.h
enum nf_inet_hooks { NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING, NF_INET_NUMHOOKS };
- NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 目的地址转换在此点进行;
- NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
- NF_IP_FORWARD:要转发的包通过此检测点,FORWARD包过滤在此点进行;
- NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。
- NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;
- NF_INET_NUMHOOKS: hook点的数目
- 优先级定义() kernel/linux/include/linux/netfilter_ipv4.h
enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_SECURITY = 50, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX, };
下图是netfilter内嵌的几个表,mangle,filter,nat等在5个hook点的注册位置和注册顺序
2. Netfilter 初始化
内核在启动的时候通过netfilter_init函数完成钩子函数的初始化工作;如果需要在相应的钩子点挂载钩子函数,则需要首先定义一个nf_hook_ops结构,在其中实现所需的钩子函数,再调用nf_register_hook将该钩子函数注册到上述的全局二维数组中。
下面先来看一下netfilter_init初始化函数:
void __init netfilter_init(void){int i, h;/*首先完成全局二维链表的初始化工作*/for (i = 0; i < ARRAY_SIZE(nf_hooks); i++) { for (h = 0; h < NF_MAX_HOOKS; h++) INIT_LIST_HEAD(&nf_hooks[i][h]);}#ifdef CONFIG_PROC_FS/*在proc文件系统内注册相应的文件选项*/proc_net_netfilter = proc_mkdir("netfilter", init_net.proc_net);if (!proc_net_netfilter) panic("cannot create netfilter proc entry");#endif/*完成proc文件夹netfilter下针对nf_queue的初始化工作,完成读、写等功能*/if (netfilter_queue_init() < 0) panic("cannot initialize nf_queue");/*完成proc文件夹netfilter下日志文件的初始化工作*/if (netfilter_log_init() < 0) panic("cannot initialize nf_log");}
主要是初始化了下面整个数组
extern struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
用来存储不同协议过滤点的回调处理函数。如下图所示:
我们所说的内核的netfilter,应该包括二层数据的filter操作,以及对三层及三层以上数据的filter等操作。 只不过二层的filter实现与三层及三层也上的filter实现有所不同。其中二层的filter与应用层程序ebtables结合使用,而三层及以上的filter结合iptables使用。但是二层filter与三层filter使用的都是统一的hook机制。
3. Netfilter hook模块注册和注销
3.1 模块注册
- 注册函数
int nf_register_hook(struct nf_hook_ops *reg){ struct nf_hook_ops *elem; int err; //互斥锁加锁(可中断) err = mutex_lock_interruptible(&nf_hook_mutex); if (err < 0) return err; //内核链表中的一个函数,常用于遍历链表搜索元素 //这里是插入新对象,下面的循环遍历就是根据优先级找到合适的插入位置,这样在进行规则匹配的时候就是按照优先级来的 list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) { if (reg->priority < elem->priority) break; } list_add_rcu(®->list, elem->list.prev);//插入新对象(参数指定) mutex_unlock(&nf_hook_mutex);//释放锁 #if defined(CONFIG_JUMP_LABEL) static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);//忽略 #endif return 0;}
nf_hook_ops数据结构在linux/netfilter.h中定义,该数据结构的定义如下:
struct nf_hook_ops { struct list_head list;//双向链表 nf_hookfn *hook;//回调函数 int pf;//协议族 int hooknum;//hook点 int priority;//优先级 };
从上面的代码就可以看出,所有的 struct nf_hook_ops 都以指针的形式记录在一个列表中,其保存的顺序就是按照优先级的顺序。这个表就是前面图片中的链表。
- 回调处理的hook函数
typedef unsigned int nf_hookfn(unsigned int hooknum,//hook点 struct sk_buff **skb, const struct net_device *in,//入口 const struct net_device *out,//出口 int (*okfn)(struct sk_buff *));//okfn函数是当数据包被允许通过时所进行的处理
#ifdef CONFIG_NETFILTER...#ifdef CONFIG_NETFILTER_DEBUG#define NF_HOOK nf_hook_slow#else#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \(list_empty(&nf_hooks[(pf)][(hook)]) \? (okfn)(skb) \: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))#endif...#else#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)#endif
可见okfn函数是必不可少的,当Netfilter被启用时,它用于完成接收的数据包后的后续操作,如果不启用Netfilter做数据包过滤,则所有的数据包都被接受,直接调用该函数做后续操作
- 内核的Netfilter钩子函数调用:
在网卡收到包之后交由ip层处理的时候,就交给了ip_recv函数
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev){ 在做了基本的头校验等工作后,就到了我们的重点NF_HOOK钩子函数,此时还未作路由等路处理 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
NF_HOOK |--> NF_HOOK_THRESH |--> nf_hook_thresh |--> nf_hook_slow |--> nf_iterate
nf_hook_slow() /net/netfilter/core.c:
int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int hook_thresh) { struct list_head *elem; unsigned int verdict; int ret = 0; /* We may already have this, but read-locks nest anyway */ rcu_read_lock(); #ifdef CONFIG_NETFILTER_DEBUG if (unlikely((*pskb)->nf_debug & (1 << hook))) { printk("nf_hook: hook %i already set.\n", hook); nf_dump_skb(pf, *pskb); } (*pskb)->nf_debug |= (1 << hook); #endif /*取得对应的链表首部*/ elem = &nf_hooks[pf][hook]; next_hook: /*调用对应的钩子函数*/ verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev, outdev, &elem, okfn, hook_thresh); /*判断返回值,做相应的处理*/ if (verdict == NF_ACCEPT || verdict == NF_STOP) { ret = 1; /*前面提到过,返回1,则表示装继续调用okfn函数指针*/ goto unlock; } else if (verdict == NF_DROP) { kfree_skb(*pskb); /*删除数据包,需要释放skb*/ ret = -EPERM; } else if (verdict == NF_QUEUE) { NFDEBUG("nf_hook: Verdict = QUEUE.\n"); if (!nf_queue(*pskb, elem, pf, hook, indev, outdev, okfn)) goto next_hook; } unlock: rcu_read_unlock(); return ret; }
内核的数据转发函数,调用宏NF_HOOK时,会告诉它协议簇和Hook类型,nf_hook_slow函数根据这两个要素,可以很轻易地从nf_hooks 中取得对应的前面注册好的Hook链表的首部: elem = &nf_hooks[pf][hook]; 然后,就调用函数nf_iterate ,遍历Hook链表,调用链用上所有的Hook函数。
在nf_iterate中会有以下几个target来处理数据包:
#define NF_DROP 0#define NF_ACCEPT 1#define NF_STOLEN 2#define NF_QUEUE 3#define NF_REPEAT 4#define NF_STOP 5
NF_DROP: 表示丢弃掉报文;
NF_ACCEPT: 表示勾子函数允许报文继续向下处理,此时应该继续执行队列上的下一个勾子函数。
NF_STOLEN: 表示报文不再往上传递,与NF_DROP不同的是,它没有调用kfree_skb()释放掉skb;
NF_QUEUE: 将该数据包插入到用户空间。
NF_REPEAT: 表示要重复执行勾子函数一次;所以勾子函数要编写得当,否则报文会一直执行一个返回。
NF_STOP : 表示停止执行队列上的勾子函数,直接返回;
小结
hook注册函数nf_register_hook 功能: 将一个新的hook 结构添加nf_hooks数组的相应的成员链表中
- A 根据协议号、hook点,确定链表
- B 根据优先级将该hook结构添加到链表的合适位置
- C.注册一个hook函数是围绕nf_hook_ops数据结构的一个操作。
3.2 模块注销
void nf_unregister_hook(struct nf_hook_ops *reg){ mutex_lock(&nf_hook_mutex);//获取互斥锁,不可中断 list_del_rcu(®->list);//删除指定对象 mutex_unlock(&nf_hook_mutex);//释放锁#if defined(CONFIG_JUMP_LABEL) static_key_slow_dec(&nf_hooks_needed[reg->pf][reg->hooknum]);#endif /** * synchronize_net - Synchronize with packet receive processing * * Wait for packets currently being received to be done. * Does not block later packets from starting. */ synchronize_net();}
注销一个 Netfilter hook 其实就是把我们之前注册插入的 struct nf_hook_ops 指针对象从链表中删除,从代码可以看出,它并没有销毁这个对象。 在后面还有个 synchronize_net(); 这个函数可能会引起睡眠,其目的是等待处理完数据包接收过程。
3.3 示例
#include <linux/module.h>#include <linux/kernel.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv4.h>MODULE_LICENSE("Dual BSD/GPL");static struct nf_hook_ops nfho;//钩子函数,按照各自的需求实现unsigned int hook_func(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)){ return NF_DROP;//丢弃所有的数据包}static int __init hook_init(void){ nfho.hook = hook_func;//关联对应处理函数 nfho.hooknum = NF_INET_PRE_ROUTING;//2.6.22及以后的内核中,内核态编程需要使用这个 nfho.pf = PF_INET;//ipv4,所以用这个 nfho.priority = NF_IP_PRI_FIRST;//优先级,第一顺位,首先执行我们定义的函数 nf_register_hook(&nfho);//注册 return 0;}static void __exit hook_exit(void){ nf_unregister_hook(&nfho);//注销}module_init(hook_init);module_exit(hook_exit);
上面的例子就是一个简单的netfilter的hook函数的实现过程,实例化一个nf_hook_ops的结构体对象,把对象插入到指定位置,执行相应的回调函数,实际应用中可以替换其中的回调函数实现相应的功能。
- netfilter框架
- netfilter框架
- Netfilter框架
- Netfilter框架
- Netfilter 框架
- 9.1.1 netfilter框架
- netfilter框架分析
- Netfilter---框架的设计
- netfilter框架分析
- netfilter/Iptables框架
- netfilter框架分析
- netfilter框架分析
- Linux中Netfilter框架
- Linux NetFilter 解析(一)--Netfilter 框架
- Netfilter
- netfilter
- netfilter
- netfilter
- java---Set
- Paper Reading:Spatial Transformer Networks(with code explanation)
- H5测试点总结分析
- Initialization-on-demand holder idiom 单例
- 数据结构——红黑树
- Netfilter 框架
- maven引用maven项目遇到的问题
- Mysql 批量修改字段存储过程
- C void*
- Hdu 6215 Brute Force Sorting【链表】
- mysql查询逗号分隔字段匹配的值,可且可或
- 【分布式3】——Chubby与Paxos
- 那么试着分析下对应的VO类是否存在问题
- 【区块链】Truffle 部署和测试