12.2.2 文件存储与读取的实例
1.将数据包存储到文件的实例
首先,介绍从一个选定的网络适配器上捕获数据包,并将其用libpcap文件格式存储到用户指定的文件中的实例。实例源代码如下,完整的代码见[ch12/SaveDumpFile工程]。
defne WIN32
defne HAVE_REMOTE
include<stdio.h>
include"pcap.h"
/回调函数原型/
void packet_handler(u_char*param,
const struct pcap_pkthdrheader,const u_charpkt_data);
struct user_param
{
pcap_dumper_t*user_dumpfile;//存储文件
int max_num;//需要捕获的数据包个数
int cur_num;//当前数据包个数
pcap_t*p;//网络设备
};
int main(int argc,char**argv)
{
pcap_if_t*alldevs;
pcap_if_t*d;
struct user_param uparam;
……
uparam.max_num=10;//默认
uparam.cur_num=0;
/检查程序输入参数/
if(argc<2)
{
printf("usage:%s flename",argv[0]);
printf("usage:%s flename packet_num",argv[0]);
return-1;
}
/设置需要捕获的数据包个数/
if(argc==3)
{
uparam.max_num=atoi(argv[2]);
}
if(uparam.max_num<=0)
{
fprintf(stderr,"Error in packet number:%d\n",uparam.max_num);
exit(1);
}
……
/打开本机一个合适的网络设备/
……
/打开存储文件/
uparam.user_dumpfle=pcap_dump_open(uparam.p,argv[1]);
if(uparam.user_dumpfle==NULL)
{
//打开存储文件失败
return-1;
}
/开始捕获并存储文件/
pcap_loop(uparam.p,0,packet_handler,(unsigned char*)&uparam);
/结束文件存储/
pcap_dump_close(uparam.user_dumpfle);
pcap_close(uparam.p);
return 0;
}
/回调函数,用来处理数据包/
void packet_handler(u_char*param,
const struct pcap_pkthdrheader,const u_charpkt_data)
{
struct user_parampuser_param=(struct user_param)param;
puser_param->cur_num++;
fprintf(stderr,"cur_num:%d\n",puser_param->cur_num);
if(puser_param->cur_num+1>puser_param->max_num)
pcap_breakloop(puser_param->p);//中止捕获循环
/存储数据包到文件/
pcap_dump((u_char*)puser_param->user_dumpfle,header,pkt_data);
}
只有当网络接口打开时,调用pcap_dump_open函数才是有效的,该调用会打开一个文件,并将它关联到特定的网络接口上。在packet_handler回调函数中调用pcap_dump函数会将从该网络接口捕获的数据包写入该文件中。pcap_dump函数的参数和packet_handler函数的参数是一一对应的。
该实例的运行结果如图12-3所示。
图 12-3 文件转储结果
可在Wireshark中查看所转储的文件dump.txt,验证文件转储的正确性。结果如图12-4所示。
图 12-4 查看转储文件的数据包
2.从文件中读取数据包的实例
下面介绍从文件中读取数据包,并把读出来的数据包发送到网络上的实例。相应的实例源代码如下,完整的代码见[ch12/ReadDumpFile工程]。
defne WIN32
defne HAVE_REMOTE
include<stdio.h>
include<pcap.h>
void dispatcher_handler(u_char,const struct pcap_pkthdr,
const u_char*);
struct user_param
{
int cur_num;//当前的数据包个数
pcap_t*src_fp;//转储文件
pcap_t*dest_fp;//输出的网络接口
};
int main(int argc,char**argv)
{
……
char source[PCAP_BUF_SIZE];
struct user_param uparam;
uparam.cur_num=0;
if(argc!=2){
printf("usage:%s flename",argv[0]);
return-1;
}
……
/打开本机一个合适的网络设备,将其作为输出的网络适配器/
……
/根据新的WinPcap语法创建一个源字符串/
if(pcap_createsrcstr(
source,//源字符串
PCAP_SRC_FILE,//要打开的是文件
NULL,//远程主机
NULL,//远程主机端口
argv[1],//要打开的文件名
errbuf//错误缓冲区
)!=0)
{
//创建失败
return-1;
}
/打开输入的源文件/
if((uparam.src_fp=pcap_open(
source,
65536,
PCAP_OPENFLAG_PROMISCUOUS,
1000,
NULL,
errbuf
))==NULL)
{
//打开失败
return-1;
}
/读取解析并发送数据包,直到到达文件结尾处(EOF)/
pcap_loop(uparam.src_fp,0,dispatcher_handler,(u_char*)&uparam);
fprintf(stderr,"process packet number is:%d\n",
uparam.cur_num);
/释放资源/
pcap_close(uparam.dest_fp);
pcap_close(uparam.src_fp);
//释放设备列表
pcap_freealldevs(alldevs);
return 0;
}
void dispatcher_handler(u_char*param,
const struct pcap_pkthdrheader,const u_charpkt_data)
{
int ret=-1;
struct user_parampuser_param=(struct user_param)param;
puser_param->cur_num++;
if((ret=pcap_sendpacket(puser_param->dest_fp,pkt_data,
header->caplen))==-1)
{
printf("发送失败\n");
pcap_breakloop(puser_param->dest_fp);
return;
}
}
上面的代码通过pcap_open函数打开转储文件,然后使用pcap_loop函数有序地获取数据包。可见,从脱机转储文件中读取数据包和从物理网络接口中捕获数据包是相似的。
在这个例子中还使用了pcap_createsrcstr函数。该函数用于创建一个源字符串,该字符串以某个标识开头,这个标识会告诉WinPcap这个源的类型。比如,使用rpcap://标识表示要打开一个适配器,使用file://表示要打开一个文件。若使用pcap_findalldevs_ex函数,则不需要创建字符串,因为其返回值中已经包含了这些字符串。但是在这个实例中,因为文件名来自于用户的输入,故必须使用该函数。
该实例的运行结果如图12-5所示。
图 12-5 实例运行结果
在Wireshark中捕获所发送的数据包,以此来验证读取数据的正确性,结果如图12-6所示。
图 12-6 Wireshark捕获所发送的数据包
3.内核转储实例
对于文件转储,WinPcap提供了一个更好的途径,就是通过pcap_live_dump函数进行内核转储。应用程序可以使用pcap_live_dump_ended函数来检查数据是否存储完毕。不过,前面已提到,直到目前为止,WinPcap的内核转储特性都没被激活,因此实际上无法使用该特性。此处只给出相关的实例代码,具体测试结果参见第13章。实例的源代码如下,完整的代码见[ch12/kdump工程]。
defne WIN32
defne HAVE_REMOTE
include<stdlib.h>
include<stdio.h>
include<pcap.h>
include<Win32-Extensions.h>
int main(int argc,char**argv)
{
pcap_if_talldevs,d;
pcap_t*fp;
u_int inum,i=0;
char errbuf[PCAP_ERRBUF_SIZE];
int ret=-1;
printf("kdump:saves network traffc to fle using
WinPcap kernel-level dump faeature.\n");
printf("\t Usage:%s dump_fle_name max_size max_packs\n",argv[0]);
if(argc!=4)
{
fprintf(stderr,"Error in input arguments.\n");
exit(1);
}
……
/打开本机一个合适的网络设备,作为输出的网络适配器/
……
/开始内核文件转储/
if(pcap_live_dump(fp,argv[1],atoi(argv[2]),atoi(argv[3]))==-1)
{
printf("Unable to start the dump,%s\n",pcap_geterr(fp));
pcap_close(fp);
return-1;
}
/*
*等待,直到内核转储文件结束,
*也就是达到所设置的max_size或max_packs参数值
*/
while(1)
{
ret=pcap_live_dump_ended(fp,TRUE);
if(ret==1)
{
printf("Dump is end.\n");
break;
}
SleepEx(1000,FALSE);
}
/关闭适配器,使内核转储的文件正确地刷到文件上/
pcap_close(fp);
return 0;
}
该实例的运行结果如图12-7所示。
图 12-7 内核文件转储的测试结果
由下列代码可知,图12-7中所示"Unable to start the dump,Error setting dump mode"错误提示之所以出现,主要是因为WinPcap驱动程序不支持内核转储(MODE_DUMP)功能,参见下面的代码。
int main(int argc,char**argv)
{
……
if(pcap_live_dump(fp,argv[1],atoi(argv[2]),
atoi(argv[3]))==-1)
{
printf("Unable to start the dump,%s\n",
pcap_geterr(fp));
pcap_close(fp);
return-1;
}
……
}
int pcap_live_dump(pcap_tp,charflename,
int maxsize,int maxpacks)
{
……
/设置驱动程序为内核转储模式/
res=PacketSetMode(p->adapter,PACKET_MODE_DUMP);
if(res==FALSE){
sprintf(p->errbuf,"Error setting dump mode");
return-1;
}
……
}