• (原创)WinpCap的详解(一)


      首先来百科一下Winpcap是一个什么东东。Winpcap(windows packet capture)是windows平台下一个免费,公共的网络访问系统。

      它有如下几个功能:

      1、捕获原始数据包,包括在共享网络上各主机发送/接收的以及相互之间交换的数据;

      2、在数据包发往应用程序之前,按照自定义的规则将某些特殊的数据包过滤掉;

      3、在网络上发送原始的数据包;

      4、收集网络通信过程中的统计信息。

          从上面的功能来看,这个库文件提供了许多的API函数,可以让我们捕获网络上的数据包以及统计网络通信的信息。为了更直观的反应这个库文件的作用,我们来看看利用这个库文件写出来的一个应用软件,wireshark。界面如下图所示,这个界面只是捕获数据的一个小界面,里面有很多的设置,有兴趣可以下载一个试试。他能统计在一个局域网的所有网络信息。

      这里面重要一点,需要提醒的是:winpcap的主要功能在于独立于主机协议(如TCP-IP)而发送和接收原始数据包。也就是说,winpcap不能阻塞,过滤或控制其他应用程序数据包的发收,它仅仅只是监听共享网络上传送的数据包。也就是说,WinpCap主要功能不能截取网络中的数据,他只能监听里面的数据。

      对于WinpCap的结构以及原理,我们自然可以不用理会啦,我们只需要知道他的用途就行啦!

    一、安装WinpCap

      1、首先我们来看看如何安装WinpCap这个库,首先是下载WinpCap安装文件,这里有许多的版本,可以在官网上下载,http://www.winpcap.org/,这里重点提醒一下,特别需要注意一下版本,如果你的版本是4.02,那么你的安装包也必须下载对应的版本,这里特别注意下,你可以下载当前比较稳定的版本。下载之后安装就ok啦!这里我用的是WinpCap4.02.

      2、下载WinpCap Develop‘s Packs,这里我也提供相同的版本WpdPack4.02.

      3、解压后会得一个目录WpdPack四个子目录:
      docs
      Examples-pcap
      Examples-remote
      Include
      Lib
      然后配置VC++
      tools --> options --> Projects and Solutions --> VC++ Directories :

      Include files :WpdPackPath\include

      Library files: WpdPackPath\lib

      4、经过上面的步骤之后,你的WinpCap应该就安装成功啦,之后就是运行一下里面提供的例程啦,如果有什么问题,就对应的把问题在网上查一查,总体来说有以下几个问题:第一个就是需要在工程的链接库上添加wpcap.lib链接库;第二个就是你的SDK太老了,需要添加更新你的SDK,相应的到官方网站上下载适合你电脑的SDK。这里面查错的能力就是大家查找相关的网站,只要把错误的英语输入google和百度就大部分能找到原因。这里提供一个别人的博客,里面写的挺详细的WinPcap 常见安装和运行错误

    二、各种功能的实现

      这里面有许多的例子,但是大部分例子都是建立在一定的基础上的,首先我们来看看几个基本的函数。这里推荐一个好的网站,里面有这个库所有解释。

      1、pcap_if,和pcap_if_t是一样的

    /*
     * Item in a list of interfaces.
     */
    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的链表指向下一个设备接口;第二个是设备的实际的名字,这个名字是机器能识别的名字,供pcap_open_live()调用;第三个是设备的文本描述符,这个描述符就是人们能够识别的文本符号;第四个是一个地址指针,指向的是一系列接口的第一个指针;第五个是一个标志位,目前这个标志位主要是不是loopback设备。

      2 用户定义了两个类型

    typedef struct pcap pcap_t; 一个已打开的捕获实例描述符,这个结构体对用户来说是不透明的,他提供wpcap.dll的函数来维护他的内容。
    typedef struct pcap_addr pcap_addr_t;接口地址。

      3 一个数据包在堆文件中的文件头,包括时间戳,目前部分的长度和数据包的长度

    struct pcap_pkthdr {
     struct timeval ts; /* time stamp */
     bpf_u_int32 caplen; /* length of portion present */
     bpf_u_int32 len; /* length this packet (off wire) */
    };

     1、获取设备列表

      通常,编写基于WinPcap应用程序的第一件事情,就是获得已连接的网络适配器列表。libpcap和WinPcap都提供了 pcap_findalldevs_ex() 函数来实现这个功能: 这个函数返回一个 pcap_if 结构的链表, 每个这样的结构都包含了一个适配器的详细信息。值得注意的是,数据域 namedescription 表示一个适配器名称和一个可以让人们理解的描述。

      来看看下面简单的一个代码:

      这个代码很简单首先利用一个API函数获得机器的所有网络设备列表,接着一个个打印出来。

    #include "pcap.h"
    
    main()
    {
        pcap_if_t *alldevs;
        pcap_if_t *d;
        int i=0;
        char errbuf[PCAP_ERRBUF_SIZE];
        
        /* 获取本地机器设备列表 */
        if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* auth is not needed */, &alldevs, errbuf) == -1)
        {
            fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf);
            exit(1);
        }
        
        /* 打印列表 */
        for(d= alldevs; d != NULL; d= d->next)
        {
            printf("%d. %s", ++i, d->name);
            if (d->description)
                printf(" (%s)\n", d->description);
            else
                printf(" (No description available)\n");
        }
        
        if (i == 0)
        {
            printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
            return;
        }
    
        /* 不再需要设备列表了,释放它 */
        pcap_freealldevs(alldevs);
    }
    
    

      2、获取已安装设备的高级信息

      在第1讲中, (获取设备列表) 我们展示了如何获取适配器的基本信息 (如设备的名称和描述)。 事实上,WinPcap提供了其他更高级的信息。 特别需要指出的是, 由 pcap_findalldevs_ex() 返回的每一个 pcap_if 结构体,都包含一个 pcap_addr 结构体,这个结构体由如下元素组成:

    • 一个地址列表
    • 一个掩码列表 (each of which corresponds to an entry in the addresses list).
    • 一个广播地址列表 (each of which corresponds to an entry in the addresses list).
    • 一个目的地址列表 (each of which corresponds to an entry in the addresses list).

      

    #include "pcap.h"
    
    #ifndef WIN32
        #include <sys/socket.h>
        #include <netinet/in.h>
    #else
        #include <winsock.h>
    #endif
    
    
    // 函数原型
    void ifprint(pcap_if_t *d);
    char *iptos(u_long in);
    char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen);
    
    
    int main()
    {
      pcap_if_t *alldevs;
      pcap_if_t *d;
      char errbuf[PCAP_ERRBUF_SIZE+1];
      char source[PCAP_ERRBUF_SIZE+1];
    
      printf("Enter the device you want to list:\n"
                "rpcap://              ==> lists interfaces in the local machine\n"
                "rpcap://hostname:port ==> lists interfaces in a remote machine\n"
                "                          (rpcapd daemon must be up and running\n"
                "                           and it must accept 'null' authentication)\n"
                "file://foldername     ==> lists all pcap files in the give folder\n\n"
                "Enter your choice: ");
    
      fgets(source, PCAP_ERRBUF_SIZE, stdin);
      source[PCAP_ERRBUF_SIZE] = '\0';
    
      /* 获得接口列表 */
      if (pcap_findalldevs_ex(source, NULL, &alldevs, errbuf) == -1)
      {
        fprintf(stderr,"Error in pcap_findalldevs: %s\n",errbuf);
        exit(1);
      }
    
      /* 扫描列表并打印每一项 */
      for(d=alldevs;d;d=d->next)
      {
        ifprint(d);
      }
    
      pcap_freealldevs(alldevs);
    
      return 1;
    }
    
    
    
    /* 打印所有可用信息 */
    void ifprint(pcap_if_t *d)
    {
      pcap_addr_t *a;
      char ip6str[128];
    
      /* 设备名(Name) */
      printf("%s\n",d->name);
    
      /* 设备描述(Description) */
      if (d->description)
        printf("\tDescription: %s\n",d->description);
    
      /* Loopback Address*/
      printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
    
      /* IP addresses */
      for(a=d->addresses;a;a=a->next) {
        printf("\tAddress Family: #%d\n",a->addr->sa_family);
      
        switch(a->addr->sa_family)
        {
          case AF_INET:
            printf("\tAddress Family Name: AF_INET\n");
            if (a->addr)
              printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
            if (a->netmask)
              printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
            if (a->broadaddr)
              printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
            if (a->dstaddr)
              printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
            break;
    
          case AF_INET6:
            printf("\tAddress Family Name: AF_INET6\n");
            if (a->addr)
              printf("\tAddress: %s\n", ip6tos(a->addr, ip6str, sizeof(ip6str)));
           break;
    
          default:
            printf("\tAddress Family Name: Unknown\n");
            break;
        }
      }
      printf("\n");
    }
    
    
    
    /* 将数字类型的IP地址转换成字符串类型的 */
    #define IPTOSBUFFERS    12
    char *iptos(u_long in)
    {
        static char output[IPTOSBUFFERS][3*4+3+1];
        static short which;
        u_char *p;
    
        p = (u_char *)&in;
        which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);
        sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
        return output[which];
    }
    
    char* ip6tos(struct sockaddr *sockaddr, char *address, int addrlen)
    {
        socklen_t sockaddrlen;
    
        #ifdef WIN32
        sockaddrlen = sizeof(struct sockaddr_in6);
        #else
        sockaddrlen = sizeof(struct sockaddr_storage);
        #endif
    
    
        if(getnameinfo(sockaddr, 
            sockaddrlen, 
            address, 
            addrlen, 
            NULL, 
            0, 
            NI_NUMERICHOST) != 0) address = NULL;
    
        return address;
    }
    
    

      代码里面都有解释,很好理解,而这些API函数如果不明白,都可以MSDN就行啦!

    3、打开适配器并捕获数据包(利用回调函数实现数据包捕获)

      现在,我们已经知道如何获取适配器的信息了,那我们就开始一项更具意义的工作,打开适配器并捕获数据包。在这讲中,我们会编写一个程序,将每一个通过适配器的数据包打印出来。

      打开设备的函数是 pcap_open()。下面是参数 snaplen, flagsto_ms 的解释说明。

    snaplen 制定要捕获数据包中的哪些部分。 在一些操作系统中 (比如 xBSD 和 Win32), 驱动可以被配置成只捕获数据包的初始化部分: 这样可以减少应用程序间复制数据的量,从而

        提高捕获效率。本例中,我们将值定为65535,它比我们能遇到的最大的MTU还要大。因此,我们确信我们总能收到完整的数据包。

    flags:  最最重要的flag是用来指示适配器是否要被设置成混杂模式。 一般情况下,适配器只接收发给它自己的数据包, 而那些在其他机器之间通讯的数据包,将会被丢弃。 相反,如果适

        配器是混杂模式,那么不管这个数据包是不是发给我的,我都会去捕获。也就是说,我会去捕获所有的数据包。 这意味着在一个共享媒介(比如总线型以太网),WinPcap能捕获其他

        主机的所有的数据包。 大多数用于数据捕获的应用程序都会将适配器设置成混杂模式,所以,我们也会在下面的范例中,使用混杂模式。

    to_ms 指定读取数据的超时时间,以毫秒计(1s=1000ms)。在适配器上进行读取操作(比如用 pcap_dispatch() 或 pcap_next_ex()) 都会在 to_ms 毫秒时间内响应,即使在网络上没

        有可用的数据包。 在统计模式下,to_ms 还可以用来定义统计的时间间隔。 将 to_ms 设置为0意味着没有超时,那么如果没有数据包到达的话,读操作将永远不会返回。 如果设

        置成-1,则情况恰好相反,无论有没有数据包到达,读操作都会立即返回。

     

    来看看下面的代码,利用回调函数实现数据捕获。

    #include "pcap.h"
    
    /* packet handler 函数原型 */
    void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
    
    main()
    {
    pcap_if_t *alldevs;
    pcap_if_t *d;
    int inum;
    int i=0;
    pcap_t *adhandle;
    char errbuf[PCAP_ERRBUF_SIZE];
        
        /* 获取本机设备列表 */
        if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
        {
            fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
            exit(1);
        }
        
        /* 打印列表 */
        for(d=alldevs; d; d=d->next)
        {
            printf("%d. %s", ++i, d->name);
            if (d->description)
                printf(" (%s)\n", d->description);
            else
                printf(" (No description available)\n");
        }
        
        if(i==0)
        {
            printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
            return -1;
        }
        
        printf("Enter the interface number (1-%d):",i);
        scanf("%d", &inum);
        
        if(inum < 1 || inum > i)
        {
            printf("\nInterface number out of range.\n");
            /* 释放设备列表 */
            pcap_freealldevs(alldevs);
            return -1;
        }
        
        /* 跳转到选中的适配器 */
        for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
        
        /* 打开设备 */
        if ( (adhandle= pcap_open(d->name,          // 设备名
                                  65536,            // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容
    							     PCAP_OPENFLAG_PROMISCUOUS,    // 混杂模式
                                  1000,             // 读取超时时间
                                  NULL,             // 远程机器验证
                                  errbuf            // 错误缓冲池
                                  ) ) == NULL)
        {
            fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
            /* 释放设备列表 */
            pcap_freealldevs(alldevs);
            return -1;
        }
        
        printf("\nlistening on %s...\n", d->description);
        
        /* 释放设备列表 */
        pcap_freealldevs(alldevs);
        
        /* 开始捕获 */
        pcap_loop(adhandle, 0, packet_handler, NULL);
        
        return 0;
    }
    
    
    /* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */
    void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
    {
        struct tm *ltime;
        char timestr[16];
        time_t local_tv_sec;
        
        /* 将时间戳转换成可识别的格式 */
        local_tv_sec = header->ts.tv_sec;
        ltime=localtime(&local_tv_sec);
        strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
        
        printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
        
    }
    
    
    

     待续......

  • 相关阅读:
    Python 爬虫简介
    Python 线程池(小节)
    Python platform 模块
    Python term 模块
    python 统计使用技巧
    ArcGIS中的WKID(转)
    c#二维码资料
    How to remove live visual tree?
    新书预告 ArcGIS跨平台开发系列第一本
    visual studio 中删除多余的空白行
  • 原文地址:https://www.cnblogs.com/yingfang18/p/1889596.html
Copyright © 2020-2023  润新知