My site
本文主要参考了以下资料:RaiChen的博客,WinPcap官方文档,《网络分析技术揭秘》-吕雪峰,Phinecos(洞庭散人)的博客
终于要开始编写嗅探器最核心的代码了!——打开适配器并捕获数据包:
#include "pcap.h"
#include <iostream>
//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
-1, //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 reaable 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);
}
首先定义了处理数据包的packet_handler()函数原型,接着打印适配器列表,让用户选择使用哪个适配器。其中有一个pcap_t结构的指针adhandle,官方文档是这样介绍pcap_t结构的:
一个已打开的捕捉实例的描述符。这个结构体对用户来说是不透明的,它通过wpcap.dll提供的函数,维护了它的内容。
我们只需要了解这个结构是给pcap_open()和pcap_compile()等函数调用的,相当于一个打开的WinPcap Session就可以了。
接下来使用pcap_open()函数打开适配器,pcap_open()函数原型定义如下:
pcap_t* pcap_open ( const char * source,
int snaplen,
int flags,
int read_timeout,
struct pcap_rmtauth * auth,
char * errbuf
)
函数接受6个参数:
接下来释放设备列表,调用pcap_loop()函数开始捕获,pcap_loop()函数原型如下:
int pcap_loop ( pcap_t * p,
int cnt,
pcap_handler callback,
u_char * user
)
typedef void(*) pcap_handler(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data)
4.第四个参数user是一个用户自定义的参数,包含了捕获session的状态,这个参数对应了pcap_handler()回调函数的第一个参数user。
以上基本上就是一个查找设备列表——选择设备——打开设备——开始捕获的过程,但我们在捕获到数据包后如果不对其进行处理就得不到有意义的信息,因此传递给pcap_loop()的回调函数packet_handler()就是用来对数据包进行处理的,函数定义如下:
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);
}
每次捕获到数据包时,libpcap都会自动调用这个回调函数,并会自动填充参数。第一个参数是pcap_loop()或pcap_dispatch()的user参数。第二个参数是将捕获驱动与数据包关联起来的头部信息,包括时间戳与长度信息。第三个参数是数据包的真实数据,还包括协议头部。
在这里我们只用到了header参数,param和pkt_data参数没有用到。header是一个pcap_pkthdr结构的指针,结构体定义如下:
struct pcap_pkthdr {
struct timeval ts;
bpf_u_int32 caplen;
bpf_u_int32 len;
};
其中ts表示时间间隔,caplen表示捕获到的数据包长度,如果在调用pcap_open()函数时设置snaplen参数比较小,就只能捕获到实际数据包的一部分,caplen就表示捕获到的长度,len表示数据包实际的长度,因此caplen总是小于等于len的。时间间隔的类型timeval定义如下:
typedef struct timeval {
long tv_sec;
long tv_usec;
} timeval;
其中tv_sec表示秒,tv_usec表示微秒。
packet_handler()函数的前面几行就都是用来解析header里的时间的,其中有tm结构,time_t类型,localtime_s()函数,strftime()函数。
然后就将时间以[时:分:秒]的格式存入timestr中,打印出这个时间,还有微妙,最后是数据包的长度。
运行一下程序,选择适配器:
得到了数据包的时间戳和长度信息:
其实打开适配器后,除了使用pcap_loop()函数捕获数据外,还可以使用pcap_dispatch()函数,两个函数非常的相似,区别就是 pcap_dispatch()当超时时间到了(timeout expires)就返回 (尽管不能保证),而 pcap_loop()不会因此而返回,只有当cnt数据包被捕获,所以,pcap_loop()会在一小段时间内,阻塞网络的利用。当然,在这个例子中,使用pcap_loop()函数已经足够了,pcap_dispatch()函数一般用于比较复杂的程序中。
还有就是使用pcap_loop()函数可能会遇到障碍,主要因为它直接由数据包捕获驱动所调用。因此,用户程序是不能直接控制它的。还有一个方法是使用pcap_next_ex()函数,同时还能提高可读性。
这些会在下一节详细说明,下一篇:不用回调方法捕获数据包