WinPcap学习(四)打开适配器并捕获数据包

打开设备的函数是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);
}

当适配器被打开,捕获工作就可以用pcap_dispatch()或pcap_loop()进行。这两个函数非常的相似,区别是pcap_dispatch()当超时到了就返回,而pcap_loop()不会因此返回,只有当cnt数据包被捕获,所以pcap_loop()会在一小段时间内阻塞网络的利用。

这两个函数都有一个回调函数,packet_handler指向一个可以接收数据包的函数。这个函数会在收到每个新的数据包并收到一个通用状态时被libpcap所调用,数据包的首部一般有一些如时间戳,数据包长度的信息,还包含了协议首部的实际数据。注意:冗余检验码CRC不再支持,因为帧到达适配器,并经过检验确认以,适配器就会将CRC删除,与此同时,大部分适配器会直接丢弃CRC错误的数据包,所以,WinPcap没法捕获到它们。

上面的程序将每一个数据包的时间戳和长度从 pcap_pkthdr 的首部解析出来,并打印在屏幕上。

请注意,使用 pcap_loop() 函数可能会遇到障碍,主要因为它直接由数据包捕获驱动所调用。因此,用户程序是不能直接控制它的。另一个实现方法(也是提高可读性的方法),是使用 pcap_next_ex()函数。

在IDE中试验这个例程时,packet_handler最后一个参数忘加const,一直报错,后来才记得:对于指针const是可以区分两个函数的。

你可能感兴趣的:(WinPcap学习(四)打开适配器并捕获数据包)