内核代码中,ip_rcv是ip层收包的主入口函数,该函数由软中断调用。存放数据包的sk_buff结构包含有目的地ip和端口信息,此时ip层进行检查,如果目的地ip不是本机,且没有开启转发的话,则将包丢弃,如果配置了netfilter,则按照配置规则对包进行转发。
tcp_v4_rcv是tcp层收包的接收入口,其调用__inet_lookup_skb函数查到数据包需要往哪个socket传送,之后将数据包放入tcp层收包队列中,如果应用层有read之类的函数调用,队列中的包将被取出。
最近遇到一个问题,就是libpcap的收包,比tcpdump的收包,要慢。
然后修改测试代码如下:
#include <pcap.h> /*libpcap*/ static pcap_t * pcap_http_in; int initPcapIn_http() { int snaplen = 1518;//以太网数据包,最大长度为1518bytes int promisc = 1;//混杂模式 int timeout = 1000; char errbuf[PCAP_ERRBUF_SIZE];//内核缓冲区大小 /*这个设备号需要根据测试服务器更改*/ char * _pcap_in = "enp8s0f1"; char * _bpf_filter = "tcp[13]=24"; /*打开输入设备或者文件*/ if((pcap_http_in = pcap_open_live(_pcap_in, snaplen, promisc, timeout, errbuf)) == NULL) { printf("pcap_open_live(%s) error, %s ", _pcap_in, errbuf); pcap_http_in = pcap_open_offline(_pcap_in, errbuf); if(pcap_http_in == NULL) { printf("pcap_open_offline(%s): %s ", _pcap_in, errbuf); } else printf("Reading packets from pcap file %s... ", _pcap_in); } else { printf("Capturing live traffic from device %s... ", _pcap_in); /*设置bpf过滤参数。*/ if(_bpf_filter!= NULL) { struct bpf_program fcode; if(pcap_compile(pcap_http_in, &fcode, _bpf_filter, 1, 0xFFFFFF00) < 0) { printf("pcap_compile error: '%s' ", pcap_geterr(pcap_http_in)); } else { if(pcap_setfilter(pcap_http_in, &fcode) < 0) { printf("pcap_setfilter error: '%s' ", pcap_geterr(pcap_http_in)); } else printf("Succesfully set BPF filter to '%s' ", _bpf_filter); } } /*设置一些参数*/ if(pcap_setdirection(pcap_http_in, PCAP_D_IN)<0) /*只抓入向包*/ { printf("pcap_setdirection error: '%s' ", pcap_geterr(pcap_http_in)); } else printf("Succesfully set direction to '%s' ", "PCAP_D_IN"); } return 0; } static inline unsigned long long rp_get_us(void) { struct timeval tv = {0}; gettimeofday(&tv, NULL); return (unsigned long long)(tv.tv_sec*1000000L + tv.tv_usec); } int main(int argc, char *argv[]) { initPcapIn_http(); unsigned char * pkt_data = NULL; struct pcap_pkthdr pcap_hdr; struct pcap_pkthdr * pkt_hdr = &pcap_hdr; while(1) { while( (pkt_data = (unsigned char * )pcap_next( pcap_http_in, &pcap_hdr))!=NULL) { if(pkt_hdr->caplen == 454) { unsigned long long time1 = rp_get_us(); printf("---BEGIN: %ld us ",time1); } } } if (pcap_http_in) { pcap_close(pcap_http_in); } return 0; }
一开始静态编译,gcc静态编译报错,/usr/bin/ld: cannot find -lc
Makefile中肯定有-static选项。这其实是静态链接时没有找到libc.a。
其实需要安装glibc-static.xxx.rpm,如glibc-static-2.12-1.107.el6_4.2.i686.rpm,或是yum install glibc-static,我最终下载的是:glibc-static-2.17-157.el7.x86_64.rpm.
结果测试发现,我们打印的---BEGIN的时间,比tcpdump对应的时间晚1ms左右,也就是1000us左右。
然后我们根据tcpdump的调用方式,发现
ldd /sbin/tcpdump |grep -i pcap libpcap.so.1 => /usr/local/lib/libpcap.so.1 (0x00007fd1903f1000)
ls -alrt /usr/local/lib/libpcap.so.1
lrwxrwxrwx. 1 root root 16 7月 25 19:36 /usr/local/lib/libpcap.so.1 -> libpcap.so.1.5.3
然后我将我的编译方式改成动态链接方式,即
gcc -lpcap -g -o pcap.o pcap.c
发现效果很好,跟tcpdump差不多,也就是说,动态链接的lpcap的性能比静态链接的lpcap的性能要好。颠覆了我的认知,因为我一直认为静态链接快一点是有可能的。
我下载的源码是http://www.tcpdump.org/release/官网的,系统自带的版本和我的版本号一致。
发现不管是gcc -O2还是O3都是如此,我的静态链接的库就是慢,然后将tcpdump官网的libpcap库改成动态链接,还是慢。对比如下:
自己tcpdump官网下载的1.5.3的libpcap如下 poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.103021> poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.095322> poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.101384> poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.100031> 对应的系统自带的1.5.3版本如下: poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.000139> poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.000061> poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.000231> poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}]) <0.000062>
从参数看,是一模一样的,但是调用的消耗看,前者明显慢,直觉告诉我,应该看fd的属性,所以针对属性又单独跟踪了一次:
系统自带的1.5.3版本: [root@localhost libpcap-1.5.3]# strace -e setsockopt ./pcaptest setsockopt(3, SOL_PACKET, PACKET_ADD_MEMBERSHIP, "3 1 ", 16) = 0 setsockopt(3, SOL_PACKET, PACKET_AUXDATA, [1], 4) = 0 setsockopt(3, SOL_PACKET, PACKET_VERSION, [1], 4) = 0 setsockopt(3, SOL_PACKET, PACKET_RESERVE, [4], 4) = 0 setsockopt(3, SOL_PACKET, PACKET_RX_RING, {block_size=4096, block_nr=655, frame_size=1600, frame_nr=1310}, 16) = 0 Capturing live traffic from device enp5s0... setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, "1 224246c ", 16) = 0 setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, "v 2602763571 ", 16) = 0 我在tcpdump官网下载的libpcap版本: [root@localhost libpcap-1.5.3]# strace -e setsockopt ./pcaptest setsockopt(3, SOL_PACKET, PACKET_ADD_MEMBERSHIP, "3 1 ", 16) = 0 setsockopt(3, SOL_PACKET, PACKET_AUXDATA, [1], 4) = 0 setsockopt(3, SOL_PACKET, PACKET_VERSION, [2], 4) = 0 setsockopt(3, SOL_PACKET, PACKET_RESERVE, [4], 4) = 0 setsockopt(3, SOL_PACKET, PACKET_RX_RING, "