现在,我们已经知道如何获取适配器的信息了,那我们就开始一项更具意义的工作,打开适配器并捕获数据包。在这讲中,我们会编写一个程序,将每一个通过适配器的数据包打印出来。
打开设备的函数是 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 "
/* 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];
/* 获取本机设备列表 */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, & alldevs, errbuf) == - 1 )
{
fprintf(stderr, " Error in pcap_findalldevs: %s\n " , errbuf);
exit( 1 );
}
/* 打印列表 */
for (d = alldevs; d; d = d -> next)
{
printf( " %d. %s " , ++ i, d -> name);
if (d -> description)
printf( " (%s)\n " , d -> description);
else
printf( " (No description available)\n " );
}
if (i == 0 )
{
printf( " \nNo interfaces found! Make sure WinPcap is installed.\n " );
return - 1 ;
}
printf( " Enter the interface number (1-%d): " ,i);
scanf( " %d " , & inum);
if (inum < 1 || inum > i)
{
printf( " \nInterface number out of range.\n " );
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return - 1 ;
}
/* 跳转到选中的适配器 */
for (d = alldevs, i = 0 ; i < inum - 1 ;d = d -> next, i ++ );
/* 打开设备 */
if ( (adhandle = pcap_open(d -> name, // 设备名
65536 , // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容
PCAP_OPENFLAG_PROMISCUOUS, // 混杂模式
1000 , // 读取超时时间
NULL, // 远程机器验证
errbuf // 错误缓冲池
) ) == NULL)
{
fprintf(stderr, " \nUnable to open the adapter. %s is not supported by WinPcap\n " , d -> name);
/* 释放设备列表 */
pcap_freealldevs(alldevs);
return - 1 ;
}
printf( " \nlistening on %s...\n " , d -> description);
/* 释放设备列表 */
pcap_freealldevs(alldevs);
/* 开始捕获 */
pcap_loop(adhandle, 0 , packet_handler, NULL);
return 0 ;
}
/* 每次捕获到数据包时,libpcap都会自动调用这个回调函数 */
void packet_handler(u_char * param, const struct pcap_pkthdr * header, const u_char * pkt_data)
{
struct tm * ltime;
char timestr[ 16 ];
time_t local_tv_sec;
/* 将时间戳转换成可识别的格式 */
local_tv_sec = header -> ts.tv_sec;
ltime = localtime( & local_tv_sec);
strftime( timestr, sizeof timestr, " %H:%M:%S " , ltime);
printf( " %s,%.6d len:%d\n " , timestr, header -> ts.tv_usec, header -> len);
}
当适配器被打开,捕获工作就可以用 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() 函数。有关这个函数的使用,我们将在下一讲为您展示。 (不用回调方法捕获数据包).
原作者:The WinPcap Team 主页: http://www.winpcap.org