12.4 数据包文件读取的幕后

数据包文件读取的主要函数调用关系如图12-9所示。

12.4 数据包文件读取的幕后 - 图1

图 12-9 数据包文件读取的主要函数调用关系

在pcap_open函数中,如果打开的源是文件,那么就会调用pcap_open_offline函数来打开该文件,该函数的具体实现代码如下:


pcap_tpcap_open(const charsource,int snaplen,int fags,int read_timeout,

struct pcap_rmtauthauth,charerrbuf)

{

……

/分析源的种类,是文件、本地主机接口还是远程主机接口类型/

if(pcap_parsesrcstr(source,&type,host,

port,name,errbuf)==-1)

{

return NULL;

}

/依据不同的源类型做不同的处理/

switch(type)

{

//文件类型,直接调用pcap_open_offline函数处理

case PCAP_SRC_FILE:

fp=pcap_open_offine(name,errbuf);

break;

……

}

……

}


上述代码中,pcap_open_offline函数用来打开一个savefiles文件,从而读取数据包,其原型如下:


pcap_tpcap_open_offine(const charfname,char*errbuf)


上述函数中,参数fname描述需要打开的文件,-表示stdin文件。注意,在Windows平台上,该文件应该以二进制模式打开。

参数errbuf用来返回错误信息,仅在pcap_open_offline或pcap_fopen_offline执行失败并返回NULL时,才会设置错误信息。

pcap_open_offline函数的具体实现代码如下:


pcap_tpcap_open_offine(const charfname,char*errbuf)

{

FILE*fp;

pcap_t*p;

if(fname[0]=='-'&&fname[1]=='\0'){

/从标准输入读取,因此把它设置为二进制模式/fp=stdin;

SET_BINMODE(fp);

}

else

{

/savefiles也是二进制模式/

fp=fopen(fname,"rb");//打开文件

if(fp==NULL){

snprintf(errbuf,PCAP_ERRBUF_SIZE,"%s:%s",fname,

pcap_strerror(errno));

return(NULL);

}

}

/打开并读取一个已存在的文件流/

p=pcap_fopen_offine(fp,errbuf);

if(p==NULL){

if(fp!=stdin)

fclose(fp);

}

return(p);

}


1.pcap_fopen_offline函数

pcap_fopen_offline函数用于打开并读取一个已存在的文件流,其原型如下:


static pcap_tpcap_fopen_offine(FILEfp,char*errbuf)

