pcap研究

来源:互联网 发布:新闻投稿知乎 编辑:程序博客网 时间:2024/05/01 04:47

Version1.0

20084




本文基于pcap0.9.8版本,该版本发布于September25, 2007RHELAS4 Update3附带的版本是0.8.3tcpdump--version)。

一、 pcap简介

封装了OS提供的底层抓包技术,对外提供一些统一的抓包(及发送)接口。实现这些功能的其他技术包括:BPF(BerkeleyPacket Filter)DLPI(DataLink Provider Interface)NITLinux专用的SOCKET_PACKETPF_PACKET等。,

二、 pcapLinux安装

参考《INSTALL.txt》。

进入pcap源码目录,执行./configure,这将检测系统环境,并生成Makefile文件;执行make;执行makeinstall,这将安装开发头文件、库、手册等;注意这不会安装动态库。

三、 pcap开发介绍

2.1API介绍

本部分介绍API,并对主要的API进行详细的说明。

pcap_open_live打开由dev指定的设备,

pcap_open_dead,只是建立一个pcap_t结构体,用处不大;

pcap_open_offline,打开一个tcpdump/libpcap格式的文件,从中读取数据;

pcap_dump_open

pcap_setnonblock

pcap_getnonblock

pcap_findalldevs,获取设备列表

pcap_freealldevs,关闭查询的设备

pcap_lookupdev,获得设备信息,如eth0,只是获得找到的第一个设备

pcap_lookupnet,获得IP/Mask信息

pcap_dispatch,抓包引擎,需循环调用

pcap_loop抓包引擎,与pcap_dispatch的不同处在于它少一个超时返回参数;

pcap_dump

pcap_compile,编译过滤语法

pcap_setfilter,绑定过滤器

pcap_freecode

pcap_next,轮询方式抓包

pcap_datalink

pcap_snapshot

pcap_is_swapped

pcap_major_version

pcap_minor_version

pcap_stats,获取当前捕获的统计信息

pcap_file

pcap_fileno

pcap_perror

pcap_geterr

pcap_strerror

pcap_close,关闭设备

pcap_dump_close

pcap_sendpacket,发送一个原始数据包

说明:

1.函数的返回值,0表示成功,-1表示错误;

2 .参数errbuf用于接收错误信息,不小于PCAP_ERRBUF_SIZE

2.2使用pcap的一般步骤

Øpcap_lookupdev等获得设备信息,网卡设备名、设备所在网络地址;

Øpcap_open_live打开设备,设置网卡成混杂模式;

Ø循环调用pcap_loop中实现包捕获引擎,编写包分析程序;

2.3回调函数定义

typedefvoid (*pcap_handler)(u_char *, const struct pcap_pkthdr *, constu_char *);

2.4设置过滤条件

首先使用pcap_compile编译一个filter字符串,然后使用pcap_setfilter将编译结果绑定到一个设备;

       char* filter ="udp port 5060";

       bpf_programfp;

       if(-1 ==pcap_compile(cap_des, &fp, filter, 0, netp))

       {

               cout<<"compileerr: "<<pcap_geterr(cap_des)<<endl;

               return 6;

       }

       if(-1 ==pcap_setfilter(cap_des, &fp))

       {

               cout<<"setfilter err: "<<pcap_geterr(cap_des)<<endl;

               return 7;

       }

2.5错误返回

存在两种获取错误原因的方式,一是通过函数参数的errbuf;如果函数没有该参数,则使用pcap_geterr获得,函数执行错误时会将错误信息写入结构体中的预分配的errbuf(其中一些是基于errno),该函数返回该errbuf的地址;

四、 pcapLinux实现

4.1函数

本部分介绍某些关键函数的实现:

1.pcap_findalldevs,首先使用socket()获得一个socket的句柄,然后使用ioctl获得所有网卡信息;该函数会尝试打开找到的设备(add_or_find_if),它只返回能够用于livecapture的设备;

2.pcap_lookupdev,调用pcap_findalldevs,将找到的第一个device返回。

3.pcap_open_live

a)参数device赋空(NULL)或“any时将抓取所有网卡的数据包(这种情况下将不支持混杂式?);

b)尝试使用live_open_new打开设备(PF_PACKET),失败将使用live_open_oldSOCK_PACKET);

