首先来百科一下Winpcap是一个什么东东。Winpcap(windows packet
capture)是windows平台下一个免费,公共的网络访问系统。
它有如下几个功能:
1、捕获原始数据包,包括在共享网络上各主机发送/接收的以及相互之间交换的数据;
2、在数据包发往应用程序之前,按照自定义的规则将某些特殊的数据包过滤掉;
3、在网络上发送原始的数据包;
4、收集网络通信过程中的统计信息。
从上面的功能来看,这个库文件提供了许多的API函数,可以让我们捕获网络上的数据包以及统计网络通信的信息。为了更直观的反应这个库文件的作用,我们来看看利用这个库文件写出来的一个应用软件,wireshark。界面如下图所示,这个界面只是捕获数据的一个小界面,里面有很多的设置,有兴趣可以下载一个试试。他能统计在一个局域网的所有网络信息。

这里面重要一点,需要提醒的是:winpcap的主要功能在于独立于主机协议(如TCP-IP)而发送和接收原始数据包。也就是说,winpcap不能阻塞,过滤或控制其他应用程序数据包的发收,它仅仅只是监听共享网络上传送的数据包。也就是说,WinpCap主要功能不能截取网络中的数据,他只能监听里面的数据。
对于WinpCap的结构以及原理,我们自然可以不用理会啦,我们只需要知道他的用途就行啦!
一、安装WinpCap
1、首先我们来看看如何安装WinpCap这个库,首先是下载WinpCap安装文件,这里有许多的版本,可以在官网上下载,http://www./,这里重点提醒一下,特别需要注意一下版本,如果你的版本是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 结构的链表,
每个这样的结构都包含了一个适配器的详细信息。值得注意的是,数据域 name 和 description
表示一个适配器名称和一个可以让人们理解的描述。
来看看下面简单的一个代码:
这个代码很简单首先利用一个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 , &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" " (rpcapd daemon must be up and running\n"
" and it must accept 'null' authentication)\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];
printf ( "%s\n" ,d->name);
if (d->description)
printf ( "\tDescription: %s\n" ,d->description);
printf ( "\tLoopback: %s\n" ,(d->flags & PCAP_IF_LOOPBACK)? "yes" : "no" );
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" );
}
#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 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, flags 和
to_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"
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,
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;
}
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);
}
|