{

register pcap_t*p;

struct pcap_fle_header hdr;

size_t amt_read;

bpf_u_int32 magic;

int linklen;

p=(pcap_t)malloc(sizeof(p));

if(p==NULL){

strlcpy(errbuf,"out of swap",PCAP_ERRBUF_SIZE);

return(NULL);

}

memset((char)p,0,sizeof(p));

/读取文件头信息/

amt_read=fread((char*)&hdr,1,sizeof(hdr),fp);

if(amt_read!=sizeof(hdr))

{//出错处理

……

goto bad;

}

/判断文件格式/

magic=hdr.magic;

if(magic!=TCPDUMP_MAGIC&&

magic!=KUZNETZOV_TCPDUMP_MAGIC)

{

magic=SWAPLONG(magic);//交换字节顺序,再判断一次

if(magic!=TCPDUMP_MAGIC&&

magic!=KUZNETZOV_TCPDUMP_MAGIC){

snprintf(errbuf,PCAP_ERRBUF_SIZE,

"bad dump fle format");

goto bad;

}

p->sf.swapped=1;

swap_hdr(&hdr);

}

/此处只分析magic为TCPDUMP_MAGIC的情况/

p->sf.hdrsize=sizeof(struct pcap_sf_pkthdr);

if(hdr.version_major<PCAP_VERSION_MAJOR){

snprintf(errbuf,PCAP_ERRBUF_SIZE,"archaic fle format");

goto bad;

}

p->tzoff=hdr.thiszone;

p->snapshot=hdr.snaplen;

p->linktype=linktype_to_dlt(LT_LINKTYPE(hdr.linktype));

p->linktype_ext=LT_LINKTYPE_EXT(hdr.linktype);

p->sf.rfle=fp;

/为结构体pcap_pkthdr分配空间,其将被pcap_read_ex函数使用/

p->bufsize=hdr.snaplen+sizeof(struct pcap_pkthdr);

/对齐数据链路层头位置/

switch(p->linktype){

case DLT_EN10MB:

linklen=14;

break;

case……

}

if(p->bufsize<0)

p->bufsize=BPF_MAXBUFSIZE;

p->sf.base=(u_char*)malloc(p->bufsize+BPF_ALIGNMENT);

if(p->sf.base==NULL){

strlcpy(errbuf,"out of swap",PCAP_ERRBUF_SIZE);

goto bad;

}

p->buffer=p->sf.base+BPF_ALIGNMENT-(linklen%BPF_ALIGNMENT);

p->sf.version_major=hdr.version_major;

p->sf.version_minor=hdr.version_minor;

/*

*处理不同版本之间存在的差异,

*version 2.3中数据包头的caplen与len成员相互交换的情况与其他版本不一样,

*此外DG/UX tcpdump把版本字段写为543.0

*/

switch(hdr.version_major){

case 2:

if(hdr.version_minor<3)

p->sf.lengths_swapped=SWAPPED;

else if(hdr.version_minor==3)

p->sf.lengths_swapped=MAYBE_SWAPPED;

else

p->sf.lengths_swapped=NOT_SWAPPED;

break;

case 543:

p->sf.lengths_swapped=SWAPPED;

break;

default:

p->sf.lengths_swapped=NOT_SWAPPED;

break;

}

/设置具体的操作函数,不过大部分都是无效的/

p->read_op=pcap_offine_read;

p->inject_op=sf_inject;

p->setflter_op=install_bpf_program;

p->setdirection_op=sf_setdirection;

p->set_datalink_op=NULL;

p->getnonblock_op=sf_getnonblock;

p->setnonblock_op=sf_setnonblock;

p->stats_op=sf_stats;

ifdef WIN32

p->setbuff_op=sf_setbuff;

p->setmode_op=sf_setmode;

p->setmintocopy_op=sf_setmintocopy;

endif

p->cleanup_op=sf_cleanup;

p->activated=1;

return(p);

bad:

free(p);

return(NULL);

}


pcap_fopen_offline函数中所设置的具体操作函数大部分都是无效的,如sf_stats函数的实现代码如下:


static int sf_stats(pcap_tp,struct pcap_statps)

{

snprintf(p->errbuf,PCAP_ERRBUF_SIZE,

"Statistics aren't available from savefles");

return(-1);

}


数据包的实际读取操作是在pcap_loop函数中调用pcap_offline_read函数实现的,具体代码如下:


int pcap_loop(pcap_tp,int cnt,pcap_handler callback,u_charuser)

{

……

for(;;)//循环读取

{

if(p->sf.rfle!=NULL)//读取文件

{

/如果返回0,则表明到达EOF(文件结尾)处,将不会再循环/

n=pcap_offine_read(p,cnt,callback,user);

}

……

}

}


2.pcap_offline_read函数

pcap_offline_read函数用于读取转储文件的数据包,其具体的实现代码如下:


int pcap_offine_read(pcap_tp,int cnt,pcap_handler callback,u_charuser)

{

struct bpf_insn*fcode;

int status=0;

int n=0;

while(status==0){

struct pcap_pkthdr h;

/是否调用了pcap_breakloop函数?/

if(p->break_loop){

……

}

status=sf_next_packet(p,&h,p->buffer,p->bufsize);

if(status){

……

}

/过滤数据包/

if((fcode=p->fcode.bf_insns)==NULL||

bpf_flter(fcode,p->buffer,h.len,h.caplen))

{

(*callback)(user,&h,p->buffer);

if(++n>=cnt&&cnt>0)

break;

}

}

return(n);

}


上述代码中,sf_next_packet函数用于读取转储文件并返回下一个数据包,具体实现代码如下:


static int sf_next_packet(pcap_tp,struct pcap_pkthdrhdr,

u_char*buf,u_int bufen)


在上面的代码中,参数hdr返回数据包头,参数buf返回数据包内容。

sf_next_packet函数执行成功则返回0,若返回SFERR_EOF则说明到达文件尾部,而返回SFERR_TRUNC则表示遇到一个不完整的包。