c)live_open_new,对捕获单块网卡,调用socket(PF_PACKET,SOCK_RAW, htons(ETH_P_ALL))(数据带链路层头),如果需捕获所有网卡,调用socket(PF_PACKET,SOCK_DGRAM, htons(ETH_P_ALL))(不带链路层头);调用setsockopt设置混杂模式;

d)设置pcap_t对象,设置操作系统相关的处理函数的指针,及初始化buffer(大小由参数snaplen确定),

4.pcap_close,调用pcap_close_linux

5.pcap_lookupnet,先使用socket()获得一个socket句柄,然后调用ioctl获得设备相关的参数;

6.pcap_loop,判断open方式,循环调用pcap_offline_read读取文件或read_oppcap_read_linux)读取socket,读取cntpacket,并对每个packet调用callback函数;将参数user传给callbask函数;函数返回已处理的packet数;

a)pcap_read_linux调用pcap_read_packet,后者调用recvfrom将数据接收到bufsize;如果kernelfilter没有起作用,调用bpf_filtercallback函数;进行处理;最后调用

7.pcap_dispatch仅调用一次read_op,相对pcap_loop,不能用于读取文件,及不循环;这样它的处理少一些;在Linux下,每次调用只抓一个packet

8.pcap_next,调用pcap_dispatch实现,每次只抓一个packet,将packet作为函数返回值;

9.pcap_next_ex,提供了读取文件的能力,其他处理与pcap_next相仿;

10.pcap_compile,调用了lex_init等函数——没看到这些函数的实现;

11.pcap_setfilter,调用了pcap_setfilter_linux#ifdefSO_ATTACH_FILTER);filter分内核filterpcap自己实现的filterpcap会优先使用内核filter;如果filter语法过于复杂(#ifdefUSHRT_MAX),会使用或经检查filter不能在内核执行时,两种,

a)调用fix_program

b)调用set_kernel_filter设置内核filter,使用setsockoptSO_ATTACH_FILTER);

12.pcap_inject,调用p->inject_oppcap_inject_linuxsend发送数据;

13.pcap_sendpacket,与pcap_inject实现一样,只是更改了接口;

14.pcap_stats,调用stats_oppcap_stats_linux)函数,内核版本需2.4以上,调用getsockopt获得数据;可统计数据包括:经过filter到达pcappacket数量、通过了filter但是因为buffer不足等原因而没有到达pcappacket数量;

15.pcap_setnonblock,将socket设置成阻塞或非阻塞模式;

16.pcap_setdirection,设置要抓取的packet的方向,发出还是收到?;


4.1数据结构

本部分为pcap的关键数据结构:

structpcap_if {

             struct pcap_if*next;

             char*name;           /*name to hand to "pcap_open_live()" */

             char*description;   /* textual description of interface, orNULL */

             structpcap_addr *addresses;

             bpf_u_int32flags;  /* PCAP_IF_ interface flags */    PCAP_IF_LOOPBACK

};


structpcap_pkthdr {

      struct timevalts;    /* time stamp */    //获得packet的时间

      bpf_u_int32caplen;       /* length of portionpresent */       //抓取到的packet长度

      bpf_u_int32len;     /* length this packet (off wire) */ //packet的真实长度

};

len可能大于caplen


pcap_t,摘出了Linux相关部分:

structpcap {

        intfd;

        intselectable_fd;

        intsend_fd;

        intsnapshot;

        intlinktype;

        inttzoff;             /* timezone offset */

        intoffset;           /* offset for proper alignment */

        intbreak_loop;            /* flag set to force break from packet-reading loop */

#ifdefPCAP_FDDIPAD

        intfddipad;

#endif

        structpcap_sf sf;

        structpcap_md md;

        /*

         *Read buffer.

         */

        intbufsize;

        u_char*buffer;

        u_char*bp;

        intcc;


        /*

         *Place holder for pcap_next().

         */

        u_char*pkt;


        /*We're accepting only packets in this direction/these directions. */

        pcap_direction_tdirection;


        /*

         *Methods.

         */

        int    (*read_op)(pcap_t *, int cnt, pcap_handler, u_char *);

        int    (*inject_op)(pcap_t *, const void *, size_t);

        int    (*setfilter_op)(pcap_t *, struct bpf_program *);

        int    (*setdirection_op)(pcap_t *, pcap_direction_t);

        int    (*set_datalink_op)(pcap_t *, int);

        int    (*getnonblock_op)(pcap_t *, char *);

        int    (*setnonblock_op)(pcap_t *, int, char *);

        int    (*stats_op)(pcap_t *, struct pcap_stat *);

        void (*close_op)(pcap_t *);


        /*

         *Placeholder for filter code if bpf not in kernel.

         */

        structbpf_program fcode;


        charerrbuf[PCAP_ERRBUF_SIZE + 1];

        intdlt_count;

        u_int*dlt_list;

        structpcap_pkthdr pcap_header;  /* This is needed for thepcap_next_ex() to work */

};

