WinPcap处理脱机的堆文件

一、前言

通过以前的学习我们已经熟悉了从网卡上捕获数据包,现在我们将学习如何处理数据包。WinPcap为我们提供了很多API来将流经网络的数据包保存到一个堆文件并读取堆的内容。这种文件的格式很简单,但包含了所捕获数据报的二进制内容,这种文件格式也是很多网络工具的标准,如WinDump、Ethereal、Snort等。


二、代码详解

将数据包保存到文件:以LIBPCAP的格式写数据包,从指定的接口上捕获数据包并将它们存储到一个指定的文件。

#include "mainwindow.h"
#include <QApplication>
#include <QDebug>

#define HAVE_REMOTE
#include "pcap.h"

#ifndef WIN32
    #include <sys/socket.h>
    #include <netinet/in.h>
#else
    #include <winsock2.h>
    #include <ws2tcpip.h>
#endif

//回调函数原型
void packet_handler(u_char* dumpfile, const struct pcap_pkthdr* header, const u_char* pkt_data);

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();


    pcap_if_t *alldevs;
    pcap_if_t *d;
    int inum;
    int i=0;
    pcap_t* adhandle;   //定义文件句柄
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_dumper_t *dumpfile;

    //检查命令行参数,是否带有文件名
    if(argc != 2) {
        qDebug()<<"usage: %s filename"<<argv[0];
    }

    //获取本机适配器列表
    if(pcap_findalldevs_ex(PCAP_SRC_IF_STRING,NULL,&alldevs, errbuf) == -1)
    {
        qDebug() << "Error in pcap_findalldevs_ex: " <<errbuf;
        exit(1);
    }

    //打印适配器列表
    for(d = alldevs; d; d = d->next)
    {
        //设备名(Name)
        qDebug()<<"Name: "<<d->name;
        ++i;
        //设备描述(Description)
        if (d->description) {
            qDebug()<<"Description: "<<d->description;
        }else {
            qDebug()<<"No description available";
        }

        qDebug()<<"====================================================================";
    }

    if(i==0) {
        qDebug()<<"No interfaces found! Make sure WinPcap is installed.";
        return -1;
    }

    qDebug()<<QString("Enter the interface number (1-%1): ").arg(i);
    //scanf("%d",&inum);
    inum = 5;
    qDebug()<<"inum: "<<inum;

    if(inum < 1 || inum > i){
        qDebug()<<"Interface number out of range.";
        //释放适配器列表
        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) {
        qDebug()<<"Unable to open the adapter."<<QString("%1 is not support by WinPcap").arg(d->name);

        //释放适配器列表
        pcap_freealldevs(alldevs);
        return -1;
    }

    //打开文件
    dumpfile = pcap_dump_open(adhandle,argv[1]);
    if(dumpfile == NULL) {
        qDebug()<<stderr<<"Error opening output file";
    }

    qDebug()<<QString("Listening on %1...").arg(d->description);

    //释放适配器列表
    pcap_freealldevs(alldevs);

    //循环捕获数据并调用packet_handler函数把数据存储到堆文件
    pcap_loop(adhandle,0,packet_handler,(unsigned char *)dumpfile);

    return a.exec();
}

//回调函数,当收到每一个数据包时会被libpcap所调用
void packet_handler(u_char* dumpfile, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
    pcap_dump(dumpfile,header,pkt_data);
}

该程序和之前的例子结构非常相似,区别在于,这个程序一旦打开网卡就调用pcap_dump_open来打开一个文件,该调用将文件和某个网卡相关联。packet_handler()内部通过调用pcap_dump()来将捕获的数据报存储到文件,pcap_dump的参数和packer_handler()一样,所以用起来比较方便。


从文件读取数据内容(pcap_loop):打开一个堆文件并打印其中的每个包内容

#include "mainwindow.h"
#include <QApplication>
#include <QDebug>

#define HAVE_REMOTE
#include "pcap.h"

#ifndef WIN32
    #include <sys/socket.h>
    #include <netinet/in.h>
#else
    #include <winsock2.h>
    #include <ws2tcpip.h>
#endif

#define LINE_LEN 16

