(原创)WinpCap的详解(一)

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

  它有如下几个功能:

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

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

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

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

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

2010112712533229.jpg

  这里面重要一点,需要提醒的是: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);elseprintf(" (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 #include 
#else#include 
#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 *)∈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 WIN32sockaddrlen = sizeof(struct sockaddr_in6);#elsesockaddrlen = sizeof(struct sockaddr_storage);#endifif(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);elseprintf(" (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);}

 待续......

转载于:https://www.cnblogs.com/yingfang18/archive/2010/11/27/1889596.html


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部