pcap研究

本文详细介绍pcap库的功能、API及其实现原理,并提供了一些实际应用案例。文章还探讨了pcap在不同操作系统上的安装配置方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Version 1.0

2008 4




本文基于pcap 0.9.8 版本,该版本发布于September 25, 2007 RHEL AS4 Update3 附带的版本是0.8.3 tcpdump --version )。

一、   pcap 简介

封装了OS 提供的底层抓包技术,对外提供一些统一的抓包(及发送)接口。实现这些功能的其他技术包括:BPF(Berkeley Packet Filter) DLPI(Data Link Provider Interface) NIT Linux 专用的SOCKET_PACKET PF_PACKET 等。 ,

二、   pcap Linux 安装

参考《INSTALL.txt 》。

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

三、   pcap 开发介绍

2.1 API 介绍

本部分介绍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 回调函数定义

typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);

2.4 设置过滤条件

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

        char* filter = "udp port 5060";

        bpf_program fp;

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

        {

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

                return 6;

        }

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

        {

                cout<<"set filter err: "<<pcap_geterr(cap_des)<<endl;

                return 7;

        }

2.5 错误返回

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

四、   pcap Linux 实现

4.1 函数

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

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

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

3 . pcap_open_live

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

b) 尝试使用live_open_new 打开设备(PF_PACKET) ,失败将使用live_open_old SOCK_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_op pcap_read_linux )读取socket ,读取cnt packet ,并对每个packet 调用callback 函数;将参数user 传给callbask 函数;函数返回已处理的packet 数;

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

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 #ifdef SO_ATTACH_FILTER );filter 分内核filter pcap 自己实现的filter pcap 会优先使用内核filter ;如果filter 语法过于复杂(#ifdef USHRT_MAX ),会使用或经检查filter 不能在内核执行时, 两种,

a) 调用fix_program

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

12 . pcap_inject ,调用p->inject_op pcap_inject_linux send 发送数据;

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

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

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

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


4.1 数据结构

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

struct pcap_if {

              struct pcap_if *next;

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

              char *description;   /* textual description of interface, or NULL */

              struct pcap_addr *addresses;

              bpf_u_int32 flags;  /* PCAP_IF_ interface flags */     PCAP_IF_LOOPBACK

};


struct pcap_pkthdr {

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

       bpf_u_int32 caplen;       /* length of portion present */       // 抓取到的packet 长度

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

};

len 可能大于caplen


pcap_t ,摘出了Linux 相关部分:

struct pcap {

         int fd;

         int selectable_fd;

         int send_fd;

         int snapshot;

         int linktype;

         int tzoff;              /* timezone offset */

         int offset;            /* offset for proper alignment */

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

#ifdef PCAP_FDDIPAD

         int fddipad;

#endif

         struct pcap_sf sf;

         struct pcap_md md;

         /*

          * Read buffer.

          */

         int bufsize;

         u_char *buffer;

         u_char *bp;

         int cc;


         /*

          * Place holder for pcap_next().

          */

         u_char *pkt;


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

         pcap_direction_t direction;


         /*

          * 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.

          */

         struct bpf_program fcode;


         char errbuf[PCAP_ERRBUF_SIZE + 1];

         int dlt_count;

         u_int *dlt_list;

         struct pcap_pkthdr pcap_header;  /* This is needed for the pcap_next_ex() to work */

};

五、   一些问题

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

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

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

六、   一些测试数据

1 ,基于Winpcap ,使用filter

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

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

测试数据:


描述

CPU(%)

程序消耗(%)

其他

1

不设置filter ,抓取所有数据

65 85

20


2

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

15 25

0


3

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

65 80

20


2 Winpcap 的发送速度

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

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

1   pcap_sendpacket pcap_sendqueue_transmit 的发送速度比较:

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

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

结论: pcap_sendqueue_transmit pcap_sendpacket 发送速度快得多。

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

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

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


用户buffer 1M ,系统buffer1M

每次发包数

50

100

1200

1

1000Mb 网络(秒)

3

4

3

32

100Mb 网络(秒)

13

13

13

30


设置用户buffer lang="EN-US"8M ,系统buffer 1M

每次发包数


100

1200


1000Mb 网络(秒)


2.7

3


100Mb 网络(秒)


13

13.3



设置用户buffer lang="EN-US"64M ,系统buffer 1M

每次发包数


100

1200


1000Mb 网络(秒)


2.7

3


100Mb 网络(秒)


13

13.4



设置用户buffer lang="EN-US"1M ,系统buffer 1M

每次发包数


100

1200


1000Mb 网络(秒)


2.7

3


100Mb 网络(秒)


13

13.4



设置用户buffer lang="EN-US"1M ,系统buffer 64M

每次发包数


100

1200


1000Mb 网络(秒)


3.7

3.1


100Mb 网络(秒)


13

13.3



设置用户buffer lang="EN-US"8M ,系统buffer 8M

每次发包数


100

1200

12000

1000Mb 网络(秒)


3.7

3

2.9

100Mb 网络(秒)


13

13.4

13.6


设置用户buffer lang="EN-US"8M ,系统buffer 64M

每次发包数


100

1200


1000Mb 网络(秒)


2.7

3


100Mb 网络(秒)


13

13.4



七、   相关资料


参考资料

pcap 编程深入解析.doc

Linux pcap man 手册(man pcap



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值