我们已经知道如何获取网卡信息了,现在让我们开始进行真正的开发工作,即打开一个网卡并抓取数据包。本文中,我们将写一个程序来打印出流经网卡的每个包的相关信息。
打开抓包设备的函数是pcap_open(),该函数的参数中,snaplen,flags和to_ms这三个值得解释一下:
snaplen
该参数设定了包需要抓取的部分。在一些操作系统上(如xBSD和Win32),包驱动程序可以配置成只抓取包的初始部分:这降低了应用拷贝数据的数量,从而提高扎包的效率。在我们使用高于我们可能碰到的最大MTU(Maximum Transmission Unit,最大传输单元,以字节为单位) 65536的情况下,这将确保应用总是接收到包的全部。
flags
最重要的标致应该是否将网卡设置成混杂模式(promiscuous mode)。在正常模式下,网卡只抓取来自网络的,以它为目的地的数据包,其他机器之间交换的包将被忽略。而在混杂模式下,将抓取所有的包,而不管该网卡是不是这些包的目的地。这意味着在共享介质(如未经交换的以太网,non-switched Ethernet)的情况下,WinPcap能够抓取到其他机器的包。多数抓包应用,混杂模式都是缺省的模式,下面的示例程序中,我们也采用混杂模式。
to_ms
该参数指定了读超时,以毫秒为单位。从网卡读数据时(比如,用pcap_dispatch()或者pcap_next_ex()),如果网络上无包可读,超时后(即过了to_ms毫秒后)总会返回。to_ms也定义了统计报告的时间间隔。将to_ms设为0,表示无超时,即在读网卡时,如果没有数据包到来,将永不返回;另一方面,如果设为-1,会使得对网卡的读操作立即返回。
#include "pcap.h"
/* prototype of the packet handler */
void packet_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;
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_s("%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];
time_t local_tv_sec;
/*
* unused variables
*/
(VOID)(param);
(VOID)(pkt_data);
/* convert the timestamp to readable format */
local_tv_sec = header->ts.tv_sec;
localtime_s(<ime, &local_tv_sec);
strftime( timestr, sizeof timestr, "%H:%M:%S", <ime);
printf("%s,%.6d len:%d/n", timestr, header->ts.tv_usec, header->len);
}
打开网卡后,就可以用pcap_dispatch()或者pcap_loop()开始抓包了。这两个函数非常相似,不同的是当超时到了后,pcap_dispatch()会返回(尽管不能保证),而pcap_loop()则不会返回,直到抓取了cnt个包(译注:cnt是pcap_loop()函数的一个参数,其含义见pcap_loop()的原型),因此在比较空闲网络情况下,会被阻塞很长时间。在本例中,pcap_loop()就够用了,pcap_dispatch()通常用于更复杂的程序。
这两个函数都各有一个callback参数,即packet_handler,它指向用于处理包数据的函数。每当有新的包从网络上到达时,该函数就会自动被libpcap调用,以接收一个通用状态(generic status,和pcap_loop()或pcap_dispatch()中的user参数对应);一个含有信息包头,诸如时间戳和长度;以及包括协议头在内的真正包数据。注意CRC帧一般不提供,因为网卡在帧完整性验证后会将其删除。另外,大多数网卡在收到错误的CRC后,会将相应的包丢弃。因此,这些被丢弃的包,WinPcap是抓取不到的。
上面的例子从pcap_pkthdr头提取了时间戳和每个包的长度,并打印在屏幕上。
请注意到,上面的pcap_loop()或许存在一个不足之处,主要是基于这样一个事实:处理函数(handler)是由抓包驱动程序调用的,因此用户的应用程序不能直接控制它。另外一个方法就是使用pcap_next_ex()函数,将在下一个示例程序说明。
后篇:待续…
此文章来自于【http://blog.csdn.net/pathuang68/article/details/4292603】