WinPcap 中文技术文档(4.1.2) 第三章
1. WinPcap教程:循序渐进教您使用WinPcap
本节将向您显示如何使用WinPcapAPI的一些特性。本教程被组织成一系列课程,以循序渐进的方式,让读者从最基本的部分(获得设备列表)到最复杂的部分(控制发送队列并收集和统计网络流量)来了解如何使用WinPcap进行程序开发。
我们会提供几个简单但是完整的程序(代码片断)作为参考:所有的源代码中都包含一些指向手册其他地方的链接,这可以让您很方便地通过点击函数和数据结构跳转到相关的文档处。
范例程序都是使用纯C语言编写的, 所以掌握基本的C语言编程知识是必须的,而且这是一个关于处理“原始”网络数据包的教程,所以我们希望读者拥有良好的网络及网络协议的基本知识。
1.1. 获取设备列表通常,编写基于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.2. 获取已安装设备的高级信息在第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 <sys/socket.h>
#include <netinet/in.h>
#else
#include <winsock.h>
#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;
}
1.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"
/* 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() 函数。有关这个函数的使用,我们将在下一讲为您展示。 (不用回调方法捕获数据包).
1.4. 不用回调方法捕获数据包这节课的范例程序实现的功能和效果和上一课的非常相似 (打开适配器并且捕获数据包)。但本讲将使用 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的情况下,会返回不同的值。
1.5. 过滤数据包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;
}
如果你想阅读本课中显示的过滤函数的一些代码,请阅读下一课中的“分析数据包”例子。
1.6. 分析数据包现在,我们可以捕捉并且过滤网络流量了,那就让我们学以致用,来完成一个简单使用的程序吧!
在本讲中,我们将会利用上一讲的一些代码,来建立一个更实用的程序。 本程序的主要目标是显示如何解析所捕获的数据包的协议首部。这个程序可以称为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行中的每一行分别代表了一个数据包。
1.7. 处理脱机输出文件在本讲中,我们将学习如何处理捕获到文件中的数据包。 WinPcap提供了很多函数来将网络数据流保存到文件并读取它们。本讲将教你如何使用所有这些函数。我们还将看到如何使用WinPcap内核输出特性来获取一个高性能的输出(请注意:此时,由于一些有关新内核缓冲的问题,可能无法使用这些特性) 。
输出文件的格式是libpcap的一种。这种格式包含捕捉到的数据包的二进制数据,并且这种格式是许多网络工具所使用的一种标准,这些工具包括WinDump,Etheral和Snort。
1.7.1. 保存数据包到输出文件首先,让我们看一下如何以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() 函数中的参数是一一对应的。
1.7.2. 从输出文件中读取数据包既然我们有了一个可用的输出文件,那我们就能读取它的内容。 以下代码将打开一个WinPcap/libpcap的输出文件,并显示文件中每一个包的信息。输出文件通过 pcap_open_offline() 打开,然后我们通常使用 pcap_loop() 来有序地获取数据包。你可以看到,从脱机文件中读取数据包和从物理接口中接收它们是很相似的。
这个例子还会介绍另一个函数:pcap_createsrcsrc()。这个函数用于创建一个源字符串,这个源字符串以一个标志开头,这个标志用于告诉WinPcap这个源的类型。比如,使用“rpcap://”标志来打开一个适配器,使用“file://”来打开一个文件。如果pcap_findalldevs_ex() 已经被使用,那么这部是不需要的,因为其返回值已经包含了这些字符串。然而,在这个例子中我们需要它。因为文件的名字来自于用户的输入。
#include<stdio.h>
#include<pcap.h>
#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_PROMISCUOUS, // 混杂模式
1000, // 读取超时时间
NULL, // 远程机器验证
errbuf // 错误缓冲池
) ) == NULL)
{
fprintf(stderr,"\nUnable to openthe file %s.\n", source);
return -1;
}
// 读取并解析数据包,直到EOF为真
pcap_loop(fp, 0, dispatcher_handler, NULL);
return 0;
}
voiddispatcher_handler(u_char *temp1, const struct pcap_pkthdr *header, constu_char *pkt_data)
{
u_int i=0;
/* 打印pkt时间戳和pkt长度 */
printf("%ld:%ld (%ld)\n",header->ts.tv_sec, header->ts.tv_usec, header->len);
/* 打印数据包 */
for (i=1; (i < header->caplen + 1 ) ;i++)
{
printf("%.2x ",pkt_data[i-1]);
if ( (i % LINE_LEN) == 0)printf("\n");
}
printf("\n\n");
}
下面的程序同样实现了如上功能,只是我们使用了 pcap_next_ex() 函数来代替需要进行回调的 pcap_loop() 。
#include<stdio.h>
#include<pcap.h>
#define LINE_LEN16
int main(intargc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
struct pcap_pkthdr *header;
const u_char *pkt_data;
u_int i=0;
int res;
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_PROMISCUOUS, // 混杂模式
1000, // 读取超时时间
NULL, // 远程机器验证
errbuf // 错误缓冲池
) ) == NULL)
{
fprintf(stderr,"\nUnable to openthe file %s.\n", source);
return -1;
}
/* 从文件获取数据包 */
while((res = pcap_next_ex( fp, &header,&pkt_data)) >= 0)
{
/* 打印pkt时间戳和pkt长度 */
printf("%ld:%ld (%ld)\n",header->ts.tv_sec, header->ts.tv_usec, header->len);
/* 打印数据包 */
for (i=1; (i < header->caplen + 1) ; i++)
{
printf("%.2x ",pkt_data[i-1]);
if ( (i % LINE_LEN) == 0)printf("\n");
}
printf("\n\n");
}
if (res == -1)
{
printf("Error reading the packets:%s\n", pcap_geterr(fp));
}
return 0;
}
1.7.3. 使用pcap_live_dump将包写入堆文件注意: 此时,由于新内核缓冲的一些问题,这个特性可能不可用。
WinPcap的最近几个版本提供了一个更好的途径来把数据流保存到磁盘,那就是pcap_live_dump() 函数。 pcap_live_dump()函数有3个参数:文件名、文件最大的大小(字节为单位)和文件可以允许存储的数据包的最大数量,0表示没有限制。注意,在调用 pcap_live_dump()将数据流保存下来之前,程序可以设置过滤器(使用 pcap_setfilter(),详情请参见教程的“过滤数据包”这部分) ,这样我们就可以定义要保存的那部分数据流了。
pcap_live_dump()不会被阻塞, 因此它开始输出处理后会立即返回。 输出处理以异步的方式进行,直到文件达到最大的大小或者存储的数据包达到最大数量。
应用程序可以使用pcap_live_dump_ended()来检查数据是否存储完毕。特别注意:sync参数必须是非零的,如果它们是0,那么程序将永远被阻塞。
/*
* 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<stdlib.h>
#include<stdio.h>
#include<pcap.h>
#error At themoment the kernel dump feature is not supported in the driver
int main(intargc, char **argv) {
pcap_if_t *alldevs, *d;
pcap_t *fp;
u_int inum, i=0;
char errbuf[PCAP_ERRBUF_SIZE];
printf("kdump: saves the networktraffic to file using WinPcap kernel-level dump faeature.\n");
printf("\t Usage: %s [adapter] |dump_file_name max_size max_packs\n", argv[0]);
printf("\t Where: max_size is themaximum size that the dump file will reach (0 means no limit)\n");
printf("\t Where: max_packs is themaximum number of packets that will be saved (0 means no limit)\n\n");
if(argc < 5){
/* 用户没有提供数据源。获取设备列表 */
if (pcap_findalldevs(&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 outof range.\n");
/* 释放列表 */
return -1;
}
/* 跳转到所选的设备 */
for(d=alldevs, i=0; i< inum-1;d=d->next, i++);
/* 打开设备 */
if ( (fp = pcap_open_live(d->name,100, 1, 20, errbuf) ) == NULL)
{
fprintf(stderr,"\nErroropening adapter\n");
return -1;
}
/* 释放设备列表 */
pcap_freealldevs(alldevs);
/* 开始堆过程 */
if(pcap_live_dump(fp, argv[1],atoi(argv[2]), atoi(argv[3]))==-1){
printf("Unable to start thedump, %s\n", pcap_geterr(fp));
return -1;
}
}
else{
/* 打开设备 */
if ( (fp= pcap_open_live(argv[1], 100,1, 20, errbuf) ) == NULL)
{
fprintf(stderr,"\nErroropening adapter\n");
return -1;
}
/* 开始堆过程 */
if(pcap_live_dump(fp, argv[0],atoi(argv[1]), atoi(argv[2]))==-1){
printf("Unable to start thedump, %s\n", pcap_geterr(fp));
return -1;
}
}
/* 等待,知道堆过程完成,也就是当数据到达max_size或max_packs的时候 */
pcap_live_dump_ended(fp, TRUE);
/* 关闭适配器,这样,就可以将数据立刻输出到文件了 */
pcap_close(fp);
return 0;
}
pcap_live_dump()和 pcap_dump()的区别,除了可以设置限制之外,就是运行结果。pcap_live_dump() 使用了WinPcap NPF驱动自带的功能(详情请参见 NPF驱动核心手册) ,在内核级来写输出文件,并将上下文交换的数量和内存拷贝的数量最小化。
显然,这个特性目前并不能应用于其它操作系统,因为 pcap_live_dump() 是WinPcap的特性之一,并且只运行于Win32平台下。
1.8. 发送数据包尽管从 WinPcap 的名字上看这个库的目标应该是数据捕捉(Packet Capture),然而它也提供了很多其它有用的特性。其中,用户可以找到一组很完整的用于发送数据包的函数。
请注意:原始的libpcap库是不支持发送数据包的,因此这里展示的函数都属于是WinPcap的扩展,并且它们不能运行于Unix平台下。
1.8.1. 使用 pcap_sendpacket() 发送单个数据包下面的代码展示了发送一个数据包的最简单的方式。打开适配器以后,调用 pcap_sendpacket() 来发送手工制作的数据包。pcap_sendpacket() 的参数中有一个要包含发送数据的缓冲区,缓冲的长度,以及用来发送数据的适配器。注意,缓冲数据将直接发送到网络,而不会进行任何加工和处理。这就意味着应用程序需要创建一个正确的协议首部,为了使这个数据包更有意义。
#include<stdlib.h>
#include<stdio.h>
#include<pcap.h>
void main(intargc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
u_char packet[100];
int i;
/* 检查命令行参数的合法性 */
if (argc != 2)
{
printf("usage: %s interface (e.g.'rpcap://eth0')", argv[0]);
return;
}
/* 打开输出设备 */
if ( (fp= pcap_open(argv[1], // 设备名
100, // 要捕获的部分 (只捕获前100个字节)
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式
1000, // 读超时时间
NULL, // 远程机器验证
errbuf // 错误缓冲
) ) == NULL)
{
fprintf(stderr,"\nUnable to openthe adapter. %s is not supported by WinPcap\n", argv[1]);
return;
}
/* 假设在以太网上,设置MAC的目的地址为1:1:1:1:1:1 */
packet[0]=1;
packet[1]=1;
packet[2]=1;
packet[3]=1;
packet[4]=1;
packet[5]=1;
/* 设置MAC源地址为2:2:2:2:2:2 */
packet[6]=2;
packet[7]=2;
packet[8]=2;
packet[9]=2;
packet[10]=2;
packet[11]=2;
/* 填充剩下的内容 */
for(i=12;i<100;i++)
{
packet[i]=i%256;
}
/* 发送数据包 */
if (pcap_sendpacket(fp, packet, 100 /* size*/) != 0)
{
fprintf(stderr,"\nError sendingthe packet: \n", pcap_geterr(fp));
return;
}
return;
}
1.8.2. 发送队列pcap_sendpacket()提供了一种简单而直接的方法来发送单个数据包,而“发送队列”则提供了一种高级的、强大的、结构更优的方法来发送一组数据包。发送队列是一个容器,它能容纳不同数量的数据包,这些数据包将被发送到网络上。队列有大小,它代表了它能存储的数据包的最大数量。
发送队列通过调用pcap_sendqueue_alloc() 函数创建,并且需要指定队列的大小。
一旦发送队列被创建,pcap_sendqueue_queue() 就可以将数据包添加到发送队列中。这个函数的参数包含一个pcap_pkthdr 的结构体,它包含时间戳和长度,同时,参数还包含一个指向数据包数据的缓冲。这些参数和那些被 pcap_next_ex() 和 pcap_handler()接收到的数据相同,因此,为那些刚刚捕获到的或是从文件读取出来的数据包排队,就相当于把三个参数传递给pcap_sendqueue_queue()。
WinPcap提供了pcap_sendqueue_transmit()函数来发送一个队列。请注意第三个参数:如果非零,那么发送过程将是同步进行,也就是说,只有时间戳相符的数据包才会被处理。这个操作需要消耗大量的CPU资源,因为同步操作由内核驱动中的“忙等 (busy wait)”循环来实现的。尽管这个操作对CPU的要求很高,但它对包传送的处理结果,通常是很精确的。(通常在数微秒左右,或更小)
请注意,使用pcap_sendqueue_transmit() 要比pcap_sendpacket() 来发送一系列数据更加有效率,因为发送队列保存在内核级的缓冲区,因此,减少了上下文交换的次数。
当队列不再需要时,我们可以使用pcap_sendqueue_destroy() 来释放它所占用的内存。
下一个程序将演示如何使用发送队列。先用pcap_open_offline() 打开一个捕获文件,然后,将文件中的数据包移到已分配的发送队列。这时就可以发送队列了,如果用户指定了同步,那么它将同步发送队列。
注意,输出文件的链路层将会那些发送数据包的接口中的一个进行比较,那些接口使用 pcap_datalink() 发送数据包。当比较的结果不相同,那么就会打印出警告信息。捕获文件的链路层和适配器的链路层相一致是非常重要的,不然,发送将变得毫无意义。
/*
* 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<stdlib.h>
#include<stdio.h>
#include<pcap.h>
#include<remote-ext.h>
void usage();
void main(intargc, char **argv)
{
pcap_t *indesc,*outdesc;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
FILE *capfile;
int caplen, sync;
u_int res;
pcap_send_queue *squeue;
struct pcap_pkthdr *pktheader;
u_char *pktdata;
float cpu_time;
u_int npacks = 0;
/* 检查命令行参数的合法性 */
if (argc <= 2 || argc >= 5)
{
usage();
return;
}
/* 获取捕获文件长度 */
capfile=fopen(argv[1],"rb");
if(!capfile){
printf("Capture file notfound!\n");
return;
}
fseek(capfile , 0, SEEK_END);
caplen= ftell(capfile)- sizeof(structpcap_file_header);
fclose(capfile);
/* 检查时间戳是否合法 */
if(argc == 4 && argv[3][0] == 's')
sync = TRUE;
else
sync = FALSE;
/* 开始捕获 */
/* 根据WinPcap的新语法创建一个源字符串*/
if ( pcap_createsrcstr( source, // 源字符串
PCAP_SRC_FILE, // 我们要打开的文件
NULL, // 远程主机
NULL, // 远程主机的端口
argv[1], // 我们要打开的文件名
errbuf // 错误缓冲
) != 0)
{
fprintf(stderr,"\nError creating asource string\n");
return;
}
/* 打开捕获文件 */
if ( (indesc= pcap_open(source, 65536,PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to open thefile %s.\n", source);
return;
}
/* 打开要输出的适配器 */
if ( (outdesc= pcap_open(argv[2], 100,PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to openadapter %s.\n", source);
return;
}
/* 检查MAC的类型 */
if (pcap_datalink(indesc) !=pcap_datalink(outdesc))
{
printf("Warning: the datalink ofthe capture differs from the one of the selected interface.\n");
printf("Press a key to continue,or CTRL+C to stop.\n");
getchar();
}
/* 分配发送队列 */
squeue = pcap_sendqueue_alloc(caplen);
/* 从文件中将数据包填充到发送队列 */
while ((res = pcap_next_ex( indesc,&pktheader, &pktdata)) == 1)
{
if (pcap_sendqueue_queue(squeue,pktheader, pktdata) == -1)
{
printf("Warning: packet buffertoo small, not all the packets will be sent.\n");
break;
}
npacks++;
}
if (res == -1)
{
printf("Corrupted inputfile.\n");
pcap_sendqueue_destroy(squeue);
return;
}
/* 发送队列 */
cpu_time = (float)clock ();
if ((res = pcap_sendqueue_transmit(outdesc,squeue, sync)) < squeue->len)
{
printf("An error occurred sendingthe packets: %s. Only %d bytes were sent\n", pcap_geterr(outdesc), res);
}
cpu_time = (clock() - cpu_time)/CLK_TCK;
printf ("\n\nElapsed time:%5.3f\n", cpu_time);
printf ("\nTotal packets generated =%d", npacks);
printf ("\nAverage packets per second= %d", (int)((double)npacks/cpu_time));
printf ("\n");
/* 释放发送队列 */
pcap_sendqueue_destroy(squeue);
/* 关闭输入文件 */
pcap_close(indesc);
/*
* 释放输出适配器
* IMPORTANT: 记得一定要关闭适配器,不然就不能保证
* 所有的数据包都回被发送出去
*/
pcap_close(outdesc);
return;
}
void usage()
{
printf("\nSendcap, sends alibpcap/tcpdump capture file to the net. Copyright (C) 2002 LorisDegioanni.\n");
printf("\nUsage:\n");
printf("\t sendcap file_name adapter[s]\n");
printf("\nParameters:\n");
printf("\nfile_name: the name of thedump file that will be sent to the network\n");
printf("\nadapter: the device to use.Use \"WinDump -D\" for a list of valid devices\n");
printf("\ns: if present, forces thepackets to be sent synchronously, i.e. respecting the timestamps in the dumpfile. This option will work only under Windows NTx.\n\n");
exit(0);
}
1.9. 收集并统计网络流量这一讲,我们将展示WinPcap的另一个高级特性:收集并统计网络流量。统计引擎利用了内核级的数据包过滤器,来有效地为收集到的数据包进行分类。如果你想阅读更多细节,请参阅NPF驱动核心手册。
为了使用这个特性,编程人员必须打开一个适配器,并且可以使用 pcap_setmode() 将它设置为统计模式(statistical mode)。特别注意,必须使用MODE_STAT来作为这个函数的mode参数。
在统计模式下,编写一个用于监听TC网络流量的程序并不复杂,代码也不多。下面的范例程序将展示如何实现这个程序。
/*
* Copyright (c) 1999 - 2005 NetGroup, Politecnicodi 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<stdlib.h>
#include<stdio.h>
#include<pcap.h>
void usage();
voiddispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
void main(intargc, char **argv)
{
pcap_t *fp;
charerrbuf[PCAP_ERRBUF_SIZE];
struct timevalst_ts;
u_int netmask;
structbpf_program fcode;
/* 检查命令行参数的合法性 */
if (argc != 2)
{
usage();
return;
}
/* 打开输出适配器 */
if ( (fp= pcap_open(argv[1], 100,PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to openadapter %s.\n", errbuf);
return;
}
/* 不用关心掩码,在这个过滤器中,它不会被使用*/
netmask=0xffffff;
// 编译过滤器
if (pcap_compile(fp, &fcode,"tcp", 1, netmask) <0 )
{
fprintf(stderr,"\nUnable tocompile the packet filter. Check the syntax.\n");
/* 释放设备列表 */
return;
}
//设置过滤器
if (pcap_setfilter(fp, &fcode)<0)
{
fprintf(stderr,"\nError settingthe filter.\n");
pcap_close(fp);
/* 释放设备列表 */
return;
}
/* 将接口设置为统计模式 */
if (pcap_setmode(fp, MODE_STAT)<0)
{
fprintf(stderr,"\nError setting themode.\n");
pcap_close(fp);
/* 释放设备列表 */
return;
}
printf("TCP traffic summary:\n");
/* 开始主循环 */
pcap_loop(fp, 0, dispatcher_handler,(PUCHAR)&st_ts);
pcap_close(fp);
return;
}
voiddispatcher_handler(u_char *state, const struct pcap_pkthdr *header, constu_char *pkt_data)
{
struct timeval *old_ts = (struct timeval*)state;
u_int delay;
LARGE_INTEGER Bps,Pps;
struct tm *ltime;
char timestr[16];
time_t local_tv_sec;
/* 以毫秒计算上一次采样的延迟时间 */
/* 这个值通过采样到的时间戳获得 */
delay=(header->ts.tv_sec -old_ts->tv_sec) * 1000000 - old_ts->tv_usec + header->ts.tv_usec;
/* 获取每秒的比特数b/s */
Bps.QuadPart=(((*(LONGLONG*)(pkt_data + 8))* 8 * 1000000) / (delay));
/* ^ ^
| |
| |
| |
将字节转换成比特-- | |
|
延时是以毫秒表示的 --
*/
/* 得到每秒的数据包数量 */
Pps.QuadPart=(((*(LONGLONG*)(pkt_data)) *1000000) / (delay));
/* 将时间戳转化为可识别的格式 */
local_tv_sec = header->ts.tv_sec;
ltime=localtime(&local_tv_sec);
strftime( timestr, sizeof timestr,"%H:%M:%S", ltime);
/* 打印时间戳*/
printf("%s ", timestr);
/* 打印采样结果 */
printf("BPS=%I64u ", Bps.QuadPart);
printf("PPS=%I64u\n",Pps.QuadPart);
//存储当前的时间戳
old_ts->tv_sec=header->ts.tv_sec;
old_ts->tv_usec=header->ts.tv_usec;
}
void usage()
{
printf("\nShows the TCP traffic load,in bits per second and packets per second.\nCopyright (C) 2002 LorisDegioanni.\n");
printf("\nUsage:\n");
printf("\t tcptop adapter\n");
printf("\t You can use \"WinDump-D\" if you don't know the name of your adapters.\n");
exit(0);
}
在启动统计模式前,用户需要设置一个过滤器,以定义要监听的数据流。详细内容请参考过滤表达式语法 。如果没有设置过滤器,那么,所有的数据流量都将会被监听。
过程:
1)设置过滤器
2)调用pcap_setmode()
3)回调函数通过pcap_loop()被启动
接口描述符(the interfacedescriptor)开始工作在统计模式下。注意 pcap_open() 函数的第4个参数( to_ms ):它定义了统计残阳的时间间隔。回调函数将在每一个to_ms时间,收到由驱动发来并且计算好的采样数据。这些采样数据将通过回调函数的第2个和第3个参数传递,如下所示
它提供了两个64位的计数器,分别记录在最后一个时间间隔内,收到的数据包的数量和字节总数。
在这个范例中,适配器打开后的超时时间设置为1000毫秒。这就意味着dispatcher_handler()每隔1秒就会被调用一次。这里的过滤器被设置为只监视TCP包。然后,pcap_setmode() 和 pcap_loop() 被调用。注意,一个指向timeval结构的指针,作为user参数传递给函数 pcap_loop() 。这个结构体会被用来存储时间戳,以便计算两次采样的时间间隔。 dispatcher_handler() 会使用这个时间间隔来获得每秒的比特数(bps)以及每秒的数据包数量(pps),并将它们的值打印在屏幕上。
最后,我们想说,这个范例程序比传统的捕获和统计流量的程序都要高效,因为传统的程序都在用户层进行。静态模式需要最小的数据包拷贝和上下文交换,因此,CPU的性能会最优,而且,内存的需求量也会很少。