循序渐进学WinPcap 循序渐进学WinPcap
去年开始学习winpcap,当时翻译了一点,现在打算把这个工作完成了。我的水平比较差,翻译的很不到位,不过对于初次接触winpcap的人应该还是有点帮助吧。不过不知道我这样来翻译是不是侵犯了人家的版权?如果有这个嫌疑,请大家告诉我,我对这方面的法律不是很了解。建议对这方面有兴趣的人还是去http://www.winpcap.org/">http://www.winpcap.org 下载文档和资料看。
下面开始吧:
WinPcap tutorial: a step by step guide to using WinPcap
详细说明
这部分展示了怎样使用WinPcap API。这个教程通过一系列的课程,从基本的函数(取得网卡列表,开始抓包,等等)到最高级的应用(处理数据包包发送队列和统计网络流量),一步一步地教会读者如何用WinPcap来编程。
这里提供了几个虽然简单但却完整的程序段作为参考:所有的源代码都有其余部分的链接,只需要点击一下函数和数据结构就可以跳转到相应的文档。
这些例子都是使用c语言写的,所以在读本教程前要了解一些基本的c语言的知识。而且,这是一个关于处理原始网络包的库的教程,所以假定读者具有良好的网络和网络协议方面的知识。
WinPcap tutorial: a step by step guide to using WinPcap(1)
获取网络设备列表
基本上所有基于Winpcap的应用程序所做的第一件事情都是获取一个已经绑定的网卡列表。为此,libcap和winpcap都提供了pcap_findalldevs_ex()函数:这个函数返回一个指向pcap_if结构的链表,其中的每一项都包含了一个已经绑定的适配器的全部信息。其中name和description这两项分别包含了相应设备的名称和描述。
下面的代码取得适配器列表并在屏幕上显示出来,如果适配器没有被发现就把显示错误。
#i nclude "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;
}
/* We don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
}
关于这段代码的说明。
首先,就象其他的libpcap函数,pcap_findalldevs_ex()有一个errbuf参数。如果发生错误,libcap就把一个错误说明放到这个参数指向的字符串中。
其次,并不是所有的操作系统都支持libpcap提供的网络接口描述,因此如果我们想写一个可移植的程序,我们必须考虑description为null的情况:这个时候我们就输出字符串"No description available" 。
最后要提醒一下:一旦我们完成了这些动作,就应该释放用pcap_freealldevs()列表。
让我们编译并运行这段简单的代码。在unix或者cygwin中编译的话,打入下面的命令:
gcc -o testaprog testprog.c -lpcap
在windows中,你需要创建一个project,照着手册中的Using WinPcap in your programs 那一章做就可以了。但是,我们建议你使用the WinPcap developer's pack(可以在http://www.winpcap.org/">http://www.winpcap.org 下载),因为这个开发包提供了许多教程中使用的代码示例,这些示例都已经配置好了,其中包含了编译执行例子所需要的include文件和lib文件。
编译好了程序后,在我的winxp工作站上运行的结果:
1. {4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter)
2. {5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)
就象你看到的一样,在windows系统中网络适配器的名称(在打开网络适配器时将被传递给libcap)根本没有办法理解,所以附加说明可能是非常有帮助的。
WinPcap tutorial: a step by step guide to using WinPcap(2)
Obtaining advanced information about installed devices
课程1(Obtaining the device list)说明了怎样得到可用适配器的基本信息(比如设备名和说明)。实际上,winpcap也提供其他的高级信息。每个由pcap_findalldevs_ex()返回的pcap_if 结构体都包含了一个pcap_addr结构列表,里面包含的内容有:
一个接口的地址列表。
一个子网掩码列表(每一个都与地址列表中的条目一一对应)。
一个广播地址列表(每一个都与地址列表中的条目一一对应)。
一个目的地址列表(每一个都与地址列表中的条目一一对应)。
除此之外,pcap_findalldevs_ex() 也能返回远程的适配器和任给的本地文件夹的pcap文件列表。
下面的例子提供了一个ifprint() 函数来打印出一个pcap_if 结构中的所有内容。程序对每一个pcap_findalldevs_ex()返回的条目都调用一次这个函数。
/*
* Copyright (c) 1999 - 2003
* NetGroup, Politecnico di Torino (Italy)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Politecnico di Torino nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 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) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#i nclude "pcap.h"
#ifndef WIN32
#i nclude <sys/socket.h>
#i nclude <netinet/in.h>
#else
#i nclude <winsock.h>
#endif
// Function prototypes
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';
/* Retrieve the interfaces list */
if (pcap_findalldevs_ex(source, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s/n",errbuf);
exit(1);
}
/* Scan the list printing every entry */
for(d=alldevs;d;d=d->next)
{
ifprint(d);
}
pcap_freealldevs(alldevs);
return 1;
}
/* Print all the available information on the given interface */
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");
}
/* From tcptraceroute, convert a numeric IP address to a string */
#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;
}
WinPcap tutorial: a step by step guide to using WinPcap(3)
Opening an adapter and capturing the packets
打开一个适配器开始抓取
现在我们已经知道了怎样去获取一个适配器并使用它,让我们开始真正的工作-----开始抓取网络数据包吧。在这一课中我们将写一个程序,这个程序将在我们选择的适配器上监听,并抓取通过这个适配器上的每一个数据包,打印其中的一些信息。
我们主要使用的函数是pcap_open(),这个函数的功能是打开一个抓取设备。在这里有必要对其中的几个参数snaplen, flags and to_ms作一下说明。
snaplen指定了我们所要抓取的包的长度(译者:也就是我们想抓多长就设置多长)。在一些操作系统(如xBSD和Win32)中,底层驱动可以通过配置只抓取数据包的开始部分:这样就减少了拷贝给应用程序的数据量,因此可以提高抓取效率。在这个例子里我们使用65536这个比我们所能遇到的最大的MTU还大的数字。这样我们就能确保我们的程序可以抓到整个数据包。
flags:最重要的标志是一个指示适配器是否工作在混杂模式下的。在正常状况下,一个适配器仅仅抓取网络中目的地是它自己的数据包;因此其他主机交换的数据包都被忽略。相反,当适配器处在混杂模式下的时候它就会抓取所有的数据包而不管是不是发给它的。这就意味着在共享媒体(如非交换的以太网)上,WinPcap将能够抓取其他主机的数据包。混杂模式是大部分抓取程序的默认模式,所以在下面的例子中我们就开启它。
to_ms以豪秒为单位指定了读取操作的超时界限。在适配器上一个读取操作(比如,pcap_dispatch() 或者 pcap_next_ex())将总是在to_ms豪秒后返回,即使网络中没有数据包可供抓取。如果适配器工作在统计模式(如果对此不了解,请看课程9),to_ms还定义了统计报告之间的间隔。把tm_ms设置为0意味着没有时间限制,如果没有数据包到达适配器,读取操作将永远不会返回。反过来,把tm_ms设置为-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];
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n", d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("/nlistening on %s.../n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
struct tm *ltime;
char timestr[16];
/* convert the timestamp to readable format */
ltime=localtime(&header->ts.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()一旦超时就可以返回(尽管不能保证)而pcap_loop() 会一直等到cnt包被抓到才会返回,所以这个函数在没有数据包的网络中会阻塞任意的时间。在这个例子中pcap_loop()就足够了,而pcap_dispatch() 则可以在一个更复杂的程序中使用。
这两个函数都有一个回调函数作为参数,packet_handler,这个参数指定的函数将收到数据包。这个函数在每一个新的数据包从网络中到达时都会被libpcap调用,并且会收到一个反映pcap_loop() 和 pcap_dispatch()函数的生成状态,和一个结构体header。这个header中带有一些数据包中的信息,比如时间戳和长度、包括所有协议头的实际数据包。注意结构体中是没有CRC的,因为在数据确认后已经被适配器去掉了。也要注意大部分适配器丢弃了CRC错误的数据包,因此Winpcap不能抓取这些包。
上面的例子从pcap_pkthdr中提取每个数据包的时间戳和长度并显示它们。
请注意使用pcap_loop()可能有一个缺点,就是使用这个函数时包处理函数要被包抓取驱动程序来调用;因此应用程序不能直接控制它。另一种方法(并且更容易理解)是使用函数pcap_next_ex(),这个函数我们将在下一个例子中使用。
4.
Capturing the packets without the callback
这节课程中的例子程序完成的功能和上节课的一样,但是使用的是pcap_next_ex()而不是pcap_loop().
基于回调捕获机制的 pcap_loop()是非常优雅的,在很多情况下都是一个不错的选择。不过,有时候处理一个回调函数显得不太现实 --- 通常这会使程序更加复杂,在使用多线程或者c++类的时候尤其如此。
在这种情况下,可以直接调用 pcap_next_ex() 来返回一个数据包 -- 这样程序员可以在仅仅想使用它们的时候再处理 pcap_next_ex() 返回的数据包。
这个函数的参数和回调函数 pcap_loop() 的一样 -- 由一个网络适配器描述符作为入口参数和两个指针作为出口参数,这两个指针将在函数中被初始化,然后再返回给用户(一个指向pcap_pkthdr 结构,另一个指向一个用作数据缓冲区的内存区域)。
在下面的程序中,我们继续使用上一节课中的例子的数据处理部分的代码,把这些代码拷贝到main()函数中pcap_next_ex()的后面。
#include "pcap.h"
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i=0;
pcap_t *adhandle;
int res;
char errbuf[PCAP_ERRBUF_SIZE];
struct tm *ltime;
char timestr[16];
struct pcap_pkthdr *header;
u_char *pkt_data;
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n", d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("/nlistening on %s.../n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* Retrieve the packets */
while((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0){
if(res == 0)
/* Timeout elapsed */
continue;
/* convert the timestamp to readable format */
ltime=localtime(&header->ts.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(译者注:end of file,意思是文件结束标志),所以当我们从一个文件中收集包的时候不是很有用(译者注:winpcap可以把捕获的数据包以很高的效率存在文件中,留待以后分析,这一点以后的课程中也会讲到)。
也要注意 pcap_next_ex() 对于成功调用、超时、错误和EOF状态会返回不同的值。
5.Filtering the traffic
WinPcap提供的最强大的特性之一就是过滤引擎。它是被集成到了winpcap的捕获机制中的,提供了一种非常高效的方法来获取部分网络数据。被用来过滤数据包的函数是 pcap_compile() 和 pcap_setfilter()。
pcap_compile() 接受一个包含布尔表达式的字符串,生成可以被捕获包驱动中的过滤引擎解释的代码。布尔表达式的语法在这个文档的Filtering expression syntax 那一节(译者注:其实和tcpdump的一样,如果了解tcpdump,可以直接按照tcpdump的语法来写)。
pcap_setfilter() 绑定一个过滤器到一个在核心驱动中的捕获进程中。一旦 pcap_setfilter() 被调用,这个过滤器就会对网络来的所有数据包进行过滤,所有符合条件的数据包(按照布尔表达式来计算出结果是真的数据包)都会被拷贝给进行捕获的应用程序。
下面的代码说明了怎样编译和设置一个过滤器。注意我们必须得到说明适配器的 pcap_if 结构中的子网掩码,因为一些被 pcap_compile() 生成的过滤器需要它。这个过滤器中传递给 pcap_compile() 的字符串是 "ip and tcp",意思是“仅仅把IPv4 and TCP 数据包保存下来并交付给应用程序”。
if (d->addresses != NULL)
/* Retrieve the mask of the first address of the interface */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* If the interface is without an address we suppose to be in a C class network */
netmask=0xffffff;
//compile the filter
if (pcap_compile(adhandle, &fcode, "ip and tcp", 1, netmask) < 0)
{
fprintf(stderr,"/nUnable to compile the packet filter. Check the syntax./n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
//set the filter
if (pcap_setfilter(adhandle, &fcode) < 0)
{
fprintf(stderr,"/nError setting the filter./n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
如果你想看一些在这节课中讲述的使用过滤功能的代码,请看下节课中的例子,Interpreting the packets.
6.Interpreting the packets.
现在我们已经能够捕获并且过滤网络数据包,下面我们就把我们的知识运用到一个简单的“真实的”应用程序中去。
在这节课中我们将从前面的课程中拷贝代码并用它们来构造出一个更有用途的程序。这个程序主要的目的就是说明怎样分析和解释我们已经捕获的数据包的协议结构。最终的应用程序,叫做UDPdump,会打印出一个在我们的网络中的UDP数据包的概要。
在开始阶段我们选择分析并显示UDP协议,因为UDP协议比其他的协议比如TCP协议更容易理解,从而非常适合作为初始阶段的例子。还是让我们开始看代码吧:
/*
* Copyright (c) 1999 - 2003
* NetGroup, Politecnico di Torino (Italy)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Politecnico di Torino nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 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) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "pcap.h"
/* 4 bytes IP address */
typedef struct ip_address{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* IPv4 header */
typedef struct ip_header{
u_char ver_ihl; // Version (4 bits) + Internet header length (4 bits)
u_char tos; // Type of service
u_short tlen; // Total length
u_short identification; // Identification
u_short flags_fo; // Flags (3 bits) + Fragment offset (13 bits)
u_char ttl; // Time to live
u_char proto; // Protocol
u_short crc; // Header checksum
ip_address saddr; // Source address
ip_address daddr; // Destination address
u_int op_pad; // Option + Padding
}ip_header;
/* UDP header*/
typedef struct udp_header{
u_short sport; // Source port
u_short dport; // Destination port
u_short len; // Datagram length
u_short crc; // Checksum
}udp_header;
/* prototype of the 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];
u_int netmask;
char packet_filter[] = "ip and udp";
struct bpf_program fcode;
/* Retrieve the device list */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the adapter */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // remote authentication
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Check the link layer. We support only Ethernet for simplicity. */
if(pcap_datalink(adhandle) != DLT_EN10MB)
{
fprintf(stderr,"/nThis program works only on Ethernet networks./n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
if(d->addresses != NULL)
/* Retrieve the mask of the first address of the interface */
netmask=((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
else
/* If the interface is without addresses we suppose to be in a C class network */
netmask=0xffffff;
//compile the filter
if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 )
{
fprintf(stderr,"/nUnable to compile the packet filter. Check the syntax./n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
//set the filter
if (pcap_setfilter(adhandle, &fcode)<0)
{
fprintf(stderr,"/nError setting the filter./n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("/nlistening on %s.../n", d->description);
/* At this point, we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
void packet_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;
/* convert the timestamp to readable format */
ltime=localtime(&header->ts.tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", ltime);
/* print timestamp and length of the packet */
printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);
/* retireve the position of the ip header */
ih = (ip_header *) (pkt_data +
14); //length of ethernet header
/* retireve the position of the udp header */
ip_len = (ih->ver_ihl & 0xf) * 4;
uh = (udp_header *) ((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
sport = ntohs( uh->sport );
dport = ntohs( uh->dport );
/* print ip addresses and udp ports */
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 and udp"。这样我们就能够确保 packet_handler() 函数只返回IPv4的UDP数据包:这可以简化分析工作,改善程序的效率。
在程序开始部分,我们已经建立了两个结构体,分别用来描述IP和UDP头。这两个结构体被 packet_handler() 函数用来正确定位IP和UDP头字段。
packet_handler(),虽然被限制为只能解析一个协议(IPv4的UDP),但是仍然可以说明那些象 tcpdump/WinDump 一样复杂的嗅探器怎样解析网络数据包的。因为我们对MAC头不感兴趣,所以我们就忽略它。为了简单起见,在开始捕获之前,我们用 pcap_datalink() 函数来检查MAC层,以确保我们正在处理的是以太网桢。这样我们就能够确保MAC头正好是14字节。(译者注:这里因为是直接把以太网桢加14来获得IP包的,所以要确保的却是以太网桢,不然就会产生错误。)
IP头就紧跟在MAC头后面。我们将从IP头中取得IP源地址和目的地址。
到达UDP头复杂了一点,因为IP头的长度是不固定的。因此,我们使用IP头的长度字段来确定它的大小。于是我们就知道了UDP头的位置,然后就可以取得源端口和目的端口。
我们把获取的数据打印到屏幕上,结果如下:
1. {A7FD048A-5D4B-478E-B3C1-34401AC3B72F} (Xircom t 10/100 Adapter)
Enter the interface number (1-2):1
listening on Xircom 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
后三行的每一行都代表了一个不同的数据包。
7.Handling offline dump files
在这节课中我们来学习怎样将数据包保存到一个文件中。Winpcap提供了一系列保存网络数据包到一个文件和从文件中读取保存内容的函数 -- 这节课就是讲述怎样使用这些函数的。同时也会展示怎样winpcap核心中的保存特性来获得高性能的存储(注意:现在,由于新的内核缓存的一些问题,这个特性已经不能使用了)。
dump文件的格式和libpcap的是一样的。在这个格式里捕获的数据包是用二进制的形式来保存的,现在已经作为标准被许多网络工具使用,其中就包括WinDump, Ethereal 和 Snort。
保存数据包到一个dump文件:
首先,让我们看看怎样用libpcap格式来写数据包。下面的例子从选择的网络接口中捕获数据并保存到一个由用户提供名字的文件中。
#include "pcap.h"
/* prototype of the packet handler */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
main(int argc, 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;
/* Check command line */
if(argc != 2)
{
printf("usage: %s filename", argv[0]);
return -1;
}
/* Retrieve the device list on the local machine */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
exit(1);
}
/* Print the list */
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* Open the device */
if ( (adhandle= pcap_open(d->name, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n", d->name);
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Open the dump file */
dumpfile = pcap_dump_open(adhandle, argv[1]);
if(dumpfile==NULL)
{
fprintf(stderr,"/nError opening output file/n");
return -1;
}
printf("/nlistening on %s... Press Ctrl+C to stop.../n", d->description);
/* At this point, we no longer need the device list. Free it */
pcap_freealldevs(alldevs);
/* start the capture */
pcap_loop(adhandle, 0, packet_handler, (unsigned char *)dumpfile);
return 0;
}
/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char *dumpfile, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
/* save the packet on the dump file */
pcap_dump(dumpfile, header, pkt_data);
}
现在你也看到了,这个程序的结构和我们以前学的程序的结构非常类似。只有两点不同:
打开网络接口后跟着就调用pcap_dump_open() ,这个调用打开一个dump文件并把它和网络接口绑定到一起。
在 packet_handler() 回调函数中数据包被 pcap_dump() 函数写到打开的文件中去。pcap_dump() 的参数与 packet_handler() 中的参数一一对应。
从一个dump文件中读取数据包:
现在我们已经有了一个有效的dump文件,我们可以尝试来读取它的内容。下面的代码打开一个dump文件并显示出里面包含的每一个数据包。文件打开是用的 pcap_open_offline() 函数,然后一般是用 pcap_loop() 来按照顺序来读取数据包。就象你看到的一样,从一个dump文件中读取数据包和从一个物理接口来捕获数据包基本上是一样的。
这个例子还介绍了另一个函数:pcap_createsrcsrc()。这个函数生成一个以一个标记开始的数据源字符串,这个标记用来告诉winpcap数据源的类型,比如"rpcap://" 代表一个适配器,"file://" 代表一个文件。如果已经使用了 pcap_findalldevs_ex() (这个函数的返回值已经包含了数据源字符串),那么就不需要再使用 pcap_createsrcsrc() 了。不过,因为文件名字是用户输入的,所以在这个例子里我们还是要使用它的。
#include <stdio.h>
#include <pcap.h>
#define LINE_LEN 16
void dispatcher_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
main(int argc, 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;
}
/* Create the source string according to the new WinPcap syntax */
if ( pcap_createsrcstr( source, // variable that will keep the source string
PCAP_SRC_FILE, // we want to open a file
NULL, // remote host
NULL, // port on the remote host
argv[1], // name of the file we want to open
errbuf // error buffer
) != 0)
{
fprintf(stderr,"/nError creating a source string/n");
return -1;
}
/* Open the capture file */
if ( (fp= pcap_open(source, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"/nUnable to open the file %s./n", source);
return -1;
}
// read and dispatch packets until EOF is reached
pcap_loop(fp, 0, dispatcher_handler, NULL);
return 0;
}
void dispatcher_handler(u_char *temp1,
const struct pcap_pkthdr *header, const u_char *pkt_data)
{
u_int i=0;
/* print pkt timestamp and pkt len */
printf("%ld:%ld (%ld)/n", header->ts.tv_sec, header->ts.tv_usec, header->len);
/* Print the packet */
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_LEN 16
main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
struct pcap_pkthdr *header;
u_char *pkt_data;
u_int i=0;
int res;
if(argc != 2)
{
printf("usage: %s filename", argv[0]);
return -1;
}
/* Create the source string according to the new WinPcap syntax */
if ( pcap_createsrcstr( source, // variable that will keep the source string
PCAP_SRC_FILE, // we want to open a file
NULL, // remote host
NULL, // port on the remote host
argv[1], // name of the file we want to open
errbuf // error buffer
) != 0)
{
fprintf(stderr,"/nError creating a source string/n");
return -1;
}
/* Open the capture file */
if ( (fp= pcap_open(source, // name of the device
65536, // portion of the packet to capture
// 65536 guarantees that the whole packet will be captured on all the link layers
PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode
1000, // read timeout
NULL, // authentication on the remote machine
errbuf // error buffer
) ) == NULL)
{
fprintf(stderr,"/nUnable to open the file %s./n", source);
return -1;
}
/* Retrieve the packets from the file */
while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
{
/* print pkt timestamp and pkt len */
printf("%ld:%ld (%ld)/n", header->ts.tv_sec, header->ts.tv_usec, header->len);
/* Print the packet */
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;
}
用pcap_live_dump()来往dump文件中写数据包:
注意:现在,由于新的内核缓存的一些问题,这个特性已经不能使用了。