Winpcap 教程

原文出处:http://winpcap.polito.it/docs/man/html/index.html  
作者:  
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino  

http://winpcap.polito.it  

本文转载自协议分析网http://www.cnpaf.net/Class/winpcap/200610/16289.html

概述:  
这篇教程将会指引读者逐步了解WinPcap编程, 从简单的基础函数(获取网络接口列表, 捕捉数据包)到更高级的内容(处理发送队列, 网络流量统计). 教程中包括一些代码片断, 以及一些简单但完整的例子, 读者可以参考这些例子更好的理解教程的内容. 这些例子全部用C语言写成, 所以基本的C语言编程知识是必要. 同时, 因为这篇教程的内容是与底层网络紧密相连的, 所以笔者假设读者已经具备有关网络和协议的相关知识.  
译者的话:  
WinPcap是一套免费的, 基于Windows的网络接口API, 它在底层网络操作方面对程序员很有帮助. 这篇文档翻译自 "WinPcap Documentation 3.0" 中的 "WinPcap tutorial: a step by step guide to program WinPcap" 一部分. 这篇教程对初学者的帮助很大, 尤其是简短清晰的例子, 但这篇教程只是整个文档的一小部分, 我认为你仍然需要参考文档的其它部分来了解各种结构等信息. 教程中注有前缀 "Y-" 的部分是译者为了让读者更明白作者的意思添加的, 原文中没有.  
1. 获取网络接口列表  
通常, 一个基于WinPcap的应用程序所要做的第一件事, 就是获得适合的网络接口的列表. Libpcap中的pcap_findalldevs()函数就是干这活的: 这个函数返回一个pcap_if结构的列表, 每个元素都记录了一个接口的信息. 其中, name和description以人类可以阅读的形式, 记录了设备的信息.  
下面的源代码输出可用的网络接口的列表, 并且在没有找到任何借口的情况下输出错误信息:  