五、 一些问题

1c代码与c++代码风格比较

1 C++使用继承结构区分共性与个性,将代表个性的数据结构放到子类中,这样区别能集中到子类中;C中使用大量的条件编译,如#ifdefHAVE_PF_PACKET_SOCKETS,代码混杂;

2 C中实现多态的方式,结构体中定义函数指针,不同的实现赋不同的值;

六、 一些测试数据

1,基于Winpcap,使用filter

程序执行环境:WindowsXP sp2,无线网卡;

测试方式:向10.130.24.158拷贝一个超过lang="EN-US"1G的文件,检查程序的性能情况;

测试数据:


描述

CPU(%)

程序消耗(%)

其他

1

不设置filter,抓取所有数据

6585

20


2

设置filter(udp)使得不抓取数据

1525

0


3

设置filter(tcp)抓取所有数据

6580

20


2Winpcap的发送速度

说明:本次测试只测试了发送函数的执行耗时,未检查接受端的情况,即不能保证数据真的通过网卡发出。

测试方式:每次发送300B大小的数据包,每循环执行100000500000次发送,记录每循环的耗时,取多次循环的折中值;另外24.158机器上安装有两块千兆网卡,一块接在千兆交换机上,另一块接在百兆交换机上。

1 pcap_sendpacketpcap_sendqueue_transmit的发送速度比较:

每循环执行100000pcap_sendpacket发送,耗时约6秒,流量约40Mb/s,且100Mb网络稍快于1000Mb网络;

使用pcap_sendqueue_transmit,积累到100个数据包时发送一次;千兆网络每循环耗时0.7秒,流量342Mb/s,百兆网络每循环耗时2.692Mb/s。秒,流量

结论:pcap_sendqueue_transmitpcap_sendpacket发送速度快得多。

2 用户buffer、系统buffer、每次发送数量对发送速度的影响

本部分测试用户buffer、系统buffer、每次发送数量对pcap_sendqueue_transmit的发送速度的影响;本测试每循环发送500000个包,每个包300B

关于pcap_sendqueue_alloc的说明:该函数用于分配一块用户空间存储,应设置得足够大以容纳数据;测试发现它会影响到程序占用的内存,但对发送速度没有影响。


用户buffer1M,系统buffer1M

每次发包数

50

100

1200

1

1000Mb网络(秒)

3

4

3

32

100Mb网络(秒)

13

13

13

30


设置用户bufferlang="EN-US"8M,系统buffer1M

每次发包数


100

1200


1000Mb网络(秒)


2.7

3


100Mb网络(秒)


13

13.3



设置用户bufferlang="EN-US"64M,系统buffer1M

每次发包数


100

1200


1000Mb网络(秒)


2.7

3


100Mb网络(秒)


13

13.4



设置用户bufferlang="EN-US"1M,系统buffer1M

每次发包数


100

1200


1000Mb网络(秒)


2.7

3


100Mb网络(秒)


13

13.4



设置用户bufferlang="EN-US"1M,系统buffer64M

每次发包数


100

1200


1000Mb网络(秒)


3.7

3.1


100Mb网络(秒)


13

13.3



设置用户bufferlang="EN-US"8M,系统buffer8M

每次发包数


100

1200

12000

1000Mb网络(秒)


3.7

3

2.9

100Mb网络(秒)


13

13.4

13.6


设置用户bufferlang="EN-US"8M,系统buffer64M

每次发包数


100

1200


1000Mb网络(秒)


2.7

3


100Mb网络(秒)


13

13.4



七、 相关资料


参考资料

pcap编程深入解析.doc

Linuxpcap man手册(manpcap



原创粉丝点击