//回调函数原型
void dispatcher_handler(u_char* temp1, const struct pcap_pkthdr* header, const u_char* pkt_data);

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();



    pcap_t* fp;   //定义文件句柄
    char errbuf[PCAP_ERRBUF_SIZE];

    //检查命令行参数,是否带有文件名
    if(argc != 2) {
        qDebug()<<"usage: %s filename"<<argv[0];
    }

    //打开一个存储有数据的堆文件
    if((fp = pcap_open_offline(argv[1],errbuf)) == NULL) {
        qDebug()<<stderr<<"Error opening dump file";
    }

    //读取数据直到遇到EOF标志
    pcap_loop(fp,0,dispatcher_handler,NULL);

    return a.exec();
}

//回调函数,当收到每一个数据包时会被libpcap所调用
void dispatcher_handler(u_char* temp1, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
    u_int i = 0;

    //打印pkt的timestamp和len
    qDebug()<<"header->ts.tv_sec: "<<header->ts.tv_sec;
    qDebug()<<"header->ts.tv_usec: "<<header->ts.tv_usec;
    qDebug()<<"header->len: "<<header->len;

    //打印报
    for(int i=1; (i<header->caplen+1); ++i) {
        qDebug()<<"pkt_data[i-1]: "<<pkt_data[i-1];
    }
}

从文件读取数据内容(pcap_next_ex):打开一个堆文件并打印其中的每个包内容

#include "mainwindow.h"
#include <QApplication>
#include <QDebug>

#define HAVE_REMOTE
#include "pcap.h"

#ifndef WIN32
    #include <sys/socket.h>
    #include <netinet/in.h>
#else
    #include <winsock2.h>
    #include <ws2tcpip.h>
#endif

#define LINE_LEN 16

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();



    pcap_t* fp;   //定义文件句柄
    char errbuf[PCAP_ERRBUF_SIZE];
    struct pcap_pkthdr* header;
    const u_char* pkt_data;
    u_int i = 0;
    int res;

    //检查命令行参数,是否带有文件名
    if(argc != 2) {
        qDebug()<<"usage: %s filename"<<argv[0];
    }

    //打开一个存储有数据的堆文件
    if((fp = pcap_open_offline(argv[1],errbuf)) == NULL) {
        qDebug()<<stderr<<"Error opening dump file";
    }

    //读取数据直到遇到EOF标志
    while((res = pcap_next_ex(fp,&header,&pkt_data)) >= 0) {
        //打印pkt的timestamp和len
        qDebug()<<"header->ts.tv_sec: "<<header->ts.tv_sec;
        qDebug()<<"header->ts.tv_usec: "<<header->ts.tv_usec;
        qDebug()<<"header->len: "<<header->len;

        //打印报
        for(int i=1; (i<header->caplen+1); ++i) {
            qDebug()<<"pkt_data[i-1]: "<<pkt_data[i-1];
        }
    }

    if(res == -1) {
        qDebug()<<"Error reading the packets: "<<pcap_geterr(fp);
    }

    return a.exec();
}

WinPcap的最新版本提供了一个进一步的方法来将数据包存储到磁盘,就是使用pcap_live_dump()函数,它需要三个参数:

  • 一个文件名;
  • 一个该文件允许的最大长度;
  • 该文件所允许的最大包的数量;
    对这些参数来说,0意味着没有最大限制

可以在调用pcap_live_dump()前设置一个过滤器来定义哪些数据报需要存储

pcap_live_dump()是非阻塞的,所以他会立刻返回:数据的存储过程将会异步的进行,知道文件到达了指定的最大长度或最大数据报的数目为止。

应用程序能够用pcap_live_dump_ended()来等检查是否数据存储完毕,如果你指定的最大长度参数和数据报数量为0,那么该操作将永远阻塞。

pcap_live_dump()pcap_dump()的不同从设置的最大极限来说就是性能的问题。pcap_live_dump()采用WinPcap NPF驱动来从内核级的层次上向文件中写数据,从而使内存拷贝最小化。

显然,这些特点当前在其它操作系统下是不能够实现的,pcap_live_dump()是WinPcap所特有的,而且只能够应用于Win32环境。

上一篇:数据分析日常之异常分析:DAU或转化率等指标下降原因​


下一篇:qdebug dump struct 转载