代码   
#include "pcap.h"  
main()  
{  
pcap_if_t *alldevs;  
pcap_if_t *d;  
int i=0;  
char errbuf[PCAP_ERRBUF_SIZE];  
/* 取得列表 */  
if (pcap_findalldevs(&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  
/* Y- 没有有效的描述 */  
printf(" (No description available)\n");  
}  
if(i==0)  
{  
/* Y- 没有有效的接口, 可能是因为没有安装WinPcap */  
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");  
return;  
}  
/* 我们不再需要列表了, 释放 */  
pcap_freealldevs(alldevs);  
}  
  
我们来看看这段代码.  
首先, 和其他的libpcap函数一样, pcap_findalldevs(), 有一个错误缓冲区(errbuf)参数. 这个参数是一个字符串指针, 一旦发生错误,libpcap将会在这里填入错误描述. 然后, 请注意, pcap_findalldev系统下的s()函数同时也被UNIX下的libpcap所支持, 但是并不是所有的操作系统都支持“网络接口描述”(description)这一项. 所以, 如果我们想写一个可以移植的的应用程序,那么我们必须要为描述为“空”(null)的情况做好准备:遇到这种情况我们就输出一个“没有有效的描述”的消息.  
最后我们通过pcap_freealldevs()函数来释放接口列表.  
现在让我们编译并运行我们的第一个WinPcap程序. 如果你使用UNIX或者Cgywin的话, 你只需要以下命令:  
gcc -o testaprog testprog.c -lpcap  
在Windows环境中(Y - 如果你使用Microsoft Visual C++), 你需要建立一个工程, 按照"Using WinPcap in your programs " 一节中说明来做.  
不过, 我仍然建议你参照Winpcap开发者包(WinPcap developer’s pack)中的例子, 那些例子包括了所以配置完善的工程, 以及全部你所需要的库和包含文件.  
(Y - 你可以在本章最后找到Microsoft Visual C++ 的配置方法)  
假设现在你已经成功编译了程序, 我们就来运行它. 在我的WinXP工作站上, 输出结果是:  
1. {4E273621-5161-46C8-895A-48D0E52A0B83} (Realtek RTL8029(AS) Ethernet Adapter)  
2. {5D24AE04-C486-4A96-83FB-8B5EC6C7F430} (3Com EtherLink PCI)  
就如你所看到的, 网络接口的名称(当打开这个接口时, 需要传递这个名称给libpcap库)在windows环境下几乎是没有办法读懂的(Y-严重同意), 所以输出一个描述对于你的用户来说是非常有帮助的.  

附注: Microsoft Visual C++ 工程的设置  
1. 下载并安装 WinPcap, 推荐的版本是3.0  
2. 从 http://winpcap.polito.it 下载 WinPcap Developer’s Pack 并解压缩  
3. 用 Microsoft Visual C++ 建立一个空工程 (empty project)  
4. 复制源代码  
5. 把 Winpcap Developer’s Pack 中的 Includes 目录添加为新的包含文件目录  
6. 添加库 wpcap.lib 和 wsock32.lib  
原文出处:http://winpcap.polito.it/docs/man/html/index.html  
作者:  
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino  
http://winpcap.polito.it  
2. 获取设备的高级信息  
上一课我们介绍了如何获取一个设备的基本信息(比如设备名称和设备描述). 实际上, WinPcap 也可以为我们提供关于接口的更多信息. 由 pcap_findalldevs() 函数返回的 pcap_if 结构也包含了一个 pcap_addr 结构的列表, 它记录了以下信息:  
1. 接口的地址列表  
2. 接口的掩码列表 (与地址列表一一对应)  
3. 接口的广播地址列表 (与地址列表一一对应)  
4. 目标地址列表 (与地址列表一一对应)  
下面例子中的 ifprint() 函数将会输出 pcap_if 结构的全部内容. 它包括了 pcap_findalldevs() 函数所返回的所有元素. ( Y- 全部有效接口)  

代码   

/*  
* Copyright (c) 1999 - 2002  
* Politecnico di Torino. All rights reserved.  [Page]
*  
* Redistribution and use in source and binary forms, with or without  
* modification, are permitted provided that: (1) source code distributions  
* retain the above copyright notice and this paragraph in its entirety, (2)  
* distributions including binary code include the above copyright notice and  
* this paragraph in its entirety in the documentation or other materials  
* provided with the distribution, and (3) all advertising materials mentioning  
* features or use of this software display the following acknowledgement:  
* ``This product includes software developed by the Politecnico  
* di Torino, and its contributors.’’ Neither the name of  
* the University 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 ``AS IS’’ AND WITHOUT ANY EXPRESS OR IMPLIED  
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF  
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  
*/  
#include "pcap.h"  
#ifndef WIN32  
#include  
#include  
#else  
#include  
#endif  
void ifprint(pcap_if_t *d);  
char *iptos(u_long in);  
int main()  
{  
pcap_if_t *alldevs;  
pcap_if_t *d;  
char errbuf[PCAP_ERRBUF_SIZE+1];  
/* 获得设备列表 */  
if (pcap_findalldevs(&alldevs, errbuf) == -1)  
{  
fprintf(stderr,"Error in pcap_findalldevs: %s\n",errbuf);  
exit(1);  
}  
/* 遍历所有元素 */  
for(d=alldevs;d;d=d->next)  
{  
ifprint(d);  
}  
return 1;  
}  
/* Print all the available information on the given interface */  
void ifprint(pcap_if_t *d)  
{  
pcap_addr_t *a;  
/* 名称 */  
printf("%s\n",d->name);  
/* 描述 */  
if (d->description)  
printf("\tDescription: %s\n",d->description);  
/* 回环地址 */  
printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");  
/* IP 地址 */  
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)  
/* Y- IP 地址 */  
printf("\tAddress: %s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));  
if (a->netmask)  
/* Y- 掩码 */  
printf("\tNetmask: %s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));  
if (a->broadaddr)  
/* Y- 广播地址 */  
printf("\tBroadcast Address: %s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));  
if (a->dstaddr)  
/* Y - 目标地址 */  
printf("\tDestination Address: %s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));  
break;  
default:  
/* 未知 */  
printf("\tAddress Family Name: Unknown\n");  
break;  
}  
}  
printf("\n");  
}  
/* 来自 tcptracert, 把数字IP地址转换为点格式 */  
#define IPTOSBUFFERS 12  
char *iptos(u_long in)  
{  
static char output[IPTOSBUFFERS][3*4+3+1];  
static short which;  
u_char *p;  
p = (u_char *)∈  
which = (which + 1 == IPTOSBUFFERS ? 0 : which + 1);  
sprintf(output[which], "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);  
return output[which];  
}  
原文出处: http://winpcap.polito.it/docs/man/html/index.html  
作者:  
Loris Degioanni (degioanni@polito.it), NetGroup, Politecnico di Torino  
http://winpcap.polito.it  
3. 打开一个接口并捕捉流量  
现在我们已经知道如何获取一个接口的有关信息了, 我们可以来点真家伙了 -- 打开一个接口并捕捉流量. 在这一课里, 我们会编译一个程序, 它将捕捉网络中所有的数据包并输出他们的一些相关信息。我们使用函数 
pcap_open_live()  来打开一个捕捉设备. 这里, 我们需要解释一下 snaplen, promisc 和 to_ms 参数.  
( Y- 函数原型: pcap_t * pcap_open_live (char *device, int snaplen, int promisc, int to_ms, char *ebuf) )  
"snaplen" 参数指定了要捕捉的数据包的部分. 在某些操作系统中 (如 xBSD 和 Win32), 驱动程序提供了只捕捉每个数据包其中一部分的可能性: 这样就降低了要处理的数据的量, 从而提高了捕捉程序的效率. 在例子中,
 我们使用一个高出 MTU 最大值的值 (65536) 以确保可以捕捉到整个数据包.   [Page]
"promisc" 表明接口将会被设置为混杂模式
. 一般情况下, 接口只处理目标地址为自己的数据; 到其他主机的数据包将会被忽略. 然而当一个接口处于混杂模式时, 它将会处理全部的流量: 也就是说, 在共享媒介 ( Y- 才疏学浅, 不知道怎么翻译好 ), 例如非交换型以太网 ( Y- 比如基于集线器的网络 )中, WinPcap 可以捕捉到所有主机的数据包. 混杂模式是多数捕捉程序的默认模式, 所以我们在例子中也采用这种模式.  
"to_ms" 用以设置超时
, 单位是毫秒. 一个从接口读取 ( Y- 捕捉) 的操作, (例如 pcap_dispatch() 或者 pcap_next_ex()), 如果没有捕捉到数据包, 那么在超过指定的时间以后就会返回. 进一步说, 如果接口处在静态模式中, to_ms 也定义了静态报告的间隔时间 (参阅 "Gathering Statistics on the network traffic " 以获取更多信息).  设置 to_ms 为 0, 则说明永远不会超时, 如果没有数据包到达, 那么捕捉操作将会永远不会返回, 而将其值设置为 -1 则会立刻返回.   

代码   
#include "pcap.h"  
/* 数据包处理函数声明 */  
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); 
main()  
{  
pcap_if_t *alldevs;  
pcap_if_t *d;  
int inum;  
int i=0;  
pcap_t *adhandle;  
char errbuf[PCAP_ERRBUF_SIZE];  
/* 获取设备列表 */  
if (pcap_findalldevs(&alldevs, errbuf) == -1)  
{  
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);  
exit(1);  
}  
/* 数据列表 */  
for(d=alldevs; d; d=d->next)  
{  
printf("%d. %s", ++i, d->name);  
if (d->description)  
printf(" (%s)\n", d->description);  
else  
printf(" (No description available)\n");  
}  
if(i==0)  
{  
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");  
return -1;  
}  
printf("Enter the interface number (1-%d):",i);  
scanf("%d", &inum);  
if(inum < 1 || inum > i)  
{  
printf("\nInterface number out of range.\n");  
/* 释放设备列表 */  
pcap_freealldevs(alldevs);  
return -1;  
}  
/* 转到选择的设备 */  
for(d=alldevs, i=0; i< inum-1;d=d->next, i++);  
/* 打开设备 */  
if ( (adhandle= pcap_open_live(d->name, //设备名  
65536, // 捕捉完整的数据包  
1, // 混在模式  
1000, // 读入超时  
errbuf // 错误缓冲  
) ) == NULL)  
{  
/* Y- 打开失败*/  
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\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;  
}  

/* 处理数据包的回调函数*/  
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data) 
{  
struct tm *ltime;  
char timestr[16];  
/* 转换时间戳为可以阅读的格式 */  
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() 则一定要等到一定数量的数据包被处理了以后才会返回 (Y- 第二个参数指定了要处理的数据包的数量, 0 为无限, 在这里, 我们设置的超时对 pcap_loop() 不起作用.) 在本例中, pcap_loop() 已经足够我们使用了, 而 pcap_dispatch() 一般应用在更复杂的程序里.  
这两个函数都有一个回调参数, 只想一个处理数据包的函数, 如本例中的 packet_handler. 每当有新的数据包到来的时候, libpcap将会调用这个函数来处理数据包, libpcap也会提供这个数据包的一些信息: 一个首部, 包含了时间戳和长度信息 (Y-header 参数); 真实数据包 (Y- pkt_data参数), 包括各种协议首部. 请注意, MAC CRC一般不会出现, 因为当设备(网卡)进行帧确认操作时, 它就已经被移除了. 同时, 大部分网卡将会丢弃错误的 CRC, 所以 WinPcap 基本上也不能捕捉他们.  
上面的例子只输出每个数据包时间戳以及长度 (来自 pcap_pkthdr header). 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值