zoukankan      html  css  js  c++  java
  • PCAP研究

     

    一、  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;           

                  char *description;   

                  struct pcap_addr *addresses;

                  bpf_u_int32 flags;       PCAP_IF_LOOPBACK

    };

     

    struct pcap_pkthdr {

           struct timeval ts;         //获得packet的时间

           bpf_u_int32 caplen;              //抓取到的packet长度

           bpf_u_int32 len;       //packet的真实长度

    };

    len可能大于caplen

     

    pcap_t,摘出了Linux相关部分:

    struct pcap {

             int fd;

             int selectable_fd;

             int send_fd;

             int snapshot;

             int linktype;

             int tzoff;              

             int offset;            

             int break_loop;             

    #ifdef PCAP_FDDIPAD

             int fddipad;

    #endif

             struct pcap_sf sf;

             struct pcap_md md;

             

             int bufsize;

             u_char *buffer;

             u_char *bp;

             int cc;

     

             

             u_char *pkt;

     

             

             pcap_direction_t direction;

     

             

             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 *);

     

             

             struct bpf_program fcode;

     

             char errbuf[PCAP_ERRBUF_SIZE + 1];

             int dlt_count;

             u_int *dlt_list;

             struct pcap_pkthdr pcap_header;  

    };

    五、  一些问题

    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_transmitpcap_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

     

     

  • 相关阅读:
    开发,从需求出发 &#183; 之四 春天在这里
    面向基于英特尔&#174; 架构的 Android* 的 CoCos2D
    js左右切换 选择年龄
    先序遍历创建二叉树,对二叉树统计叶子节点个数和统计深度(创建二叉树时#代表空树,序列不能有误)c语言
    [ACM] hdu 1251 统计难题 (字典树)
    设计模式:单例模式的三种创建方式及其各自的优缺点
    [Android 4.4.2] 泛泰A850 Mokee4.4.2 20140509 RC2.0 by syhost
    IA32 MMU paging初始化代码
    为Android开发人员定制的搜索引擎
    Android中Activity切换时共享视图元素的切换动画(5.0以上)
  • 原文地址:https://www.cnblogs.com/lvdongjie/p/4081500.html
Copyright © 2011-2022 走看看