一、前言
通过前面的学习,我们已经知道了如何打开设备捕获数据了,接下来就可以捕获并过滤网络流量了。
本教程主要的目标是展示如何解析数据包的协议首部,选中分析和实现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首部的位置,我们就能解析到源端口和目的端口;