时间:2021-07-01 10:21:17 帮助过:36人阅读
1.WinPcap教程:循序渐进教您使用WinPcap 本节将向您显示如何使用WinPcapAPI的一些特性。本教程被组织成一系列课程,以循序渐进的方式,让读者从最基本的部分(获得设备列表)到最复杂的部分(控制发送队列并收集和统计网络流量)来了解如何使用WinPcap进行程序开
本节将向您显示如何使用WinPcapAPI的一些特性。本教程被组织成一系列课程,以循序渐进的方式,让读者从最基本的部分(获得设备列表)到最复杂的部分(控制发送队列并收集和统计网络流量)来了解如何使用WinPcap进行程序开发。
我们会提供几个简单但是完整的程序(代码片断)作为参考:所有的源代码中都包含一些指向手册其他地方的链接,这可以让您很方便地通过点击函数和数据结构跳转到相关的文档处。
范例程序都是使用纯C语言编写的, 所以掌握基本的C语言编程知识是必须的,而且这是一个关于处理“原始”网络数据包的教程,所以我们希望读者拥有良好的网络及网络协议的基本知识。
通常,编写基于WinPcap应用程序的第一件事情就是获得已连接的网络适配器列表。libpcap和WinPcap都提供了 pcap_findalldevs_ex() 函数来实现这个功能: 这个函数返回一个pcap_if结构的链表, 每个pcap_if结构都包含一个适配器的详细信息。值得注意的是,数据域name和description分别表示一个适配器名称和一个人们可读的描述。
下列代码用于获取适配器列表,并在屏幕上显示出来,如果没有找到适配器,将打印一个错误信息。
#include"pcap.h"
int 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 inpcap_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 descriptionavailable)\n");
}
if (i == 0)
{
printf("\nNo interfaces found!Make sure WinPcap is installed.\n");
return;
}
/* 不再需要设备列表了,释放它 */
pcap_freealldevs(alldevs);
}
有关这段代码的一些说明。
首先,与其他libpcap函数一样,pcap_findalldevs_ex()有一个 errbuf 参数。一旦发生错误,由libpcap把错误描述写入到该字符串中。
第二,请记住不是所有的操作系统都支持libpcap提供的网络程序接口,因此,如果我们想编写一个可移植的应用程序,我们就必须考虑在什么情况下,description 是null。本程序中,我们遇到这种情况时,会打印一个提示语句“No description available”。
最后要记住,当我们完成了设备列表的使用,我们要调用 pcap_freealldevs() 函数将其占用的内存资源释放掉。
让我们编译并运行我们的第一个示例程序吧!为了能在Unix或Cygwin平台上编译这段程序,需要简单输入:
gcc -o testprog testprog.c -lpcap
在Windows平台上,您需要创建一个工程,并按照“使用WinPcap编程”里的步骤做。然而,我们建议您使用WinPcap developer's pack (详情请访问WinPcap网站,http://www.winpcap.org ), 因为它提供了很多已经配置好的范例,包括本教程中所有的示例代码,以及在编译运行时需要的包含文件(include) 和动态库文件(libraries)。
假设我们已经完成了对程序的编译,那让我们来运行它吧。在某台WinXP的电脑上,获得的结果是:
1.\Device\NPF_{4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS)Ethernet Adapter)
2.\Device\NPF_{5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)
正如您看到的,Windows平台下的网络适配器的名字(当打开设备的时候,把它传递给libpcap库)是相当不可读的,因此解释性的描述是非常有帮助阿!
在第1讲(获取设备列表) 中,我们展示了如何获取适配器的基本信息 (如设备的名称和描述)。事实上,WinPcap提供了其他更高级的信息。特别需要指出的是:由 pcap_findalldevs_ex() 返回的每一个pcap_if结构体,都包含一个pcap_addr的结构体,这个结构体由如下元素组成:
l 一个地址列表
l 一个掩码列表 (eachof which corresponds to an entry in the addresses list).
l 一个广播地址列表(each of which corresponds to an entry in the addresses list).
l 一个目的地址列表(each of which corresponds to an entry in the addresses list).
另外,函数pcap_findalldevs_ex()可以返回远程适配器信息和一个位于给定本地目录下pcap文件列表。
下面的范例使用ifprint()函数打印出 pcap_if结构体中所有的内容。程序对每一个由 pcap_findalldevs_ex()函数返回的pcap_if都调用ifprint()函数来实现打印。
/*
* Copyright (c) 1999 - 2005 NetGroup,Politecnico di Torino (Italy)
* Copyright (c) 2005 - 2006 CACE Technologies,Davis (California)
* All rights reserved.
*
* Redistribution and use in source and binaryforms, with or without
* modification, are permitted provided thatthe following conditions
* are met:
*
* 1. Redistributions of source code mustretain the above copyright
* notice, this list of conditions and thefollowing disclaimer.
* 2. Redistributions in binary form mustreproduce the above copyright
* notice, this list of conditions and thefollowing disclaimer in the
* documentation and/or other materialsprovided with the distribution.
* 3. Neither the name of the Politecnico diTorino, CACE Technologies
* nor the names of its contributors may beused to endorse or promote
* products derived from this software withoutspecific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHTHOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIEDWARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NOEVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODSOR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION)HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT,STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISINGIN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF SUCH DAMAGE.
*
*/
#include"pcap.h"
#ifndef WIN32
#include
#include
#else
#include
#endif
// 函数原型
voidifprint(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 tolist:\n"
"rpcap:// ==> lists interfaces in thelocal machine\n"
"rpcap://hostname:port ==>lists interfaces in a remote machine\n"
" (rpcapd daemon mustbe up and running\n"
" and it must accept'null' authentication)\n"
"file://foldername ==> lists all pcap files in the givefolder\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 inpcap_findalldevs: %s\n",errbuf);
exit(1);
}
/* 扫描列表并打印每一项 */
for(d=alldevs;d;d=d->next)
{
ifprint(d);
}
pcap_freealldevs(alldevs);
return 1;
}
/* 打印所有可用信息 */
voidifprint(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(((structsockaddr_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地址转换成字符串类型的*/
#defineIPTOSBUFFERS 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(structsockaddr_storage);
#endif
if(getnameinfo(sockaddr,
sockaddrlen,
address,
addrlen,
NULL,
0,
NI_NUMERICHOST) != 0) address = NULL;
return address;
}
现在,我们已经知道如何获取要使用的适配器,那就让我们开始真正的工作,打开一个适配器,然后捕获一些数据包。在这一课中,我们将编写一个程序,它把每一个通过适配器的数据包打印一些信息。
打开设备的函数是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"
/* packethandler 函数原型 */
voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char*pkt_data);
int main()
{
pcap_if_t*alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t*adhandle;
charerrbuf[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 descriptionavailable)\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 ofrange.\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 openthe 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都会自动调用这个回调函数*/
voidpacket_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);
}
一旦打开适配器,捕获工作就可以使用pcap_dispatch() 或 pcap_loop()进行。这两个函数非常的相似,区别就是pcap_ dispatch() 当超时时间到了(timeout expires)就返回 (尽管不能保证) ,但是 pcap_loop() 不会因此而返回,只有当 cnt 数据包被捕获才能返回,所以pcap_loop()会在一小段时间内,阻塞网络的使用。pcap_loop()对于我们这个简单的范例来说,可以满足需求,不过pcap_dispatch() 函数一般用于比较复杂的程序中。
这两个函数都有一个“回调”参数,packet_handler指向一个可以接收数据包的函数。这个函数会在收到每个新的数据包并收到一个通用状态时被libpcap所调用 (与函数 pcap_loop() 和 pcap_dispatch() 中的 user 参数相似),数据包的首部一般有一些诸如:时间戳,数据包长度的信息,还有包含了协议首部的实际数据。
注意:冗余校验码CRC不再支持,因为帧到达适配器,并经过校验确认以后,适配器就会将CRC删除,与此同时,大部分适配器会直接丢弃CRC错误的数据包,所以WinPcap没法捕获到它们。
上面的程序将每一个数据包的时间戳和长度从pcap_pkthdr的首部解析出来,然后打印在屏幕上。
请注意,使用 pcap_loop() 函数可能会遇到障碍,主要因为它直接由数据包捕获驱动所调用。因此,用户程序是不能直接控制它的。另一个实现方法(也是提高可读性的方法),是使用 pcap_next_ex() 函数。有关这个函数的使用,我们将在下一讲为您展示。 (不用回调方法捕获数据包).
这节课的范例程序实现的功能和效果和上一课的非常相似 (打开适配器并且捕获数据包)。但本讲将使用 pcap_next_ex() 函数代替上一讲的 pcap_loop()函数。
pcap_loop()函数是基于回调的原理来进行数据捕获,这是一种精妙的方法,并且在某些场合中,它是一种很好的选择。然而,处理回调有时候并不是很实用 -- 它会增加程序的复杂度,特别是在拥有多线程的C++程序中。
可以通过直接调用pcap_next_ex()函数来获得一个数据包 -- 只有当编程人员使用了 pcap_next_ex() 函数才能收到数据包。
这个函数的参数和捕获回调函数的参数是一样的-- 它包含一个网络适配器的描述符和两个可以初始化和返回给用户的指针 (一个指向 pcap_pkthdr 结构体,另一个指向数据报数据的缓冲)。
在下面的程序中,我们会再次用到上一讲中的有关回调方面的代码,只是我们将它放入了main()函数,之后调用 pcap_next_ex()函数。
#include"pcap.h"
int main()
{
pcap_if_t*alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t*adhandle;
int res;
charerrbuf[PCAP_ERRBUF_SIZE];
struct tm*ltime;
chartimestr[16];
structpcap_pkthdr *header;
const u_char*pkt_data;
time_tlocal_tv_sec;
/* 获取本机设备列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error inpcap_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 descriptionavailable)\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 ofrange.\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 openthe 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);
/* 获取数据包 */
while((res = pcap_next_ex( adhandle,&header, &pkt_data)) >= 0){
if(res == 0)
/* 超时时间到 */
continue;
/* 将时间戳转换成可识别的格式 */
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);
}
if(res == -1){
printf("Error reading the packets:%s\n", pcap_geterr(adhandle));
return -1;
}
return 0;
}
为什么我们要使用pcap_next_ex()代替以前的 pcap_next()?因为pcap_next() 有一些不好的地方。首先,它效率低下,尽管它隐藏了回调的方式,但它依然依赖于函数 pcap_dispatch()。第二,它不能检测到文件末尾这个状态(EOF),因此,如果数据包是从文件读取来的,那么它就不那么有用了。
值得注意的是,pcap_next_ex()在成功、超时、出错或EOF的情况下,会返回不同的值。
WinPcap和Libpcap的最强大的特性之一是拥有过滤数据包的引擎。它提供一种非常高效的方法接收网络中的某些数据包,这通常集成在WinPcap提供的捕获机制。过滤数据包的函数是pcap_compile() 和 pcap_setfilter() 。
pcap_compile()函数把一个高级的布尔过滤表达式编译生成一个能够被过滤引擎所解释的低层的字节码。有关布尔过滤表达式的语法可以参见“过滤表达式语法”这一节的内容。
pcap_setfilter()将一个过滤器与内核驱动中的捕获会话相关联。当pcap_setfilter()被调用时,这个过滤器将应用到来自网络的所有数据包,并且所有的符合要求的数据包 (即那些经过过滤器以后,布尔表达式为真的数据包) 将会立即复制给应用程序。
以下代码显示了如何编译和设置过滤器。 请注意,我们必须从pcap_if结构体中获得掩码,因为一些使用 pcap_compile() 创建的过滤器需要它。
在这段代码片断中,传递给pcap_compile()的过滤器是"ipand tcp",它的含义是“只希望保留IPv4和TCP的数据包,并且把他们发送给应用程序”。
if (d->addresses != NULL)
/* 获取接口第一个地址的掩码 */
netmask=((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* 如果这个接口没有地址,那么我们假设这个接口在C类网络中 */
netmask=0xffffff;
编译过滤器
if (pcap_compile(adhandle, &fcode,"ip and tcp", 1, netmask) < 0)
{
fprintf(stderr,"\nUnable tocompile the packet filter. Check the syntax.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
设置过滤器
if (pcap_setfilter(adhandle, &fcode)< 0)
{
fprintf(stderr,"\nError settingthe filter.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
如果你想阅读本课中显示的过滤函数的一些代码,请阅读下一课中的“分析数据包”例子。
现在,我们可以捕捉并且过滤网络流量了,那就让我们学以致用,来完成一个简单使用的程序吧!
在本讲中,我们将会利用上一讲的一些代码,来建立一个更实用的程序。 本程序的主要目标是显示如何解析所捕获的数据包的协议首部。这个程序可以称为UDPdump,打印一些网络上传输的UDP数据的信息。
我们选择解析和显示UDP协议,而不是TCP等其它协议,是因为它比其它的协议更简单,作为一个入门程序范例,这是很不错的选择。让我们看看代码:
/*
* Copyright (c) 1999 - 2005 NetGroup,Politecnico di Torino (Italy)
* Copyright (c) 2005 - 2006 CACE Technologies,Davis (California)
* All rights reserved.
*
* Redistribution and use in source and binaryforms, with or without
* modification, are permitted provided thatthe following conditions
* are met:
*
* 1. Redistributions of source code mustretain the above copyright
* notice, this list of conditions and thefollowing disclaimer.
* 2. Redistributions in binary form mustreproduce the above copyright
* notice, this list of conditions and thefollowing disclaimer in the
* documentation and/or other materialsprovided with the distribution.
* 3. Neither the name of the Politecnico diTorino, CACE Technologies
* nor the names of its contributors may beused to endorse or promote
* products derived from this software withoutspecific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHTHOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIEDWARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NOEVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODSOR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION)HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT,STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISINGIN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF SUCH DAMAGE.
*
*/
#include"pcap.h"
/* 4字节的IP地址 */
typedef structip_address{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* IPv4 首部 */
typedef structip_header{
u_char ver_ihl; // 版本 (4 bits) +首部长度 (4 bits)
u_char tos; // 服务类型(Type ofservice)
u_short tlen; // 总长(Total length)
u_short identification; // 标识(Identification)
u_short flags_fo; // 标志位(Flags) (3 bits) +段偏移量(Fragment offset) (13 bits)
u_char ttl; // 存活时间(Time tolive)
u_char proto; // 协议(Protocol)
u_short crc; // 首部校验和(Headerchecksum)
ip_address saddr; // 源地址(Sourceaddress)
ip_address daddr; // 目的地址(Destinationaddress)
u_int op_pad; // 选项与填充(Option+ Padding)
}ip_header;
/* UDP 首部*/
typedef structudp_header{
u_short sport; // 源端口(Source port)
u_short dport; // 目的端口(Destinationport)
u_short len; // UDP数据包长度(Datagramlength)
u_short crc; // 校验和(Checksum)
}udp_header;
/* 回调函数原型 */
voidpacket_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;
charerrbuf[PCAP_ERRBUF_SIZE];
u_int netmask;
charpacket_filter[] = "ip and udp";
structbpf_program fcode;
/* 获得设备列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error inpcap_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 descriptionavailable)\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 ofrange.\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 openthe adapter. %s is not supported by WinPcap\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/* 检查数据链路层,为了简单,我们只考虑以太网*/
if(pcap_datalink(adhandle) != DLT_EN10MB)
{
fprintf(stderr,"\nThis programworks only on Ethernet networks.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
if(d->addresses != NULL)
/* 获得接口第一个地址的掩码 */
netmask=((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* 如果接口没有地址,那么我们假设一个C类的掩码 */
netmask=0xffffff;
//编译过滤器
if (pcap_compile(adhandle, &fcode,packet_filter, 1, netmask) <0 )
{
fprintf(stderr,"\nUnable tocompile the packet filter. Check the syntax.\n");
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
//设置过滤器
if (pcap_setfilter(adhandle,&fcode)<0)
{
fprintf(stderr,"\nError settingthe filter.\n");
/* 释放设备列表 */
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所调用 */
voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char*pkt_data)
{
struct tm *ltime;
char timestr[16];
ip_header *ih;
udp_header *uh;
u_int ip_len;
u_short sport,dport;
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 ",timestr, header->ts.tv_usec, header->len);
/* 获得IP数据包头部的位置 */
ih = (ip_header *) (pkt_data +
14); //以太网头部长度
/* 获得UDP首部的位置 */
ip_len = (ih->ver_ihl & 0xf) * 4;
uh = (udp_header *) ((u_char*)ih + ip_len);
/* 将网络字节序列转换成主机字节序列 */
sport = ntohs( uh->sport );
dport = ntohs( uh->dport );
/* 打印IP地址和UDP端口 */
printf("%d.%d.%d.%d.%d ->%d.%d.%d.%d.%d\n",
ih->saddr.byte1,
ih->saddr.byte2,
ih->saddr.byte3,
ih->saddr.byte4,
sport,
ih->daddr.byte1,
ih->daddr.byte2,
ih->daddr.byte3,
ih->daddr.byte4,
dport);
}
首先,我们将过滤器设置成“ip andudp”。在这种方式下,我们确信packet_handler()只会收到基于IPv4的UDP数据包;这将简化解析过程,提高程序的效率。
我们还分别创建了用于描述IP首部和UDP首部的结构体。packet_handler()所使用的这些结构体会合理地定位到各个头部字段上。
packet_handler(),虽然只限于单个协议的解析(比如基于IPv4的UDP),不过它显示一个复杂的捕捉器(sniffers),就像TcpDump或WinDump一样,对网络数据流进行解码那样。因为我们对MAC首部不感兴趣,所以我们忽略了它。为了简化,在开始捕捉之前,使用了pcap_datalink() 对MAC层进行了检测,以确保我们是在处理一个以太网络。这样,我们就能确保MAC层的首部为14字节。
IP数据包的首部位于MAC首部的后面。我们将从IP数据包的首部解析到源IP地址和目的IP地址。
处理UDP的首部有一些复杂,因为IP数据包的首部的长度并不是固定不变的。然而,我们可以通过IP数据包的length字段来得到它的长度。一旦我们知道了UDP首部的位置,我们就能提取源端口和目的端口。
提取出来的值会打印在屏幕上,结果如下所示:
1.\Device\Packet_{A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter)
Enter the interface number (1-2):1
listening onXircom CardBus Ethernet 10/100 Adapter...
16:13:15.312784 len:87 130.192.31.67.2682-> 130.192.3.21.53
16:13:15.314796 len:137 130.192.3.21.53 ->130.192.31.67.2682
16:13:15.322101 len:78 130.192.31.67.2683-> 130.192.3.21.53
最后3行中的每一行分别代表了一个数据包。
在本讲中,我们将学习如何处理捕获到文件中的数据包。 WinPcap提供了很多函数来将网络数据流保存到文件并读取它们。本讲将教你如何使用所有这些函数。我们还将看到如何使用WinPcap内核输出特性来获取一个高性能的输出(请注意:此时,由于一些有关新内核缓冲的问题,可能无法使用这些特性) 。
输出文件的格式是libpcap的一种。这种格式包含捕捉到的数据包的二进制数据,并且这种格式是许多网络工具所使用的一种标准,这些工具包括WinDump,Etheral和Snort。
首先,让我们看一下如何以libpcap的格式把一个数据包写入到文件。
接下来的例子描述从一个选定的接口捕获数据包,然后将它们保存到用户指定的文件中。
#include"pcap.h"
/* 回调函数原型 */
voidpacket_handler(u_char *param, const struct pcap_pkthdr *header, const u_char*pkt_data);
int main(intargc, char **argv)
{
pcap_if_t*alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t*adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_dumper_t*dumpfile;
/* 检查程序输入参数 */
if(argc != 2)
{
printf("usage: %s filename",argv[0]);
return -1;
}
/* 获取本机设备列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error inpcap_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 descriptionavailable)\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 ofrange.\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 openthe adapter. %s is not supported by WinPcap\n", d->name);
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/* 打开输出文件 */
dumpfile = pcap_dump_open(adhandle,argv[1]);
if(dumpfile==NULL)
{
fprintf(stderr,"\nError openingoutput file\n");
return -1;
}
printf("\nlistening on %s... PressCtrl+C to stop...\n", d->description);
/* 释放设备列表 */
pcap_freealldevs(alldevs);
/* 开始捕获 */
pcap_loop(adhandle, 0, packet_handler,(unsigned char *)dumpfile);
return 0;
}
/* 回调函数,用来处理数据包*/
voidpacket_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char*pkt_data)
{
/* 保存数据包到输出文件 */
pcap_dump(dumpfile, header, pkt_data);
}
你可以看到,这个程序的结构和前面几讲的程序非常相似,它们的区别有:
1) 只有当接口打开时,调用pcap_dump_open()才是有效的。 这个调用将打开一个输出文件,并将它关联到特定的接口上。
2) 数据包将会通过pcap_dump() 函数写入输出文件中,这个函数是packet_handler()的回调函数。 pcap_dump() 的参数和 pcap_handler() 函数中的参数是一一对应的。
既然我们有了一个可用的输出文件,那我们就能读取它的内容。 以下代码将打开一个WinPcap/libpcap的输出文件,并显示文件中每一个包的信息。输出文件通过 pcap_open_offline() 打开,然后我们通常使用 pcap_loop() 来有序地获取数据包。你可以看到,从脱机文件中读取数据包和从物理接口中接收它们是很相似的。
这个例子还会介绍另一个函数:pcap_createsrcsrc()。这个函数用于创建一个源字符串,这个源字符串以一个标志开头,这个标志用于告诉WinPcap这个源的类型。比如,使用“rpcap://”标志来打开一个适配器,使用“file://”来打开一个文件。如果pcap_findalldevs_ex() 已经被使用,那么这部是不需要的,因为其返回值已经包含了这些字符串。然而,在这个例子中我们需要它。因为文件的名字来自于用户的输入。
#include
#include
#define LINE_LEN16
voiddispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
int main(intargc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
if(argc != 2){
printf("usage: %s filename",argv[0]);
return -1;
}
/* 根据新WinPcap语法创建一个源字符串 */
if ( pcap_createsrcstr( source, // 源字符串
PCAP_SRC_FILE, // 我们要打开的文件
NULL, // 远程主机
NULL, // 远程主机端口
argv[1], // 我们要打开的文件名
errbuf // 错误缓冲区
) != 0)
{
fprintf(stderr,"\nError creating asource string\n");
return -1;
}
/* 打开捕获文件 */
if ( (fp= pcap_open(source, // 设备名
65536, // 要捕捉的数据包的部分
//65535保证能捕获到不同数据链路层上的每个数据包的全部内容
PCAP_OPENFLAG_PROMISCU