11.2.4 网络状态统计的实现

网络状态统计实现的主要函数调用关系如图11-8所示。

11.2.4 网络状态统计的实现 - 图1

图 11-8 网络状态统计实现的主要函数调用关系

1.wpcap.dll库中相应接口函数的实现

(1)pcap_stats函数

该函数用于获得当前捕获的统计信息,其原型如下:


int pcap_stats(pcap_tp,struct pcap_statps)


函数执行成功将返回0,并填充pcap_stat结构体。如果出现错误或底层数据包捕获不支持数据包统计,则返回-1,错误信息可通过pcap_perror或pcap_geterr获得。

该函数的具体实现如下:


int pcap_stats(pcap_tp,struct pcap_statps)

{

return p->stats_op(p,ps);

}

static int pcap_activate_win32(pcap_t*p)

{

……

p->stats_op=pcap_stats_win32;

……

}

/真正获得统计信息的函数/

static int pcap_stats_win32(pcap_tp,struct pcap_statps)

{

if(PacketGetStats(p->adapter,(struct bpf_stat*)ps)!=

TRUE)

{

return-1;

}

return 0;

}


注意,pcap_stats仅被活动的捕获类型源支持,而不被存储文件(savefiles)的类型源支持。savefiles中没有存储统计信息,因此读取savefile得不到可用的统计信息。

(2)pcap_stats_ex函数

pcap_stats_ex函数用于返回当前捕获的统计信息,其原型如下:


pcap_statpcap_stats_ex(pcap_tp,int*pcap_stat_size)


上述函数中,参数p指向当前使用的pcap_t结构体;参数pcap_stat_size指向一个整形变量,当该函数返回时将包含系统所分配的pcap_stat结构体的大小。

如果pcap_stats_ex函数执行成功,则返回一个指向pcap_stat的结构体,其中包含了当前网络设备的状态统计信息。如果出现错误,则返回NULL,通过pcap_perror或pcap_geterr可获得错误信息。

pcap_stats_ex函数是对pcap_stats函数的扩展,其允许返回更多的统计参数,同时pcap_stat结构体不再由用户分配,而是由系统分配。也就是说扩展pcap_stat结构体,并不会影响向后的兼容性。一种实现兼容性的简单方法就是,以前的应用程序只检查结构体开始的一些成员值,而新的应用程序能够读取新的附加在结构体尾部的统计值。该扩展方式明显要比从用户空间直接传递pcap_stat结构体更安全。在实际应用中,可能会发生分配的用户空间不够大的情况,这将导致错误产生,而使用这种扩展方式,我们可确保对所分配的内存进行正确的操作。

该函数的具体实现如下:


struct pcap_statpcap_stats_ex(pcap_tp,int*pcap_stat_size)

{

*pcap_stat_size=sizeof(struct pcap_stat);

if(p->adapter==NULL)

{//错误

return NULL;

}

/获得状态统计信息/

if(PacketGetStatsEx(

p->adapter,(struct bpf_stat*)(&p->md.stat))!=TRUE)

{//错误,无法获得统计信息

return NULL;

}

return(&p->md.stat);

}


注意,pcap_stats_ex函数仅支持活动的捕获类型源,而不支持文件(savefiles)类型源。在savefiles中没有存储统计信息,因此读取savefiles得不到可用的统计信息。

2.Packet.dll库中相应接口函数的实现

(1)PacketGetStats函数

PacketGetStats函数会为当前捕获会话返回两个状态统计信息值,其原型如下:


BOOLEAN PacketGetStats(LPADAPTER AdapterObject,struct bpf_stat*s)


上述函数中,参数AdapterObject指向一个_ADAPTER结构体,以描述当前的适配器;参数s指向一个用户提供的bpf_stat结构体,这个结构体将由该函数填充。

如果PacketGetStats函数执行成功则返回非0值。

通过该函数可以获得驱动程序的两个内部变量值:

❑从调用PacketOpenAdapter函数打开适配器AdapterObject开始,适配器所接收的数据包个数。

❑驱动程序所丢弃的数据包个数。比如与适配器关联的内核缓冲区满时,一个数据包将被丢弃。

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


BOOLEAN PacketGetStats(LPADAPTER AdapterObject,

struct bpf_stat*s)

{

BOOLEAN Res;

DWORD BytesReturned;

//申明该临时结构体,防止内核与用户空间类型不一致

struct bpf_stat tmpstat;

if(AdapterObject->Flags==INFO_FLAG_NDIS_ADAPTER)

{//获得状态结构体的值

Res=(BOOLEAN)DeviceIoControl(

AdapterObject->hFile,

BIOCGSTATS,

NULL,

0,

&tmpstat,

sizeof(struct bpf_stat),

&BytesReturned,

NULL);

if(Res)

{

//只复制驱动程序中前两个成员

s->bs_recv=tmpstat.bs_recv;

s->bs_drop=tmpstat.bs_drop;

}

}

else

{//未知设备类型,错误

Res=FALSE;

}

return Res;

}


