内核版本:Linux 2.6.30.9
PF_RING版本:4.1.0
最近看了一个PF_RING的实现,看了个大概,发上来大家讨论讨论,共同学习。
一、什么是PF_RING
PF_RING是一个第三方的内核数据包捕获接口,类似于libpcap,它的官方网址是: http://www.ntop.org/PF_RING.html
二、为什么需要PF_RING
一切为了效率,按照其官方网站上的测试数据,在Linux平台之上,其效率至少高于libpcap 50% - 60%,甚至是一倍。更好的是,PF_RING提供了一个修改版本的libpcap,使之建立在PF_RING接口之上。这样,原来使用libpcap的程序,就可以自然过渡了。
三、声明
1、这不是“零拷贝”,研究“零拷贝”的估计要失望,如果继续看下去的话;
2、这不是包截获接口,如果需要拦截、修改内核数据包,请转向Netfilter;
3、本文只分析了PF_RING最基础的部份。关于DNA、TNAPI,BPF等内容不包含在内。
四、源码的获取
svn co https://svn.ntop.org/svn/ntop/trunk/PF_RING/
最近好像全流行svn了。
五、编译和使用
接口分为两部份,一个是内核模块,一个是用户态的库
cd my_pf_ring_goes_here
cd kernel
make
sudo insmod ./pf_ring.ko
cd ../userland
make
在源码目录中,关于用户态的库有使用的现成的例子,很容易依葫芦画瓢。后文也会提到用户态库的实现的简单分析,可以两相比照,很容易上手。而且源码目录中有一个PDF文档,有详细的API介绍,建议使用前阅读。
六、实现分析初步
1、核心思路
A、在内核队列层注册Hook,获取数据帧。
B、在内核创建一个环形队列(这也是叫RING的原因),用于存储数据,并使用mmap映射到用户空间。这样,避免用户态的系统调用,也是提高性能的关键所在。
C、创建了一个新的套接字类型PF_RING,用户态通过它与内核通信。
2、模块初始化
模块源码只有一个文件,在目录树kernel/pf_ring.c,嗯,还有一个头文件,在kernel/linux下
ring_proto的定义为
初始化四个链表,它们的作用,后文会分析到:
device_ring_list是一个指针数组,它的每一个元素对应一个网络设备,后文也会分析它的使用:
ring_family_ops定义为
这样,当用户空间创建PF_RING时,例如,
ring_create将会被调用
PF_RING一共有三种工作模式:
第一种最简单,这里仅分析第一种
num_slots为槽位总数,系统采用数组来实现双向环形队列,它也就代表数组的最大元素。
版本信息:不用多说了。不过我的版本在2.6.18及以下都没有编译成功,后来使用2.6.30.9搞定之。
enable_tx_capture:是否启用发送时的数据捕获,对于大多数应用而言,都是在接收时处理。
enable_ip_defrag:为用户提供一个接口,是否在捕获最重组IP分片。
register_device_handler注册了一个协议,用于数据包的获取:
2、创建套接字
Linux的套按字的内核接口,使用了两个重要的数据结构:
struct socket和struct sock,这本来并没有什么,不过令人常常迷惑的是,前者常常被缩写为sock,即:
struct socket *sock;
这样,“sock”就容易造成混淆了。还好,后者常常被缩写为sk……
我这里写sock指前者,sk指后者,如果不小心写混了,请参考上下文区分 。
关于这两个结构的含义,使用等等,可以参考相关资料以获取详细信息,如《Linux情景分析》。我的个人网站 www.skynet.org.cn 上也分析了Linux socket的实现。可以参考。这里关于socket的进一步信息,就不详细分析了。
这里的创建套接字,内核已经在系统调用过程中,准备好了sock,主要就是分析sk,并为sk指定一系列的操作函数,如bind、mmap、poll等等。
如前所述,套接字的创建,是通过调用ring_create函数来完成的:
在模块初始化中,初始化过四个链表。其中一个是ring_table,ring_insert将刚刚创建的套接字插入其中。其封装引进了一个struct ring_element 结构:
3 、分配队列空间
用户态在创建了套接字后,接下来就调用bind函数,绑定套接字,而PF_RING实际做的就是为RING分配相应的空间。也就是说,一个套接字,都有一个与之对应的RING。这样,有多个进程同时使用PF_RING,也没有问题:
因为前一步创建套接字时,为sk指定了其ops:
这样,当bing系统调用触发时,ring_bind函数将被调用:
在做了一些必要的语法检查后,函数转向packet_ring_bind:
这个函数中,最重要的三点:
1、整个空间的详细构成,作者画了一个简单的草图,清晰明了。
2、如果取得某个槽位。
3、device_ring_list链表的使用。
一些作者有详细注释的地方,我就不再重重了。
这一步进行完了后,就有一块内存了(系统将其看成一个数组),用来存储捕获的数据帧。接下来要做的事情。就是把它映射到用户态。
4、mmap操作
用户态的接下来调用:
进行内存映射。
同样地,内核调用相应的ring_mmap进行处理。
Ring选项结构通过ring_sk宏与sk 建立关联
pfr->ring_memory 即为分配的环形队列空间。所以,要mmap操作,实际上就是调用remap_pfn_range函数把pfr->ring_memory 映射到用户空间即可。这个函数的原型为:
关于remap_pfn_range函数的进一步说明,可以参考LDD3,上面有详细说明和现成的例子。
实际上的内存映射工作,是由do_memory_mmap来完成的,这个函数实际上基本就是remap_pfn_range的包裹函数。
不过因为系统支持dna等技术,相应的mode参数有些变化,这里只分析了最基本的方法:mode == 0
嗯,跳过了太多的细节,不过其mmap最核心的东东已经呈现出来。
如果要共享内核与用户空间内存,这倒是个现成的可借鉴的例子。
5、数据包的入队操作
做到这一步,准备工作基本上就完成了。因为PF_RING在初始化中,注册了prot_hook。其func指针指向packet_rcv函数:
当数据报文进入Linux网络协议栈队列时,netif_receive_skb会遍历这些注册的Hook:
相应的Hook函数得到调用:
packet_rcv随之执行环形队列的入队操作:
static int skb_ring_handler(struct sk_buff *skb, //要捕获的数据包
u_char recv_packet, //数据流方向,>0表示是进入(接收)方向
u_char real_skb /* 1=real skb, 0=faked skb */ ,
short channel_id) //通道ID
{
struct sock *skElement;
int rc = 0, is_ip_pkt;
struct list_head *ptr;
struct pfring_pkthdr hdr;
int displ;
struct sk_buff *skk = NULL;
struct sk_buff *orig_skb = skb;
#ifdef PROFILING
uint64_t rdt = _rdtsc(), rdt1, rdt2;
#endif
//skb合法检查,包括数据流的方向
if((!skb) /* Invalid skb */
||((!enable_tx_capture) && (!recv_packet))) {
/*
An outgoing packet is about to be sent out
but we decided not to handle transmitted
packets.
*/
return(0);
}
#if defined(RING_DEBUG)
if(1) {
struct timeval tv;
skb_get_timestamp(skb, &tv);
printk
("[PF_RING] skb_ring_handler() [skb=%p][%u.%u][len=%d][dev=%s][csum=%u]\n",
skb, (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec,
skb->len,
skb->dev->name == NULL ? "<NULL>" : skb->dev->name,
skb->csum);
}
#endif
//如果通道ID未指定,根据进入的报文设备索引,设定之
#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21))
if(channel_id == RING_ANY_CHANNEL /* Unknown channel */ )
channel_id = skb->iif; /* Might have been set by the driver */
#endif
#if defined (RING_DEBUG)
/* printk("[PF_RING] channel_id=%d\n", channel_id); */
#endif
#ifdef PROFILING
rdt1 = _rdtsc();
#endif
if(recv_packet) {
/* Hack for identifying a packet received by the e1000 */
if(real_skb)
displ = SKB_DISPLACEMENT;
else
displ = 0; /* Received by the e1000 wrapper */
} else
displ = 0;
//解析数据报文,并判断是否为IP报文
is_ip_pkt = parse_pkt(skb, displ, &hdr);
//分片处理,是一个可选的功能项,事实上,对大多数包捕获工具而言,它们好像都不使用底层库来完成这一功能
/* (de)Fragmentation < fusco@ntop.org > */
if(enable_ip_defrag
&& real_skb && is_ip_pkt && recv_packet && (ring_table_size > 0)) {
} else {
#if defined (RING_DEBUG)
printk("[PF_RING] Do not seems to be a fragmented ip_pkt[iphdr=%p]\n",
iphdr);
#endif
}
}
}
//按惯例,在报文的捕获首部信息中记录捕获的时间戳
/* BD - API changed for time keeping */
#if(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14))
if(skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
hdr.ts.tv_sec = skb->stamp.tv_sec, hdr.ts.tv_usec = skb->stamp.tv_usec;
#elif(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22))
if(skb->tstamp.off_sec == 0)
__net_timestamp(skb);
hdr.ts.tv_sec = skb->tstamp.off_sec, hdr.ts.tv_usec =
skb->tstamp.off_usec;
#else /* 2.6.22 and above */
if(skb->tstamp.tv64 == 0)
__net_timestamp(skb);
hdr.ts = ktime_to_timeval(skb->tstamp);
#endif
//除了时间,还有长度,熟悉libpcap的话,这些操作应该很眼熟
hdr.len = hdr.caplen = skb->len + displ;
/* Avoid the ring to be manipulated while playing with it */
read_lock_bh(&ring_mgmt_lock);
/* 前面在创建sk时,已经看过ring_insert的入队操作了,现在要检查它的成员
* 它们的关系是,通过ring_table的成员,获取到element,它里面封装了sk,
*通过ring_sk宏,就可以得到ring_opt指针
*/
list_for_each(ptr, &ring_table) {
struct ring_opt *pfr;
struct ring_element *entry;
entry = list_entry(ptr, struct ring_element, list);
skElement = entry->sk;
pfr = ring_sk(skElement);
//看来要加入社团,条件还是满多的,pfr不能为空,未指定集群cluster_id,槽位不能为空,方向要正确,绑定的网络设备
//得对上号
//另一种可能就是对bonding的支持,如果设备是从属设备,则应校验其主设备
if((pfr != NULL)
&& (pfr->cluster_id == 0 /* No cluster */ )
&& (pfr->ring_slots != NULL)
&& is_valid_skb_direction(pfr->direction, recv_packet)
&& ((pfr->ring_netdev == skb->dev)
|| ((skb->dev->flags & IFF_SLAVE)
&& (pfr->ring_netdev == skb->dev->master)))) {
/* We've found the ring where the packet can be stored */
/* 从新计算捕获帧长度,是因为可能因为巨型帧的出现——超过了桶能容纳的长度 */
int old_caplen = hdr.caplen; /* Keep old lenght */
hdr.caplen = min(hdr.caplen, pfr->bucket_len);
/* 入队操作 */
add_skb_to_ring(skb, pfr, &hdr, is_ip_pkt, displ, channel_id);
hdr.caplen = old_caplen;
rc = 1; /* Ring found: we've done our job */
}
}
/* [2] Check socket clusters */
list_for_each(ptr, &ring_cluster_list) {
ring_cluster_element *cluster_ptr;
struct ring_opt *pfr;
cluster_ptr = list_entry(ptr, ring_cluster_element, list);
if(cluster_ptr->cluster.num_cluster_elements > 0) {
u_int skb_hash = hash_pkt_cluster(cluster_ptr, &hdr);
skElement = cluster_ptr->cluster.sk[skb_hash];
if(skElement != NULL) {
pfr = ring_sk(skElement);
if((pfr != NULL)
&& (pfr->ring_slots != NULL)
&& ((pfr->ring_netdev == skb->dev)
|| ((skb->dev->flags & IFF_SLAVE)
&& (pfr->ring_netdev ==
skb->dev->master)))
&& is_valid_skb_direction(pfr->direction, recv_packet)
) {
/* We've found the ring where the packet can be stored */
add_skb_to_ring(skb, pfr, &hdr,
is_ip_pkt, displ,
channel_id);
rc = 1; /* Ring found: we've done our job */
}
}
}
}
read_unlock_bh(&ring_mgmt_lock);
#ifdef PROFILING
rdt1 = _rdtsc() - rdt1;
#endif
#ifdef PROFILING
rdt2 = _rdtsc();
#endif
/* Fragment handling */
if(skk != NULL)
kfree_skb(skk);
if(rc == 1) {
if(transparent_mode != driver2pf_ring_non_transparent) {
rc = 0;
} else {
if(recv_packet && real_skb) {
#if defined(RING_DEBUG)
printk("[PF_RING] kfree_skb()\n");
#endif
kfree_skb(orig_skb);
}
}
}
#ifdef PROFILING
rdt2 = _rdtsc() - rdt2;
rdt = _rdtsc() - rdt;
#if defined(RING_DEBUG)
printk
("[PF_RING] # cycles: %d [lock costed %d %d%%][free costed %d %d%%]\n",
(int)rdt, rdt - rdt1,
(int)((float)((rdt - rdt1) * 100) / (float)rdt), rdt2,
(int)((float)(rdt2 * 100) / (float)rdt));
#endif
#endif
//printk("[PF_RING] Returned %d\n", rc);
return(rc); /* 0 = packet not handled */
}
上面跳过了对cluster(集群)的分析,PF_RING允许同时对多个接口捕获报文,而并不是一个。这就是集群。看一下它用户态的注释就一目了然了:
进一步的入队操作,是通过add_skb_to_ring来完成的:
槽位的计算:
PF_RING版本:4.1.0
最近看了一个PF_RING的实现,看了个大概,发上来大家讨论讨论,共同学习。
一、什么是PF_RING
PF_RING是一个第三方的内核数据包捕获接口,类似于libpcap,它的官方网址是: http://www.ntop.org/PF_RING.html
二、为什么需要PF_RING
一切为了效率,按照其官方网站上的测试数据,在Linux平台之上,其效率至少高于libpcap 50% - 60%,甚至是一倍。更好的是,PF_RING提供了一个修改版本的libpcap,使之建立在PF_RING接口之上。这样,原来使用libpcap的程序,就可以自然过渡了。
三、声明
1、这不是“零拷贝”,研究“零拷贝”的估计要失望,如果继续看下去的话;
2、这不是包截获接口,如果需要拦截、修改内核数据包,请转向Netfilter;
3、本文只分析了PF_RING最基础的部份。关于DNA、TNAPI,BPF等内容不包含在内。
四、源码的获取
svn co https://svn.ntop.org/svn/ntop/trunk/PF_RING/
最近好像全流行svn了。
五、编译和使用
接口分为两部份,一个是内核模块,一个是用户态的库
cd my_pf_ring_goes_here
cd kernel
make
sudo insmod ./pf_ring.ko
cd ../userland
make
在源码目录中,关于用户态的库有使用的现成的例子,很容易依葫芦画瓢。后文也会提到用户态库的实现的简单分析,可以两相比照,很容易上手。而且源码目录中有一个PDF文档,有详细的API介绍,建议使用前阅读。
六、实现分析初步
1、核心思路
A、在内核队列层注册Hook,获取数据帧。
B、在内核创建一个环形队列(这也是叫RING的原因),用于存储数据,并使用mmap映射到用户空间。这样,避免用户态的系统调用,也是提高性能的关键所在。
C、创建了一个新的套接字类型PF_RING,用户态通过它与内核通信。
2、模块初始化
模块源码只有一个文件,在目录树kernel/pf_ring.c,嗯,还有一个头文件,在kernel/linux下
ring_proto的定义为
初始化四个链表,它们的作用,后文会分析到:
device_ring_list是一个指针数组,它的每一个元素对应一个网络设备,后文也会分析它的使用:
ring_family_ops定义为
这样,当用户空间创建PF_RING时,例如,
ring_create将会被调用
PF_RING一共有三种工作模式:
第一种最简单,这里仅分析第一种
num_slots为槽位总数,系统采用数组来实现双向环形队列,它也就代表数组的最大元素。
版本信息:不用多说了。不过我的版本在2.6.18及以下都没有编译成功,后来使用2.6.30.9搞定之。
enable_tx_capture:是否启用发送时的数据捕获,对于大多数应用而言,都是在接收时处理。
enable_ip_defrag:为用户提供一个接口,是否在捕获最重组IP分片。
register_device_handler注册了一个协议,用于数据包的获取:
2、创建套接字
Linux的套按字的内核接口,使用了两个重要的数据结构:
struct socket和struct sock,这本来并没有什么,不过令人常常迷惑的是,前者常常被缩写为sock,即:
struct socket *sock;
这样,“sock”就容易造成混淆了。还好,后者常常被缩写为sk……
我这里写sock指前者,sk指后者,如果不小心写混了,请参考上下文区分 。
关于这两个结构的含义,使用等等,可以参考相关资料以获取详细信息,如《Linux情景分析》。我的个人网站 www.skynet.org.cn 上也分析了Linux socket的实现。可以参考。这里关于socket的进一步信息,就不详细分析了。
这里的创建套接字,内核已经在系统调用过程中,准备好了sock,主要就是分析sk,并为sk指定一系列的操作函数,如bind、mmap、poll等等。
如前所述,套接字的创建,是通过调用ring_create函数来完成的:
在模块初始化中,初始化过四个链表。其中一个是ring_table,ring_insert将刚刚创建的套接字插入其中。其封装引进了一个struct ring_element 结构:
3 、分配队列空间
用户态在创建了套接字后,接下来就调用bind函数,绑定套接字,而PF_RING实际做的就是为RING分配相应的空间。也就是说,一个套接字,都有一个与之对应的RING。这样,有多个进程同时使用PF_RING,也没有问题:
因为前一步创建套接字时,为sk指定了其ops:
这样,当bing系统调用触发时,ring_bind函数将被调用:
在做了一些必要的语法检查后,函数转向packet_ring_bind:
这个函数中,最重要的三点:
1、整个空间的详细构成,作者画了一个简单的草图,清晰明了。
2、如果取得某个槽位。
3、device_ring_list链表的使用。
一些作者有详细注释的地方,我就不再重重了。
这一步进行完了后,就有一块内存了(系统将其看成一个数组),用来存储捕获的数据帧。接下来要做的事情。就是把它映射到用户态。
4、mmap操作
用户态的接下来调用:
进行内存映射。
同样地,内核调用相应的ring_mmap进行处理。
Ring选项结构通过ring_sk宏与sk 建立关联
pfr->ring_memory 即为分配的环形队列空间。所以,要mmap操作,实际上就是调用remap_pfn_range函数把pfr->ring_memory 映射到用户空间即可。这个函数的原型为:
关于remap_pfn_range函数的进一步说明,可以参考LDD3,上面有详细说明和现成的例子。
实际上的内存映射工作,是由do_memory_mmap来完成的,这个函数实际上基本就是remap_pfn_range的包裹函数。
不过因为系统支持dna等技术,相应的mode参数有些变化,这里只分析了最基本的方法:mode == 0
嗯,跳过了太多的细节,不过其mmap最核心的东东已经呈现出来。
如果要共享内核与用户空间内存,这倒是个现成的可借鉴的例子。
5、数据包的入队操作
做到这一步,准备工作基本上就完成了。因为PF_RING在初始化中,注册了prot_hook。其func指针指向packet_rcv函数:
当数据报文进入Linux网络协议栈队列时,netif_receive_skb会遍历这些注册的Hook:
相应的Hook函数得到调用:
packet_rcv随之执行环形队列的入队操作:
static int skb_ring_handler(struct sk_buff *skb, //要捕获的数据包
u_char recv_packet, //数据流方向,>0表示是进入(接收)方向
u_char real_skb /* 1=real skb, 0=faked skb */ ,
short channel_id) //通道ID
{
struct sock *skElement;
int rc = 0, is_ip_pkt;
struct list_head *ptr;
struct pfring_pkthdr hdr;
int displ;
struct sk_buff *skk = NULL;
struct sk_buff *orig_skb = skb;
#ifdef PROFILING
uint64_t rdt = _rdtsc(), rdt1, rdt2;
#endif
//skb合法检查,包括数据流的方向
if((!skb) /* Invalid skb */
||((!enable_tx_capture) && (!recv_packet))) {
/*
An outgoing packet is about to be sent out
but we decided not to handle transmitted
packets.
*/
return(0);
}
#if defined(RING_DEBUG)
if(1) {
struct timeval tv;
skb_get_timestamp(skb, &tv);
printk
("[PF_RING] skb_ring_handler() [skb=%p][%u.%u][len=%d][dev=%s][csum=%u]\n",
skb, (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec,
skb->len,
skb->dev->name == NULL ? "<NULL>" : skb->dev->name,
skb->csum);
}
#endif
//如果通道ID未指定,根据进入的报文设备索引,设定之
#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21))
if(channel_id == RING_ANY_CHANNEL /* Unknown channel */ )
channel_id = skb->iif; /* Might have been set by the driver */
#endif
#if defined (RING_DEBUG)
/* printk("[PF_RING] channel_id=%d\n", channel_id); */
#endif
#ifdef PROFILING
rdt1 = _rdtsc();
#endif
if(recv_packet) {
/* Hack for identifying a packet received by the e1000 */
if(real_skb)
displ = SKB_DISPLACEMENT;
else
displ = 0; /* Received by the e1000 wrapper */
} else
displ = 0;
//解析数据报文,并判断是否为IP报文
is_ip_pkt = parse_pkt(skb, displ, &hdr);
//分片处理,是一个可选的功能项,事实上,对大多数包捕获工具而言,它们好像都不使用底层库来完成这一功能
/* (de)Fragmentation < fusco@ntop.org > */
if(enable_ip_defrag
&& real_skb && is_ip_pkt && recv_packet && (ring_table_size > 0)) {
} else {
#if defined (RING_DEBUG)
printk("[PF_RING] Do not seems to be a fragmented ip_pkt[iphdr=%p]\n",
iphdr);
#endif
}
}
}
//按惯例,在报文的捕获首部信息中记录捕获的时间戳
/* BD - API changed for time keeping */
#if(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14))
if(skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
hdr.ts.tv_sec = skb->stamp.tv_sec, hdr.ts.tv_usec = skb->stamp.tv_usec;
#elif(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22))
if(skb->tstamp.off_sec == 0)
__net_timestamp(skb);
hdr.ts.tv_sec = skb->tstamp.off_sec, hdr.ts.tv_usec =
skb->tstamp.off_usec;
#else /* 2.6.22 and above */
if(skb->tstamp.tv64 == 0)
__net_timestamp(skb);
hdr.ts = ktime_to_timeval(skb->tstamp);
#endif
//除了时间,还有长度,熟悉libpcap的话,这些操作应该很眼熟
hdr.len = hdr.caplen = skb->len + displ;
/* Avoid the ring to be manipulated while playing with it */
read_lock_bh(&ring_mgmt_lock);
/* 前面在创建sk时,已经看过ring_insert的入队操作了,现在要检查它的成员
* 它们的关系是,通过ring_table的成员,获取到element,它里面封装了sk,
*通过ring_sk宏,就可以得到ring_opt指针
*/
list_for_each(ptr, &ring_table) {
struct ring_opt *pfr;
struct ring_element *entry;
entry = list_entry(ptr, struct ring_element, list);
skElement = entry->sk;
pfr = ring_sk(skElement);
//看来要加入社团,条件还是满多的,pfr不能为空,未指定集群cluster_id,槽位不能为空,方向要正确,绑定的网络设备
//得对上号
//另一种可能就是对bonding的支持,如果设备是从属设备,则应校验其主设备
if((pfr != NULL)
&& (pfr->cluster_id == 0 /* No cluster */ )
&& (pfr->ring_slots != NULL)
&& is_valid_skb_direction(pfr->direction, recv_packet)
&& ((pfr->ring_netdev == skb->dev)
|| ((skb->dev->flags & IFF_SLAVE)
&& (pfr->ring_netdev == skb->dev->master)))) {
/* We've found the ring where the packet can be stored */
/* 从新计算捕获帧长度,是因为可能因为巨型帧的出现——超过了桶能容纳的长度 */
int old_caplen = hdr.caplen; /* Keep old lenght */
hdr.caplen = min(hdr.caplen, pfr->bucket_len);
/* 入队操作 */
add_skb_to_ring(skb, pfr, &hdr, is_ip_pkt, displ, channel_id);
hdr.caplen = old_caplen;
rc = 1; /* Ring found: we've done our job */
}
}
/* [2] Check socket clusters */
list_for_each(ptr, &ring_cluster_list) {
ring_cluster_element *cluster_ptr;
struct ring_opt *pfr;
cluster_ptr = list_entry(ptr, ring_cluster_element, list);
if(cluster_ptr->cluster.num_cluster_elements > 0) {
u_int skb_hash = hash_pkt_cluster(cluster_ptr, &hdr);
skElement = cluster_ptr->cluster.sk[skb_hash];
if(skElement != NULL) {
pfr = ring_sk(skElement);
if((pfr != NULL)
&& (pfr->ring_slots != NULL)
&& ((pfr->ring_netdev == skb->dev)
|| ((skb->dev->flags & IFF_SLAVE)
&& (pfr->ring_netdev ==
skb->dev->master)))
&& is_valid_skb_direction(pfr->direction, recv_packet)
) {
/* We've found the ring where the packet can be stored */
add_skb_to_ring(skb, pfr, &hdr,
is_ip_pkt, displ,
channel_id);
rc = 1; /* Ring found: we've done our job */
}
}
}
}
read_unlock_bh(&ring_mgmt_lock);
#ifdef PROFILING
rdt1 = _rdtsc() - rdt1;
#endif
#ifdef PROFILING
rdt2 = _rdtsc();
#endif
/* Fragment handling */
if(skk != NULL)
kfree_skb(skk);
if(rc == 1) {
if(transparent_mode != driver2pf_ring_non_transparent) {
rc = 0;
} else {
if(recv_packet && real_skb) {
#if defined(RING_DEBUG)
printk("[PF_RING] kfree_skb()\n");
#endif
kfree_skb(orig_skb);
}
}
}
#ifdef PROFILING
rdt2 = _rdtsc() - rdt2;
rdt = _rdtsc() - rdt;
#if defined(RING_DEBUG)
printk
("[PF_RING] # cycles: %d [lock costed %d %d%%][free costed %d %d%%]\n",
(int)rdt, rdt - rdt1,
(int)((float)((rdt - rdt1) * 100) / (float)rdt), rdt2,
(int)((float)(rdt2 * 100) / (float)rdt));
#endif
#endif
//printk("[PF_RING] Returned %d\n", rc);
return(rc); /* 0 = packet not handled */
}
上面跳过了对cluster(集群)的分析,PF_RING允许同时对多个接口捕获报文,而并不是一个。这就是集群。看一下它用户态的注释就一目了然了:
进一步的入队操作,是通过add_skb_to_ring来完成的:
槽位的计算: