打开设备的函数是pcap_open()。下面参数snaplen,flags和to_ms的解释说明
snaplen制定要捕获数据包中的哪些部分。在一些操作系统中(比如xBSD和Win32),驱动可以被配置成只捕获数据包的初始化部分:这样可以减少应用间程序间复制数据的量,从而提高捕获效率。本例中,我们将值定为65535,它比我们能遇到的最大的MTU达要大。因此,我们确信我们总收到完整的数据包。
flags:最最重要的flag是用来指示适配器是否要补设置成混杂模式。一般情况下,适配器只接收发送给它自己的数据包,而那些在其他机器之间通讯的数据包,将会被丢弃。相反,如果适配器是混杂模式,那么不管这个数据包是不是发送给我的,我都会去捕获。这意味着在一个共享媒介(比如总线型以太网),WinPcap能捕获其他主机的所有数据包。大多数用于数据捕获的应用程序都会将适配器设置混杂模式,所以,我们也会在下面使用混杂模式。
to_ms:指定读取数据的超时时间,以毫秒计。在适配器上进行读取操作(比如用pcap_dispatch()或pcap_next_ex())都会在to_ms毫秒时间内响应,即使在网络上没有可用的数据包。在统计模式下,to_ms还可以用来定义统计的时间间隔。将to_ms设置为0意味着没有超进,那么如果没有数据包到达的话,读操作将永远不会返回。如果设置成-1,名家跟个部恰好相反,无论有没有数据包到达,读操作都会立即返回。
#include<iostream> #include<pcap.h> //packet handler函数原型 void packet_handler(u_char *param,const pcap_pkthdr *header,const u_char *pkt_data); int main() { pcap_if_t *alldevs; char errbuf[PCAP_ERRBUF_SIZE]; //获取本机设备列表 if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs,errbuf)==-1) { std::cerr<<"Error in pcap_findalldevs:"<<errbuf<<std::endl; exit(1); } //打印列表 int i=0; pcap_if_t *d; for(d=alldevs;d;d=d->next) { std::cout<<++i<<"."<<d->name<<std::endl; if(d->description) std::cout<<d->description<<std::endl; else std::cout<<"(No description available)"<<std::endl; } if(i==0) { std::cout<<"\nNo interface found!Make sure WinPcap is installed."<<std::endl; return -1; } std::cout<<"Enter the interface number(1-"<<i<<")"; int inum; std::cin>>inum; if(inum<1||inum>i) { std::cout<<"\nInterface number out of range."<<std::endl; pcap_freealldevs(alldevs); return -1; } //跳转到选中的适配器 for(d=alldevs,i=0;i<inum-1;d=d->next,++i); //打开设备 pcap_t *adhandle; if((adhandle=pcap_open(d->name, //设备名 65536, PCAP_OPENFLAG_PROMISCUOUS, //混杂模式 1000, //读取超时时间 NULL, //远程机器验证 errbuf //错误缓冲池 ))==NULL) { std::cerr<<"\nUnable to open the adapter."<<d->name<<" is not supported by WinPcap"<<std::endl; pcap_freealldevs(alldevs); return -1; } std::cout<<"\nlistening on "<<d->description<<"..."<<std::endl; pcap_freealldevs(alldevs); //开始捕获 pcap_loop(adhandle,0,packet_handler,NULL); return 0; } //每次捕到数据包时,libcap都会自动调用这个回调函数 void packet_handler(u_char *param,const pcap_pkthdr* header,const u_char *pkt_data) { 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); }
这两个函数都有一个回调函数,packet_handler指向一个可以接收数据包的函数。这个函数会在收到每个新的数据包并收到一个通用状态时被libpcap所调用,数据包的首部一般有一些如时间戳,数据包长度的信息,还包含了协议首部的实际数据。注意:冗余检验码CRC不再支持,因为帧到达适配器,并经过检验确认以,适配器就会将CRC删除,与此同时,大部分适配器会直接丢弃CRC错误的数据包,所以,WinPcap没法捕获到它们。
上面的程序将每一个数据包的时间戳和长度从 pcap_pkthdr 的首部解析出来,并打印在屏幕上。
请注意,使用 pcap_loop() 函数可能会遇到障碍,主要因为它直接由数据包捕获驱动所调用。因此,用户程序是不能直接控制它的。另一个实现方法(也是提高可读性的方法),是使用 pcap_next_ex()函数。
在IDE中试验这个例程时,packet_handler最后一个参数忘加const,一直报错,后来才记得:对于指针const是可以区分两个函数的。