WinPcap分析数据包

一、前言

通过前面的学习,我们已经知道了如何打开设备捕获数据了,接下来就可以捕获并过滤网络流量了。

本教程主要的目标是展示如何解析数据包的协议首部,选中分析和实现UDP协议,因为UDP协议相对于其它协议来说更简单,用于入门。


二、代码详解

#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

//4字节的IP地址
typedef struct ip_address
{
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
}ip_address;

//IPv4首部
typedef struct ip_header
{
    u_char ver_ihl;             //版本(4 bits) + 首部长度(4 bits)
    u_char tos;                 //服务类型(Type of service)
    u_short tlen;               //总长(Total length)
    u_short identification;     //标识(Identification)
    u_short flags_fo;           //标志位(Flags)(3 bits) + 偏移量(Fragment offset)(13 bits)
    u_short ttl;                //存活时间(Time to live)
    u_char proto;               //协议(protocol)
    u_short crc;                //首部校验和(Header checksum)
    ip_address saddr;           //源地址(Source address)
    ip_address daddr;           //目的地址(Destination address)
    u_int op_pad;               //选项与填充
}ip_header;

//UDP首部
typedef struct udp_header
{
    u_short sport;              //源端口(Source port)
    u_short dport;              //目的端口(Destination port)
    u_short len;                //UDP数据包长度(Datagram length)
    u_short crc;                //校验和(Checksum)
}udp_header;

//回调函数原型
void packet_handler(u_char* param, 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];
    u_int netmask;
    char packet_filter[] = "ip and udp";
    struct bpf_program fcode;

    //获取本机适配器列表
    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;
    }

    //检查数据链路层,为了简单,只考虑以太网
    if(pcap_datalink(adhandle) != DLT_EN10MB) {
        qDebug()<<stderr<<endl<<"This program works only on Ethernet networks.";

        //释放设备列表
        pcap_freealldevs(alldevs);
        return -1;
    }

    if(d->addresses != NULL) {
        //获得接口第一个地址的掩码
        netmask = ((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;
    }else {
        //如果接口没有地址,那么假设一个C类的掩码
        netmask = 0xffffff;
    }

    //编译过滤器
    if(pcap_compile(adhandle,&fcode,packet_filter,1,netmask) < 0) {
        qDebug()<<stderr<<endl<<"Unable to compile the packet filter. Check the syntax";

        //释放设备列表
        pcap_freealldevs(alldevs);
        return -1;
    }

    //设置过滤器
    if(pcap_setfilter(adhandle,&fcode) < 0) {
        qDebug()<<stderr<<endl<<"Error setting the filter.";

        pcap_freealldevs(alldevs);
        return -1;
    }

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

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

    //开始捕捉
    pcap_loop(adhandle,0,packet_handler,NULL);

    return a.exec();
}

//回调函数,当收到每一个数据包时会被libpcap所调用
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
    struct tm* ltime;
    char timestr[16];
    ip_header* ih;
    udp_header* uh;
    u_int ip_len;
    u_short sport,dport;
    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);

    //打印数据包的时间戳和长度
    qDebug()<<timestr<<header->ts.tv_usec<<header->len;

    //获取IP数据包头部的位置
    ih = (ip_header*)(pkt_data + 14); //14是以太网头部长度

    //获得UDP首部的位置
    ip_len = (ih->ver_ihl & 0xf) * 4;
    uh = (udp_header*)((u_char*)ih + ip_len);

    //将网络字节序列转换成主机字节序列
    sport = ntohs(uh->sport);
    dport = ntohs(uh->dport);

    //打印IP地址和UDP端口
    qDebug()<<ih->saddr.byte1<<":"<<ih->saddr.byte2<<":"<<ih->saddr.byte3<<":"<<ih->saddr.byte4<<"  "<<sport;
    qDebug()<<ih->daddr.byte1<<":"<<ih->daddr.byte2<<":"<<ih->daddr.byte3<<":"<<ih->daddr.byte4<<"  "<<dport;
    qDebug()<<"===================================================================";
}

运行结果如下
在这里插入图片描述

  • 首先,我们将过滤器设置成“ip and udp”,在这种方式下,确信packet_handler()只会收到基于IPv4的UDP数据包,这将简化解析过程,提高程序的效率;
  • 我们还分别创建了用于描述IP首部和UDP首部的结构体,这些结构体中的各种数据会被packet_handler()合理地定位;
  • pack_handler(),尽管只受限于单个协议的解析(比如基于IPv4的UDP),不过它展示了捕捉器(sniffers)是多么的复杂,就像TcpDump或WinDump对网络数据流进行解码那样;
  • 因为我们对MAC首部不感兴趣,所以跳过它,为了简介,我们在开始捕捉前,使用了pcap_datalink()对MAC层进行了检测,以确保我们是在处理一个以太网,这样就能确保MAC首部是14位的 ;
  • IP数据包的首部就位于MAC首部的后面,我们将IP数据包的首部解析到源IP地址和目的IP地址;
  • 处理UDP的首部有一些复杂,因为IP数据包的首部并不是固定的,然而,我们可以通过IP数据包的length域来得到它的长度,一旦我们知道了UDP首部的位置,我们就能解析到源端口和目的端口;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝勒里恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值