一、前言
通过以前的学习我们已经熟悉了从网卡上捕获数据包,现在我们将学习如何处理数据包。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环境。