版本说明
Linux版本: 3.10.103
网卡驱动: ixgbe
位置
框架图
ip_rcv
调用NF_HOOK
开始进入netfilter框架对数据进行处理
NF_HOOK(pf, hook, skb, indev, outdev, okfn)
- pf :协议族名,Netfilter架构可以用于IP层之外,ipv4、arp、bridge、ipv6等。
- hook :HOOK点的名字,对于IP层,就是取上面的五个值,其他的大同小异。
- skb :不解释。
- indev :数据包进来的设备,以struct net_device结构表示。
- outdev:数据包出去的设备,以struct net_device结构表示。
- okfn :是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程。
Hook点
PRE_ROUTING
:在进入网络层之初就会经过。LOCAL_IN
:数据进入本地时经过。FORWARD
:转发数据经过。LOCAL_OUT
:本地发送数据时经过。POST_ROUTING
:数据出设备时调用
比如我们做数据统计的话:
- 如果需要统计流入和流出流量的话,只需要在1和5两个点统计skb即可。
- 如果我们要分别统计本地流量和转发,需要在2、3、4三个点统计。
Hook点的选取和我们的操作息息相关,主要看我们改变网络特性的情况,比如我们要改变连接属性的话,需要考虑conntrack的影响,以IP做策略匹配的时候,需要考虑NAT。
优先级
协议族的引入是为了支持更多的协议
优先级的引入是因为同一个hook点可以挂很多钩子,不同的Hook函数用不同的优先级表示调用的先后顺序,数值越小的优先被调用,最大值是0x7fffffff,最小值是-0x80000000
实现
netfilter定义struct list_head nf_hooks[PROTO][HOOKS]
,建立一个二维数组的链表,分别是协议族和hook点,然后以优先级为基准进行链表的创建和排序。
我们在挂Hook函数的时候会指定协议、hook点、优先级和执行函数,直接将这些信息加入相应的链表中,协议栈的数据包进入的时候会根据相应的协议和当前执行到的hook点去遍历链表,执行相应的函数。
比如,我们设置协议为ipv4, hook点为pre_routing,优先级-100,函数为dnat
for{
a = get_list(nf_hooks[ipv4][prerouting]);
a.func(skb);
}
当遍历到优先级为-100的时候,就会执行dnat函数。
挂钩子
Netfilter处于kernel层,推荐直接编写module,比直接修改内核代码方便调试和编译
static struct nf_hook_ops my_nfhook[] = {
{
.hook=my_hook_func, //Hook函数
.owner=THIS_MODULE,
.pf=PF_INET, //协议族
.hooknum=NF_INET_LOCAL_IN, //Hook点
.priority=NF_IP_PRI_FIRST, //优先级
},//可以写多个
};
挂Hook的接口nf_register_hooks(my_nfhook, ARRAY_SIZE(my_nfhook))
取消Hook的接口nf_unregister_hooks(my_nfhook, ARRAY_SIZE(my_nfhook))
unsigned int my_hook_func(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff*))
{
//此处skb任你处置
return NF_ACCEPT;
}
需要注意的是比较老的内核版本的函数参数skb是二维指针**skb
, 新的是*skb
,老内核我遇到过的是2.6.18之前的,2.6.32之后是新的
返回值
Netfilter将skb交给Hook函数进行处理,处理的结果如何,需要通过返回值的形式告知协议栈:
NF_ACCEPT
继续正常传输数据报。这个返回值告诉 Netfilter:到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段。NF_DROP
丢弃该数据报,不再传输。NF_STOLEN
模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是回调函数从Netfilter 获取了该数据包的所有权。NF_QUEUE
对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)。NF_REPEAT
再次调用该回调函数,应当谨慎使用这个值,以免造成死循环。
注意:在此阶段数据包的存储空间还是位于DMA的空间中,尽量不要做特别耗时的操作,以免影响网卡收取数据。