(2)PacketGetStatsEx函数

PacketGetStatsEx函数用于返回当前捕获会话的状态统计信息值,它为PacketGetStats函数的加强版,其原型如下:


BOOLEAN PacketGetStatsEx(LPADAPTER AdapterObject,struct bpf_stat*s)


上述函数中,参数AdapterObject指向一个_ADAPTER结构体,描述当前的适配器;参数s指向一个用户提供的bpf_stat结构体,这个结构体将由该函数填充。

如果PacketGetStatsEx函数执行成功则返回非0值。

通过该函数,获得的驱动程序变量值比PacketGetStats函数多,比如,增加了下列值:

❑由网络接口丢弃的数据包个数(不过现在NPF驱动程序还不支持,总设置为0)。

❑到达应用程序的数据包个数,也就是通过了内核过滤器并且被合适地放置在内核缓冲区中的数据包个数。

该函数的具体实现如下:


BOOLEAN PacketGetStatsEx(LPADAPTER AdapterObject,

struct bpf_stat*s)

{

BOOLEAN Res;

DWORD BytesReturned;

struct bpf_stat tmpstat;

if(AdapterObject->Flags==INFO_FLAG_NDIS_ADAPTER)

{//获得状态结构体的值

Res=(BOOLEAN)DeviceIoControl(

AdapterObject->hFile,

BIOCGSTATS,

NULL,

0,

tmpstat,

sizeof(struct bpf_stat),

&BytesReturned,

NULL);

if(Res)

{

//从驱动复制四个成员值

s->bs_recv=tmpstat.bs_recv;

s->bs_drop=tmpstat.bs_drop;

s->ps_ifdrop=tmpstat.ps_ifdrop;

s->bs_capt=tmpstat.bs_capt;

}

}

else

{//未知设备类型,错误

Res=FALSE;

}

return Res;

}


3.驱动程序中对应的函数

(1)NPF_IoControl函数

NPF_IoControl函数的BIOCGSTATS命令码用来获得网络状态统计信息,主要代码实现如下:


/#defne BIOCGSTATS 9031/

PUINT pStats;

……

case BIOCGSTATS:

/四个状态统计量占用的内存空间长度/

StatsLength=4*sizeof(UINT);

/检查输入参数的合法性/

if(IrpSp->Parameters.DeviceIoControl.OutputBufferLength<StatsLength)

{

SET_FAILURE_BUFFER_SMALL();

break;

}

if(Irp->UserBuffer==NULL)

{

SET_FAILURE_UNSUCCESSFUL();

break;

}

/获得与锁定用户空间传递的内存地址/

mdl=NULL;

pStats=NULL;

__try

{

mdl=IoAllocateMdl(

Irp->UserBuffer,

StatsLength,

FALSE,

TRUE,

NULL);

if(mdl==NULL)

{

SET_FAILURE_UNSUCCESSFUL();

break;

}

MmProbeAndLockPages(mdl,UserMode,IoWriteAccess);

pStats=(PUINT)(Irp->UserBuffer);

}

__except(GetExceptionCode()==STATUS_ACCESS_VIOLATION)

{

pStats=NULL;

}

if(pStats==NULL)

{

if(mdl!=NULL)

{

IoFreeMdl(mdl);

}

SET_FAILURE_UNSUCCESSFUL();

break;

}

/复位各状态值/

pStats[3]=0;

pStats[0]=0;

pStats[1]=0;

pStats[2]=0;//该状态值不被支持

/计算各状态值/

for(i=0;i<g_NCpu;i++)

{

pStats[3]+=Open->CpuData[i].Accepted;

pStats[0]+=Open->CpuData[i].Received;

pStats[1]+=Open->CpuData[i].Dropped;

pStats[2]+=0;//该状态值不被支持,始终为0

}

/解除内存锁定/

MmUnlockPages(mdl);

IoFreeMdl(mdl);

SET_RESULT_SUCCESS(StatsLength);

break;


(2)NPF对统计信息的更新

NPF驱动程序在NPF_tap函数中更新网络状态统计信息,相关代码如下:


/代码多处调用下面的语句,更新网络状态信息/

LocalData->Dropped++;

……

LocalData->Accepted++;


NPF驱动程序在函数NPF_TransferDataComplete中更新网络流量统计信息,相关代码如下:


……

//数据包被正确复制到内核缓冲区中,增加已接收的数据包个数

LocalData->Accepted++;

……