目前,在linux操作系统下的网络数据包捕获系统普遍是建立在libpcap捕包平台上的,libpcap的英文意思是Library of Packet Capture,即数据包捕获函数库。该库提供的C函数接口可用于需要捕获经过网络接口(只要经过该接口,目标地址不一定为本机)数据包的应用系统中。在大流量网络环境下,基于libpcap的低效报文捕获技术无法捕获足够数量的网络数据包以供上层应用系统使用。
一、linux内核协议栈分析与性能评价
在libpcap系统中,操作系统内核协议栈为用户提供了一种工作在数据链路层的套接字SOCK_PACKET。libpcap通过该套接字应用程序接口从用户态进入到系统内核态,绕过内核协议栈中的TCP层和IP层处理过程直接从数据链路层捕获原始网络数据帧。为了深入剖析libpcap的捕包机制,首先在linux-2.4.10 操作系统的基础上,研究了传统TCP/IP内核协议栈的报文传输机制和性能瓶颈。
1、TCP/IP协议栈报文传输模型
linux操作系统的内核协议栈基本可以分为数据链路层、IP层、TCP/UDP层、INET Socket层、BSD Socket层和应用层等几个部分。以传输层协议为TCP协议为例,在图1中给出了典型的TCP/IP协议栈层次和模块结构。
内核协议栈包括一组函数和若干个关键数据结构。各个协议层的函数调用关系如图1所示。用户层的socket套接字库通过内核的数据结构struct socket和struct sock来维护。数据在发送/接收过程中通过两个数据结构struct msghdr和struct sk_buff来管理数据缓冲区。其中struct msghdr由BSD Socket层和INET Socket层来维护,struct msghdr用于存放应用层数据缓冲区的地址和大小。在TCP/IP以下各层使用struct sk_buff作为数据缓冲区。内核在tcp_sendmsg()/tcp_recvmsg()函数中通过内存拷贝操作实现这两种数据结构的转换。内核数据缓冲区struct sk_buff在通过TCP/UDP层以下各层时,不进行数据拷贝操作,仅仅将数据指针在不同报文协议头之间移动。这样就可以避免不必要的系统开销。
2、TCP/IP协议栈报文接收过程性能分析
以Intel 100M网卡eepro100作为物理层设备,对照图1中的内核协议栈框架,详细分析一下linux系统内核协议栈的报文接收过程:
linux内核协议栈的报文接收分为两个过程:自上而下的过程和自下而上的过程。其中自上而下的过程是一个被动的过程,系统调用read()或recv()/recvfrom()通过上层协议栈传递“需要数据”请求,通过tcp_recvmsg()函数进入TCP协议层。函数tcp_recvmsg()会到接收队列struct sk->receive_queue中读取数据,若接收队列中没有所需要的数据时,则函数tcp_recvmsg()所在的当前进程就会休眠等待struct sk->receive_queue接收队列上。当系统重新唤醒被挂起进程时,tcp_recvmsg()函数会读取所需的数据并调用tcp_v4_do_rcv()函数将struct sk->backlog队列中的数据填充到sk->receive_queue队列中同时唤醒等待在该队列上的进程。tcp_recvmsg()函数在读取数据包的过程中,将数据包从内核缓冲区拷贝到应用程序缓冲区。自下而上的过程如下所示:
- 当网络数据包到达后,eepro100网卡通过DMA方式将数据包传输到网卡驱动程序缓冲环中并在数据传输结束时产生硬件接收中断。系统根据中断类型调用相应的硬中断处理程序,由此内核协议栈完成从物理层到数据链路层的转换。
- 在硬中断处理程序speed_rx()中,内核处理接收到的数据包并通过调用netif_rx()函数将驱动程序缓冲环中的数据添加到系统接收队列中。操作系统在空闲时机调用上层软中断处理函数net_rx_action()。在函数net_rx_action()中,根据已经注册的数据包类型(如ETH_P_IP)调用相应的处理函数ip_rcv()。这样内核协议栈从数据链路层进入IP层。
- 在ip_rcv函数中,根据路由结果判断数据应该发送到上层TCP协议处理时调用tcp_v4_rcv()。该函数会调用tcp_v4_do_rcv(),通过函数tcp_v4_do_rcv()将struct sk->backlog队列中的数据填充到struct sk->receive_queue队列中。
为了测试内核协议栈在报文接收过程中各个部分的系统开销(消耗CPU 的时间),在linux下修改了网卡的驱动程序并在系统内核中插入了一些代码来记录报文到达各个部分的时间戳,从而可以确定各部分的时间代价。测试结果如表1所示。
从表中可以看出,系统硬中断的处理时间(从speedo_rx()到net_rx_action())约占20%左右,系统软中断处理的系统开销(net_rx_action()到tcp_recvmsg())约占15%左右,而从tcp_recvmsg()到inet_recvmsg()过程中由于执行数据内存拷贝操作,所以消耗了大约55%左右的CPU时间。系统调用大约占用5%~6%左右的CPU时间。内存拷贝操作的代价是昂贵的,主要有以下几点原因:
- 内存总线带宽有限,每次内存拷贝都要占用带宽;
- 每次拷贝操作都会消耗大量的CPU周期。通常,CPU都是逐字地将数据从源缓冲区移动到目的缓冲区。这意味着在拷贝操作过程中,CPU是不可利用的;
- 数据拷贝操作影响了系统Cache性能。因为CPU通过cache访问主存,因此在拷贝操作之前cache中驻留的有用信息会被清空,然后被拷贝的数据所代替。
二、基于libpcap的传统捕包机制分析
libpcap利用工作在数据链路层的套接字SOCK_PACKET来完成网络数据包的读取。同样以Intel 100M网卡eepro100为例,分析libpcap的捕包流程如下所示:
- 当网络数据包到达后,eepro100网卡通过DMA方式将数据包传输到网卡驱动程序缓冲环中并在数据传输结束时产生硬件接收中断。系统根据中断类型调用相应的硬中断处理程序。
- 在硬中断处理程序speedo_rx()中,内核处理接收到的数据包并通过调用netif_rx()函数将驱动程序缓冲环中的数据添加到系统接收队列中。操作系统在空闲时机调用中断处理下半部分即软中断处理函数net_rx_action()。此函数的主要功能是,检查系统中已注册的数据包类型并调用相应的处理函数。对应libpcap的注册包类型为ETH_P_ALL, 其处理函数为packet_rcv()。packet_rcv()仍然工作在数据链路层。
- 在packet_rcv函数中,直接调用skb_queue_tail()将数据包存放在struct sk->receive_queue队列中。这样数据包在接收过程中就绕过了TCP层和IP层的处理。
- 由休眠在队列struct sk->receive_queue上的函数packet_recvmsg()接收链路层数据帧并将数据帧拷贝到应用层缓冲区中。
libpcap的捕包流程和内核TCP/IP协议栈的报文接收过程对比如图2所示。从图中可以看出相对于传统内核TCP/IP协议栈的收包过程而言,libpcap绕过了TCP层(UDP层)和IP层的处理过程,直接将数据包从数据链路层拷贝到应用程序缓冲区中。这样能够节省数据包在接收过程中所消耗的CPU时间,从表1中可以看出节省了大约10%的处理时间。但是在libpcap捕包过程中,系统调用、数据拷贝和内核中断处理仍然是系统主要的性能瓶颈。
三、针对libpcap的捕包优化措施
针对libpcap捕包过程中的几个耗时环节,分别进行优化和改进以便减少系统资源消耗,同时提高libpcap在大流量网络环境下的捕包性能。以下提出了几种主要的捕包优化措施并对其优缺点进行了分析。
1、增加内核过滤机制
内核过滤就是在相当于软中断处理程序的部分(packet_rcv()函数)判断接收的数据包是否是应用程序感兴趣的报文。如果是才将其复制到应用层缓冲区内,否则就丢弃。这样可以大大降低系统实际处理的报文数量,从而提高了捕包效率。但这种方式仅对某些应用起作用,对某些应用如流量统计,常用协议分析等应用中就不会起很大作用,因为这些应用往往要处理网络上的绝大部分报文。
2、将主要的应用处理移至内核模块处理程序中
由于系统调用涉及到0x80号中断现场保存和进程切换,在大流量网络环境下频繁的系统调用是很费时的工作。所以许多应用将主要的应用层处理部分作为LKM模块放在内核中处理,从而可以减少系统调用的次数。但是这样做需要将应用处理写在驱动模块或内核代码中,在内核中编写代码与应用层编写代码的方法以及使用的函数等有许多区别,而且内存分配、读写用户数据的方式也有所变化,内核编码的复杂性和难度都有所增加。另外内核代码对稳定性要求很高,一旦内核程序出现问题会造成整个系统的崩溃。所以,一般这种方法适用于一些处理效率要求高,处理过程相对简单的应用,比如防火墙等。
3、一次复制多个报文到用户缓冲区
libpcap通过系统调用revfrom()读取数据包,而且每调用一次该函数,它只向用户区传递一个数据包。因此可以在内核缓冲区中保存一定数量的报文,在报文达到一定数量时唤醒用户进程读取内核缓冲区中的所有数据包。这样就节省了用户进程切换的时间。在对大流量的数据报文进行处理时,是一个很有效的方法。不过这样会延迟报文到达用户进程的时间。当报文到达速率很大时,时间延迟会明显增加。
4、旁路内核协议栈
从内核到应用层的数据内存拷贝操作是捕包系统的主要性能瓶颈。为此可以采用零拷贝技术,通过在内核添加处理模块来将用户缓冲区的虚拟地址转换为网卡可用的物理地址并锁定该地址。改进网卡驱动程序以获取用户缓冲区的物理地址并利用网卡异步DMA工作方式将数据包从网卡直接传送到用户空间,从而能够旁路操作系统内核协议栈,降低系统内核处理、数据拷贝和系统调用的开销。该方法能够大幅度的提高系统的捕包性能,甚至能够达到网卡的性能极限。但是由于需要修改网卡驱动程序,因而限制了这种方法的通用性。
5、 降低系统硬件中断频率
当网络报文到达系统速率过于频繁的时候,会出现CPU处理时间全部用于中断处理的情况。这时系统频繁运行硬中断处理程序,导致上层软中断处理得不到运行。硬中断处理程序将网络报文填充到系统缓冲区中,这样系统缓冲区很快就被添满,多余的数据包将被丢弃。同时其他进程也无法获得CPU的控制权。因此减少中断频率对于提高大流量网络捕包系统性能来说是必要的。在某些改进方法中,当发现中断频率过高时就禁止掉硬件中断,这样虽然会丢失一些报文,但可使系统有机会响应其他的进程。一些系统使用了中断和轮询混合机制来降低系统中断频率,Macquelin等人提出的轮询看门狗机制就是这样一种方法。
原文链接:https://tomcat.one/files/papers/%E5%9F%BA%E4%BA%8ELinux%E7%B3%BB%E7%BB%9F%E7%9A%84%E6%8A%A5%E6%96%87%E6%8D%95%E8%8E%B7%E6%8A%80%E6%9C%AF%E7%A0%94%E7%A9%B6_%E6%9D%A8%E6%AD%A6.pdf