PF_RING实现分析
http://bbs.chinaunix.net/thread-1943951-1-1.html
版权所有,转载请注明出处
独孤九贱
内核版本: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下
- static int __init ring_init(void)
- {
- int i, rc;
- printk("[PF_RING] Welcome to PF_RING %s ($Revision: 4012 $)\n"
- "(C) 2004-09 L.Deri <[email][email protected][/email]>\n", RING_VERSION);
-
- //注册名为PF_RING的新协议
- if((rc = proto_register(&ring_proto, 0)) != 0)
- return(rc);
ring_proto的定义为
- #if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,11))
- static struct proto ring_proto = {
- .name = "PF_RING",
- .owner = THIS_MODULE,
- .obj_size = sizeof(struct ring_sock),
- };
- #endif
初始化四个链表,它们的作用,后文会分析到:
- //初始化四个链表
- INIT_LIST_HEAD(&ring_table); /* List of all ring sockets. */
- INIT_LIST_HEAD(&ring_cluster_list); /* List of all clusters */
- INIT_LIST_HEAD(&ring_aware_device_list); /* List of all devices on which PF_RING has been registered */
- INIT_LIST_HEAD(&ring_dna_devices_list); /* List of all dna (direct nic access) devices */
device_ring_list是一个指针数组,它的每一个元素对应一个网络设备,后文也会分析它的使用:
- /*
- For each device, pf_ring keeps a list of the number of
- available ring socket slots. So that a caller knows in advance whether
- there are slots available (for rings bound to such device)
- that can potentially host the packet
- */
- for (i = 0; i < MAX_NUM_DEVICES; i++)
- INIT_LIST_HEAD(&device_ring_list[i]);
- //为新协议注册sock
- sock_register(&ring_family_ops);
ring_family_ops定义为
- static struct net_proto_family ring_family_ops = {
- .family = PF_RING,
- .create = ring_create,
- .owner = THIS_MODULE,
- };
这样,当用户空间创建PF_RING时,例如,
- fd = socket(PF_RING, SOCK_RAW, htons(ETH_P_ALL));
ring_create将会被调用
- //注册通知链表
- register_netdevice_notifier(&ring_netdev_notifier);
- /* 工作模式语法检查 */
- if(transparent_mode > driver2pf_ring_non_transparent)
- transparent_mode = standard_linux_path;
PF_RING一共有三种工作模式:
- typedef enum {
- standard_linux_path = 0, /* Business as usual */
- driver2pf_ring_transparent = 1, /* Packets are still delivered to the kernel */
- driver2pf_ring_non_transparent = 2 /* Packets not delivered to the kernel */
- } direct2pf_ring;
第一种最简单,这里仅分析第一种
- //输出工作信息参数
- printk("[PF_RING] Ring slots %d\n", num_slots);
- printk("[PF_RING] Slot version %d\n",
- RING_FLOWSLOT_VERSION);
- printk("[PF_RING] Capture TX %s\n",
- enable_tx_capture ? "Yes [RX+TX]" : "No [RX only]");
- printk("[PF_RING] Transparent Mode %d\n",
- transparent_mode);
- printk("[PF_RING] IP Defragment %s\n",
- enable_ip_defrag ? "Yes" : "No");
- printk("[PF_RING] Initialized correctly\n");
num_slots为槽位总数,系统采用数组来实现双向环形队列,它也就代表数组的最大元素。
版本信息:不用多说了。不过我的版本在2.6.18及以下都没有编译成功,后来使用2.6.30.9搞定之。
enable_tx_capture:是否启用发送时的数据捕获,对于大多数应用而言,都是在接收时处理。
enable_ip_defrag:为用户提供一个接口,是否在捕获最重组IP分片。
- //创建/proc目录
- ring_proc_init();
-
- //注册设备句柄
- register_device_handler();
- pfring_enabled = 1; //工作标志
- return 0;
- }
register_device_handler注册了一个协议,用于数据包的获取:
- /* Protocol hook */
- static struct packet_type prot_hook;
- void register_device_handler(void) {
- //只有在第一种模式下,才用这种方式接收数据
- if(transparent_mode != standard_linux_path) return;
- prot_hook.func = packet_rcv;
- prot_hook.type = htons(ETH_P_ALL);
- dev_add_pack(&prot_hook);
- }
- void register_device_handler(void) {
- if(transparent_mode != standard_linux_path) return;
- prot_hook.func = packet_rcv;
- prot_hook.type = htons(ETH_P_ALL);
- dev_add_pack(&prot_hook);
- }
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函数来完成的:
- static int ring_create(
- #if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
- struct net *net,
- #endif
- struct socket *sock, int protocol)
- {
- struct sock *sk;
- struct ring_opt *pfr;
- int err;
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_create()\n");
- #endif
- /* 权限验证 ? */
- if(!capable(CAP_NET_ADMIN))
- return -EPERM;
- //协议簇验证
- if(sock->type != SOCK_RAW)
- return -ESOCKTNOSUPPORT;
- //协议验证
- if(protocol != htons(ETH_P_ALL))
- return -EPROTONOSUPPORT;
- err = -ENOMEM;
- // 分配sk
- // options are.
- #if(LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,11))
- sk = sk_alloc(PF_RING, GFP_KERNEL, 1, NULL);
- #else
- #if(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24))
- // BD: API changed in 2.6.12, ref:
- // [url]http://svn.clkao.org/svnweb/linux/revision/?rev=28201[/url]
- sk = sk_alloc(PF_RING, GFP_ATOMIC, &ring_proto, 1);
- #else
- sk = sk_alloc(net, PF_INET, GFP_KERNEL, &ring_proto);
- #endif
- #endif
- //分配失败
- if(sk == NULL)
- goto out;
-
- //这里很重要,设定sock的ops,即对应用户态的bind、connect诸如此类操作的动作
- sock->ops = &ring_ops;
- //初始化sock结构(即sk)各成员,并设定与套接字socket(即sock)的关联
- sock_init_data(sock, sk);
- #if(LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,11))
- sk_set_owner(sk, THIS_MODULE);
- #endif
- err = -ENOMEM;
- #define ring_sk_datatype(__sk) ((struct ring_opt *)__sk)
- #define ring_sk(__sk) ((__sk)->sk_protinfo)
- //作者喜欢用小写的宏
- //这里分配一个struct ring_opt结构,这个结构比较重要,其记录了ring的选项信息。
- 在sk中,使用sk_protinfo成员指向之,这样就建立了sock->sk->ring_opt的关联。可以通过套接字很容易获取Ring的信息。
- ring_sk(sk) = ring_sk_datatype(kmalloc(sizeof(*pfr), GFP_KERNEL));
- //分配失败
- if(!(pfr = ring_sk(sk))) {
- sk_free(sk);
- goto out;
- }
- //初始化各成员
- memset(pfr, 0, sizeof(*pfr));
- //激活标志
- pfr->ring_active = 0; /* We activate as soon as somebody waits for packets */
- //通道ID
- pfr->channel_id = RING_ANY_CHANNEL;
- //RING的每个槽位的桶的大小,其用来存储捕获的数据帧,这个值,用户态也可以使用setsocketopt来调整
- pfr->bucket_len = DEFAULT_BUCKET_LEN;
- //过滤器hash桶
- pfr->handle_hash_rule = handle_filtering_hash_bucket;
- //初始化等待队列
- init_waitqueue_head(&pfr->ring_slots_waitqueue);
- //初始化RING的锁
- rwlock_init(&pfr->ring_index_lock);
- rwlock_init(&pfr->ring_rules_lock);
- //初始化使用计数器
- atomic_set(&pfr->num_ring_users, 0);
- INIT_LIST_HEAD(&pfr->rules);
- //设定协议簇
- sk->sk_family = PF_RING;
- //设定sk的destuct函数
- sk->sk_destruct = ring_sock_destruct;
- //sk入队
- ring_insert(sk);
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_create() - created\n");
- #endif
- return(0);
- out:
- return err;
在模块初始化中,初始化过四个链表。其中一个是ring_table,ring_insert将刚刚创建的套接字插入其中。其封装引进了一个struct ring_element 结构:
- /*
- * ring_insert()
- *
- * store the sk in a new element and add it
- * to the head of the list.
- */
- static inline void ring_insert(struct sock *sk)
- {
- struct ring_element *next;
- struct ring_opt *pfr;
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_insert()\n");
- #endif
- //分配element
- next = kmalloc(sizeof(struct ring_element), GFP_ATOMIC);
- if(next != NULL) {
- //记录sk
- next->sk = sk;
- write_lock_bh(&ring_mgmt_lock);
- //入队
- list_add(&next->list, &ring_table);
- write_unlock_bh(&ring_mgmt_lock);
- } else {
- if(net_ratelimit())
- printk("[PF_RING] net_ratelimit() failure\n");
- }
- //累计使用计数器
- ring_table_size++;
- //ring_proc_add(ring_sk(sk));
- //记录进程PID
- pfr = (struct ring_opt *)ring_sk(sk);
- pfr->ring_pid = current->pid;
- }
3 、分配队列空间
用户态在创建了套接字后,接下来就调用bind函数,绑定套接字,而PF_RING实际做的就是为RING分配相应的空间。也就是说,一个套接字,都有一个与之对应的RING。这样,有多个进程同时使用PF_RING,也没有问题:
- sa.sa_family = PF_RING;
- snprintf(sa.sa_data, sizeof(sa.sa_data), "%s", device_name);
- rc = bind(ring->fd, (struct sockaddr *)&sa, sizeof(sa));
因为前一步创建套接字时,为sk指定了其ops:
- static struct proto_ops ring_ops = {
- .family = PF_RING,
- .owner = THIS_MODULE,
- /* Operations that make no sense on ring sockets. */
- .connect = sock_no_connect,
- .socketpair = sock_no_socketpair,
- .accept = sock_no_accept,
- .getname = sock_no_getname,
- .listen = sock_no_listen,
- .shutdown = sock_no_shutdown,
- .sendpage = sock_no_sendpage,
- .sendmsg = sock_no_sendmsg,
- /* Now the operations that really occur. */
- .release = ring_release,
- .bind = ring_bind,
- .mmap = ring_mmap,
- .poll = ring_poll,
- .setsockopt = ring_setsockopt,
- .getsockopt = ring_getsockopt,
- .ioctl = ring_ioctl,
- .recvmsg = ring_recvmsg,
- };
这样,当bing系统调用触发时,ring_bind函数将被调用:
- * Bind to a device */
- static int ring_bind(struct socket *sock, struct sockaddr *sa, int addr_len)
- {
- struct sock *sk = sock->sk;
- struct net_device *dev = NULL;
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_bind() called\n");
- #endif
- /*
- * Check legality
- */
- if(addr_len != sizeof(struct sockaddr))
- return -EINVAL;
- if(sa->sa_family != PF_RING)
- return -EINVAL;
- if(sa->sa_data == NULL)
- return -EINVAL;
- /* Safety check: add trailing zero if missing */
- sa->sa_data[sizeof(sa->sa_data) - 1] = '\0';
- #if defined(RING_DEBUG)
- printk("[PF_RING] searching device %s\n", sa->sa_data);
- #endif
- if((dev = __dev_get_by_name(
- #if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24))
- &init_net,
- #endif
- sa->sa_data)) == NULL) {
- #if defined(RING_DEBUG)
- printk("[PF_RING] search failed\n");
- #endif
- return(-EINVAL);
- } else
- return(packet_ring_bind(sk, dev));
- }
在做了一些必要的语法检查后,函数转向packet_ring_bind:
- /*
- * We create a ring for this socket and bind it to the specified device
- */
- static int packet_ring_bind(struct sock *sk, struct net_device *dev)
- {
- u_int the_slot_len;
- u_int32_t tot_mem;
- struct ring_opt *pfr = ring_sk(sk);
- // struct page *page, *page_end;
- if(!dev)
- return(-1);
- #if defined(RING_DEBUG)
- printk("[PF_RING] packet_ring_bind(%s) called\n", dev->name);
- #endif
- /* **********************************************
- * *************************************
- * * *
- * * FlowSlotInfo *
- * * *
- * ************************************* <-+
- * * FlowSlot * |
- * ************************************* |
- * * FlowSlot * |
- * ************************************* +- num_slots
- * * FlowSlot * |
- * ************************************* |
- * * FlowSlot * |
- * ************************************* <-+
- *
- * ********************************************** */
- //计算每一个槽位所需的内存空间
- the_slot_len = sizeof(u_char) /* flowSlot.slot_state */
- #ifdef RING_MAGIC
- + sizeof(u_char)
- #endif
- + sizeof(struct pfring_pkthdr)
- + pfr->bucket_len /* flowSlot.bucket */ ;
- /*
- 对于槽位空间的计算,有意思的是
- typedef struct flowSlot {
- #ifdef RING_MAGIC
- u_char magic; /* It must alwasy be zero */
- #endif
- u_char slot_state; /* 0=empty, 1=full */
- u_char bucket; /* bucket[bucketLen] */
- } FlowSlot;
- 对照结构定义和上面的计算公式:
- 1、作者好像把magic和slog_state的顺序给搞反了,不过还好,它们都是u_char,对结果不影响
- 2、bucket,桶的大小,这个桶就是拿来装要捕获的数据包了,虽然它在结构中定义是一个成员,事实上,
- 它由两个部份组成,一个是包的首部信息,这个结构的定义同libpcap很接近。另一个才是包的空间。
- */
- //总共的环形队列内存所需空间,包含一个队列控制信息FlowSlotInfo和若干个(由变量num_slots决定)槽位空间
- tot_mem = sizeof(FlowSlotInfo) + num_slots * the_slot_len;
-
- //确保按整数页分配,mmap也要求这样
- if(tot_mem % PAGE_SIZE)
- tot_mem += PAGE_SIZE - (tot_mem % PAGE_SIZE);
- //分配内存空间
- pfr->ring_memory = rvmalloc(tot_mem);
- if(pfr->ring_memory != NULL) {
- printk("[PF_RING] successfully allocated %lu bytes at 0x%08lx\n",
- (unsigned long)tot_mem, (unsigned long)pfr->ring_memory);
- } else {
- printk("[PF_RING] ERROR: not enough memory for ring\n");
- return(-1);
- }
- // memset(pfr->ring_memory, 0, tot_mem); // rvmalloc does the memset already
- //初始化各成员
- //内存指定,因为分配的内存开始部份是sizeof(FlowSlotInfo),所以可以做这样的强制转换,很容易互相取值
- pfr->slots_info = (FlowSlotInfo *) pfr->ring_memory;
- //跳过控制信息,指向槽位指针.事实上,它就是一个一维数组了,可以计算出合适的索引值,取到数组(RING)中的任意槽位值
- pfr->ring_slots = (char *)(pfr->ring_memory + sizeof(FlowSlotInfo));
- //版本信息
- pfr->slots_info->version = RING_FLOWSLOT_VERSION;
- //登记单个槽的大小
- pfr->slots_info->slot_len = the_slot_len;
- //登记bucket大小,从前面特别注释的bucket的分配看,bucket_len这个大小并不代表bucket成员的实际大小——它不包含struct pfring_pkthdr
- pfr->slots_info->data_len = pfr->bucket_len;
- //登记实际分配到的槽位数量,这里不用num_slots,难道是怕rvmalloc偷吃?
- pfr->slots_info->tot_slots =
- (tot_mem - sizeof(FlowSlotInfo)) / the_slot_len;
- //登记实际分配的内存总数
- pfr->slots_info->tot_mem = tot_mem;
- //采样速率??
- pfr->slots_info->sample_rate = 1;
- printk("[PF_RING] allocated %d slots [slot_len=%d][tot_mem=%u]\n",
- pfr->slots_info->tot_slots, pfr->slots_info->slot_len,
- pfr->slots_info->tot_mem);
- #ifdef RING_MAGIC
- {
- int i;
- for (i = 0; i < pfr->slots_info->tot_slots; i++) {
- unsigned long idx = i * pfr->slots_info->slot_len;
- FlowSlot *slot = (FlowSlot *) & pfr->ring_slots[idx];
- slot->magic = RING_MAGIC_VALUE;
- slot->slot_state = 0;
- }
- }
- #endif
- //这些控制变量可以在环的入队操作中看到它们的作用
- pfr->sample_rate = 1; /* No sampling */
- pfr->insert_page_id = 1, pfr->insert_slot_id = 0;
- pfr->rules_default_accept_policy = 1, pfr->num_filtering_rules = 0;
- ring_proc_add(ring_sk(sk), dev);
- //记录与之相应的设备信息,例如,如果在eth0上打开了5 个PF_RING, bind
- //被调用5次,分配了5个环形队列空间。eth0上随之分配5个elem,它们指向与
- //之对应的ring,然后根据设备索引号民全部加入至了device_ring_list
- //当有数据报文从指定接口进入时,可以很容易地在device_ring_list中找到相应的设备
- //然后再遍历链表,再找到与之相应的ring
- if(dev->ifindex < MAX_NUM_DEVICES) {
- device_ring_list_element *elem;
- /* printk("[PF_RING] Adding ring to device index %d\n", dev->ifindex); */
- elem = kmalloc(sizeof(device_ring_list_element), GFP_ATOMIC);
- if(elem != NULL) {
- elem->the_ring = pfr;
- INIT_LIST_HEAD(&elem->list);
- write_lock(&ring_list_lock);
- list_add(&elem->list, &device_ring_list[dev->ifindex]);
- write_unlock(&ring_list_lock);
- /* printk("[PF_RING] Added ring to device index %d\n", dev->ifindex); */
- }
- }
- /*
- IMPORTANT
- Leave this statement here as last one. In fact when
- the ring_netdev != NULL the socket is ready to be used.
- */
- pfr->ring_netdev = dev;
- return(0);
- }
这个函数中,最重要的三点:
1、整个空间的详细构成,作者画了一个简单的草图,清晰明了。
2、如果取得某个槽位。
3、device_ring_list链表的使用。
一些作者有详细注释的地方,我就不再重重了。
这一步进行完了后,就有一块内存了(系统将其看成一个数组),用来存储捕获的数据帧。接下来要做的事情。就是把它映射到用户态。
4、mmap操作
用户态的接下来调用:
- ring->buffer = (char *)mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE,
- MAP_SHARED, ring->fd, 0);
进行内存映射。
同样地,内核调用相应的ring_mmap进行处理。
Ring选项结构通过ring_sk宏与sk 建立关联
- struct ring_opt *pfr = ring_sk(sk);
pfr->ring_memory 即为分配的环形队列空间。所以,要mmap操作,实际上就是调用remap_pfn_range函数把pfr->ring_memory 映射到用户空间即可。这个函数的原型为:
- /**
- * remap_pfn_range - remap kernel memory to userspace
- * @vma: user vma to map to
- * @addr: target user address to start at
- * @pfn: physical address of kernel memory
- * @size: size of map area
- * @prot: page protection flags for this mapping
- *
- * Note: this is only safe if the mm semaphore is held when called.
- */
- int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
- unsigned long pfn, unsigned long size, pgprot_t prot)
- {
关于remap_pfn_range函数的进一步说明,可以参考LDD3,上面有详细说明和现成的例子。
- static int ring_mmap(struct file *file,
- struct socket *sock, struct vm_area_struct *vma)
- {
- struct sock *sk = sock->sk;
- struct ring_opt *pfr = ring_sk(sk); //取得pfr指针,也就是相应取得环形队列的内存空间地址指针
- int rc;
- unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);
- if(size % PAGE_SIZE) {
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_mmap() failed: "
- "len is not multiple of PAGE_SIZE\n");
- #endif
- return(-EINVAL);
- }
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_mmap() called, size: %ld bytes\n", size);
- #endif
- if((pfr->dna_device == NULL) && (pfr->ring_memory == NULL)) {
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_mmap() failed: "
- "mapping area to an unbound socket\n");
- #endif
- return -EINVAL;
- }
- //dns设备为空,即没有使用dns技术
- if(pfr->dna_device == NULL) {
- /* if userspace tries to mmap beyond end of our buffer, fail */
- //映射空间超限
- if(size > pfr->slots_info->tot_mem) {
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_mmap() failed: "
- "area too large [%ld > %d]\n",
- size, pfr->slots_info->tot_mem);
- #endif
- return(-EINVAL);
- }
- #if defined(RING_DEBUG)
- printk("[PF_RING] mmap [slot_len=%d]"
- "[tot_slots=%d] for ring on device %s\n",
- pfr->slots_info->slot_len, pfr->slots_info->tot_slots,
- pfr->ring_netdev->name);
- #endif
- //进行内存映射
- if((rc =
- do_memory_mmap(vma, size, pfr->ring_memory, VM_LOCKED,
- 0)) < 0)
- return(rc);
- } else {
- /* DNA Device */
- if(pfr->dna_device == NULL)
- return(-EAGAIN);
- switch (pfr->mmap_count) {
- case 0:
- if((rc = do_memory_mmap(vma, size,
- (void *)pfr->dna_device->
- packet_memory, VM_LOCKED,
- 1)) < 0)
- return(rc);
- break;
- case 1:
- if((rc = do_memory_mmap(vma, size,
- (void *)pfr->dna_device->
- descr_packet_memory, VM_LOCKED,
- 1)) < 0)
- return(rc);
- break;
- case 2:
- if((rc = do_memory_mmap(vma, size,
- (void *)pfr->dna_device->
- phys_card_memory,
- (VM_RESERVED | VM_IO), 2)) < 0)
- return(rc);
- break;
- default:
- return(-EAGAIN);
- }
- pfr->mmap_count++;
- }
- #if defined(RING_DEBUG)
- printk("[PF_RING] ring_mmap succeeded\n");
- #endif
- return 0;
- }
实际上的内存映射工作,是由do_memory_mmap来完成的,这个函数实际上基本就是remap_pfn_range的包裹函数。
不过因为系统支持dna等技术,相应的mode参数有些变化,这里只分析了最基本的方法:mode == 0
- static int do_memory_mmap(struct vm_area_struct *vma,
- unsigned long size, char *ptr, u_int flags, int mode)
- {
- unsigned long start;
- unsigned long page;
- /* we do not want to have this area swapped out, lock it */
- vma->vm_flags |= flags;
- start = vma->vm_start;
- while (size > 0) {
- int rc;
- if(mode == 0) {
- #if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11))
- //根据地址,计算要映射的页帧
- page = vmalloc_to_pfn(ptr);
- //进行内存映射
- rc = remap_pfn_range(vma, start, page, PAGE_SIZE,
- PAGE_SHARED);
- #else
- page = vmalloc_to_page(ptr);
- page = kvirt_to_pa(ptr);
- rc = remap_page_range(vma, start, page, PAGE_SIZE,
- PAGE_SHARED);
- #endif
- } else if(mode == 1) {
- rc = remap_pfn_range(vma, start,
- __pa(ptr) >> PAGE_SHIFT,
- PAGE_SIZE, PAGE_SHARED);
- } else {
- rc = remap_pfn_range(vma, start,
- ((unsigned long)ptr) >> PAGE_SHIFT,
- PAGE_SIZE, PAGE_SHARED);
- }
- if(rc) {
- #if defined(RING_DEBUG)
- printk("[PF_RING] remap_pfn_range() failed\n");
- #endif
- return(-EAGAIN);
- }
- start += PAGE_SIZE;
- ptr += PAGE_SIZE;
- if(size > PAGE_SIZE) {
- size -= PAGE_SIZE;
- } else {
- size = 0;
- }
- }
- return(0);
- }
嗯,跳过了太多的细节,不过其mmap最核心的东东已经呈现出来。
如果要共享内核与用户空间内存,这倒是个现成的可借鉴的例子。
5、数据包的入队操作
做到这一步,准备工作基本上就完成了。因为PF_RING在初始化中,注册了prot_hook。其func指针指向packet_rcv函数:
当数据报文进入Linux网络协议栈队列时,netif_receive_skb会遍历这些注册的Hook:
- int netif_receive_skb(struct sk_buff *skb)
- {
- list_for_each_entry_rcu(ptype, &ptype_all, list) {
- if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
- ptype->dev == orig_dev) {
- if (pt_prev)
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = ptype;
- }
- }
- }
相应的Hook函数得到调用:
- static inline int deliver_skb(struct sk_buff *skb,
- struct packet_type *pt_prev,
- struct net_device *orig_dev)
- {
- atomic_inc(&skb->users); //注意,这里引用计数器被增加了
- return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
- }
packet_rcv随之执行环形队列的入队操作:
- static int packet_rcv(struct sk_buff *skb, struct net_device *dev,
- struct packet_type *pt, struct net_device *orig_dev)
- {
- int rc;
- //忽略本地环回报文
- if(skb->pkt_type != PACKET_LOOPBACK) {
- //进一步转向,最后一个参数直接使用-1,从上下文来看,写为RING_ANY_CHANNEL(其实也是-1)似乎可读性更强,
- //这里表示,如果从packet_rcv进入队列,由通道ID是“未指定的”,由skb_ring_handler来处理
- rc = skb_ring_handler(skb,
- (skb->pkt_type == PACKET_OUTGOING) ? 0 : 1,
- 1, -1 /* unknown channel */);
- } else
- rc = 0;
- kfree_skb(skb); //所以,这里要做相应的减少
- return(rc);
- }
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 < [email protected] > */
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允许同时对多个接口捕获报文,而并不是一个。这就是集群。看一下它用户态的注释就一目了然了:
- /* Syntax
- ethX@1,5 channel 1 and 5
- ethX@1-5 channel 1,2...5
- ethX@1-3,5-7 channel 1,2,3,5,6,7
- */
进一步的入队操作,是通过add_skb_to_ring来完成的:
- static int add_skb_to_ring(struct sk_buff *skb,
- struct ring_opt *pfr,
- struct pfring_pkthdr *hdr,
- int is_ip_pkt, int displ, short channel_id)
- {
- //add_skb_to_ring函数比较复杂,因为它要处理过滤器方面的问题。
- //关于PF_RING的过滤器,可以参考[url]http://luca.ntop.org/Blooms.pdf[/url]
- //获取更多内容。这里不做详细讨论了。或者留到下回分解吧。
-
- //最终入队操作,是通过调用dd_pkt_to_ring来实现的。
- add_pkt_to_ring(skb, pfr, hdr, displ, channel_id,
- offset, mem);
- }
- static void add_pkt_to_ring(struct sk_buff *skb,
- struct ring_opt *pfr,
- struct pfring_pkthdr *hdr,
- int displ, short channel_id,
- int offset, void *plugin_mem)
- {
- char *ring_bucket;
- int idx;
- FlowSlot *theSlot;
- int32_t the_bit = 1 << channel_id;
- #if defined(RING_DEBUG)
- printk("[PF_RING] --> add_pkt_to_ring(len=%d) [pfr->channel_id=%d][channel_id=%d]\n",
- hdr->len, pfr->channel_id, channel_id);
- #endif
- //检查激活标志
- if(!pfr->ring_active)
- return;
- if((pfr->channel_id != RING_ANY_CHANNEL)
- && (channel_id != RING_ANY_CHANNEL)
- && ((pfr->channel_id & the_bit) != the_bit))
- return; /* Wrong channel */
- //写锁
- write_lock_bh(&pfr->ring_index_lock);
- //获取前一次插入的位置索引
- idx = pfr->slots_info->insert_idx;
- //调用get_insert_slot获取当前要捕获数据报文的合适的槽位
- //这里idx++后,指向了下一次插入的位置索引
- idx++, theSlot = get_insert_slot(pfr);
- //累计计数器
- pfr->slots_info->tot_pkts++;
- //没位子了,累计丢包计数器,返回之
- if((theSlot == NULL) || (theSlot->slot_state != 0)) {
- /* No room left */
- pfr->slots_info->tot_lost++;
- write_unlock_bh(&pfr->ring_index_lock);
- return;
- }
- //获取当前槽位的桶
- ring_bucket = &theSlot->bucket;
- //支持插件??在最开始处记录插件信息??
- if((plugin_mem != NULL) && (offset > 0))
- memcpy(&ring_bucket[sizeof(struct pfring_pkthdr)], plugin_mem, offset);
- if(skb != NULL) {
- //重新计算捕获帧长度
- hdr->caplen = min(pfr->bucket_len - offset, hdr->caplen);
- if(hdr->caplen > 0) {
- #if defined(RING_DEBUG)
- printk("[PF_RING] --> [caplen=%d][len=%d][displ=%d][parsed_header_len=%d][bucket_len=%d][sizeof=%d]\n",
- hdr->caplen, hdr->len, displ,
- hdr->parsed_header_len, pfr->bucket_len,
- sizeof(struct pfring_pkthdr));
- #endif
- //拷贝捕获的数据报文,前面空了两个栏位:一个是pkthdr首部,一个是插件offset长度
- //这里经过了一次数据拷贝,对于完美主义者,这并不是一个好的方法。但是PF_RING定位于一个
- //通用的接口库,似乎只有这么做了。否则,追求“零拷贝”,为了避免这一次拷贝,只有逐个修改网卡驱动了。
- skb_copy_bits(skb, -displ,
- &ring_bucket[sizeof(struct pfring_pkthdr) + offset], hdr->caplen);
- } else {
- if(hdr->parsed_header_len >= pfr->bucket_len) {
- static u_char print_once = 0;
- if(!print_once) {
- printk("[PF_RING] WARNING: the bucket len is [%d] shorter than the plugin parsed header [%d]\n",
- pfr->bucket_len, hdr->parsed_header_len);
- print_once = 1;
- }
- }
- }
- }
- //记录首部
- memcpy(ring_bucket, hdr, sizeof(struct pfring_pkthdr)); /* Copy extended packet header */
- //前面idx已经自加过了,判断是否队列已满,若满,归零,否则更新插入索引
- if(idx == pfr->slots_info->tot_slots)
- pfr->slots_info->insert_idx = 0;
- else
- pfr->slots_info->insert_idx = idx;
- #if defined(RING_DEBUG)
- printk("[PF_RING] ==> insert_idx=%d\n", pfr->slots_info->insert_idx);
- #endif
- //累计插入计数器
- pfr->slots_info->tot_insert++;
- //槽位就绪标记,用户空间可以来取了
- theSlot->slot_state = 1;
- write_unlock_bh(&pfr->ring_index_lock);
- //有的时候会出现,用户空间取不到的情况,如队列为空。这样,用户空间调用poll等待数据。这里做相应的唤醒处理
- /* wakeup in case of poll() */
- if(waitqueue_active(&pfr->ring_slots_waitqueue))
- wake_up_interruptible(&pfr->ring_slots_waitqueue);
- }
槽位的计算:
- 在ring_bind函数中,分配空间后,使用ring_slots做为槽位指针。事实上,这里要计算槽位,就是通过索引号 * 槽位长度来得到:
- static inline FlowSlot *get_insert_slot(struct ring_opt *pfr)
- {
- if(pfr->ring_slots != NULL) {
- FlowSlot *slot =
- (FlowSlot *) & (pfr->
- ring_slots[pfr->slots_info->insert_idx *
- pfr->slots_info->slot_len]);
- #if defined(RING_DEBUG)
- printk
- ("[PF_RING] get_insert_slot(%d): returned slot [slot_state=%d]\n",
- pfr->slots_info->insert_idx, slot->slot_state);
- #endif
- return(slot);
- } else {
- #if defined(RING_DEBUG)
- printk("[PF_RING] get_insert_slot(%d): NULL slot\n",
- pfr->slots_info->insert_idx);
- #endif
- return(NULL);
- }
- }
整理中,未完,待续。。。。。。