免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1948 | 回复: 0
打印 上一主题 下一主题

pcap研究 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-11-11 16:01 |只看该作者 |倒序浏览
文基于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
                return 6;
        }
        if(-1 == pcap_setfilter(cap_des,
&fp))
        {
                cout
                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拷贝一个超过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为8M,系统buffer 1M;

  
  每次发包数
  
  
   
  
  
  100
  
  
  1200
  
  
   
  


  
  1000Mb网络(秒)
  
  
   
  
  
  2.7
  
  
  3
  
  
   
  


  
  100Mb网络(秒)
  
  
   
  
  
  13
  
  
  13.3
  
  
   
  


设置用户buffer为64M,系统buffer 1M;

  
  每次发包数
  
  
   
  
  
  100
  
  
  1200
  
  
   
  


  
  1000Mb网络(秒)
  
  
   
  
  
  2.7
  
  
  3
  
  
   
  


  
  100Mb网络(秒)
  
  
   
  
  
  13
  
  
  13.4
  
  
   
  


设置用户buffer为1M,系统buffer 1M;

  
  每次发包数
  
  
   
  
  
  100
  
  
  1200
  
  
   
  


  
  1000Mb网络(秒)
  
  
   
  
  
  2.7
  
  
  3
  
  
   
  


  
  100Mb网络(秒)
  
  
   
  
  
  13
  
  
  13.4
  
  
   
  


设置用户buffer为1M,系统buffer 64M;

  
  每次发包数
  
  
   
  
  
  100
  
  
  1200
  
  
   
  


  
  1000Mb网络(秒)
  
  
   
  
  
  3.7
  
  
  3.1
  
  
   
  


  
  100Mb网络(秒)
  
  
   
  
  
  13
  
  
  13.3
  
  
   
  


设置用户buffer为8M,系统buffer 8M;

  
  每次发包数
  
  
   
  
  
  100
  
  
  1200
  
  
  12000
  


  
  1000Mb网络(秒)
  
  
   
  
  
  3.7
  
  
  3
  
  
  2.9
  


  
  100Mb网络(秒)
  
  
   
  
  
  13
  
  
  13.4
  
  
  13.6
  


设置用户buffer为8M,系统buffer 64M;

  
  每次发包数
  
  
   
  
  
  100
  
  
  1200
  
  
   
  


  
  1000Mb网络(秒)
  
  
   
  
  
  2.7
  
  
  3
  
  
   
  


  
  100Mb网络(秒)
  
  
   
  
  
  13
  
  
  13.4
  
  
   
  


七、
相关资料

参考资料
《pcap编程深入解析.doc》
Linux pcap man手册(man pcap)
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/95634/showart_2091750.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP