7.2.2 关闭适配器的实现

WinPcap关闭适配器的实现主要依赖于wpcap.dll库中的pcap_cleanup_win32函数,而该函数主要依赖于Packet.dll库提供的PacketCloseAdapter函数,至于最终的适配器关闭是在内核空间NPF_Cleanup函数中调用NDIS库NdisCloseAdapter函数来完成的。

关闭适配器的主要函数调用关系图如图7-8所示。下面将详细介绍WinPcap中关闭适配器函数的具体设计与实现。

7.2.2 关闭适配器的实现 - 图1

图 7-8 关闭适配器的主要函数调用关系图

1.wpcap.dll库中关闭适配器的实现

在wpcap.dll库中,pcap_close函数用于释放打开函数所获得的相关资源。该函数实际上主要调用pcap_cleanup_win32函数。其原型如下:


void pcap_close(pcap_t*p);


上述函数中,参数p为pcap_open或pcap_open_XXX打开函数所获取的pcap_t结构体指针。

pcap_close函数的实现代码如下:


void pcap_close(pcap_t*p)

{

if(p->opt.source!=NULL)

free(p->opt.source);

p->cleanup_op(p);

free(p);

}


在调用pcap_open_live函数时,已在pcap_activate_win32函数中设置好了p->cleanup_op,即pcap_cleanup_win32函数,具体设置如下:


static int pcap_activate_win32(pcap_t*p)

{

……

p->cleanup_op=pcap_cleanup_win32;

……

}


所以调用pcap_close函数时,函数执行p->cleanup_op(p)语句就是调用pcap_cleanup_win32函数。而函数pcap_cleanup_win32则会关闭适配器,释放存储数据包的内存,最后调用pcap_cleanup_live_common函数清除过滤器,其具体实现代码如下:


static void pcap_cleanup_win32(pcap_t*p)

{

if(p->adapter!=NULL){

PacketCloseAdapter(p->adapter);

p->adapter=NULL;

}

if(p->Packet){

PacketFreePacket(p->Packet);

p->Packet=NULL;

}

pcap_cleanup_live_common(p);

}


上述代码中,函数pcap_cleanup_live_common主要是清除过滤器,其通过调用函数pcap_freecode来清除一个bpf_program结构体,释放与它相关联的所有内存(参见第10章)。其具体实现代码如下:


void pcap_cleanup_live_common(pcap_t*p)

{

if(p->buffer!=NULL){

free(p->buffer);

p->buffer=NULL;

}

if(p->dlt_list!=NULL){

free(p->dlt_list);

p->dlt_list=NULL;

p->dlt_count=0;

}

pcap_freecode(&p->fcode);

}


2.Packet.dll库中关闭适配器的实现

在wpcap.dll库中,pcap_cleanup_win32函数会调用Packet.dll库中PacketCloseAdapter函数来关闭适配器。而函数PacketCloseAdapter的作用就是关闭一个给定的适配器并释放相关联的ADAPTER结构体资源。其原型如下:


VOID PacketCloseAdapter(LPADAPTER lpAdapter);


上述函数中,参数lpAdapter为指向待关闭适配器的指针。

PacketCloseAdapter函数的主要实现代码如下:


VOID PacketCloseAdapter(LPADAPTER lpAdapter)

{

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

if(!lpAdapter)

{//失败,函数返回

return;

}

/*

*根据lpAdapter->Flags检查适配器的类型,并分别处理,

*此处只考虑正常的NPF适配器

*/

……

if(lpAdapter->Flags!=INFO_FLAG_NDIS_ADAPTER)

{//试图关闭一个不明类型的适配器

}

else

{

SetEvent(lpAdapter->ReadEvent);

//释放各种资源

CloseHandle(lpAdapter->ReadEvent);

CloseHandle(lpAdapter->hFile);

GlobalFreePtr(lpAdapter);

}

}


3.内核空间中关闭适配器的实现

这里主要使用NPF_Cleanup函数,该函数用于关闭一个驱动程序实例。对于一个正在运行的驱动程序实例,用户在调用CloseHandle系统函数时,该函数会被调用(响应IRP_MJ_CLOSE)。它停止捕获/监视/转储过程,并释放与该实例相关的资源。该函数原型如下:


NTSTATUS NPF_Cleanup(

IN PDEVICE_OBJECT DeviceObject,

IN PIRP Irp

);


上述函数中,参数DeviceObject指向用户所使用的设备对象;参数Irp指向包含用户请求的IRP。该函数最后返回操作的状态。

NPF_Cleanup函数的主要实现代码如下:


NTSTATUS NPF_Cleanup(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

{

POPEN_INSTANCE Open;

NDIS_STATUS Status;

PIO_STACK_LOCATION IrpSp;

LARGE_INTEGER ThreadDelay;

ULONG localNumOpenInstances;

IrpSp=IoGetCurrentIrpStackLocation(Irp);

Open=IrpSp->FileObject->FsContext;

NPF_CloseOpenInstance(Open);

if(Open->ReadEvent!=NULL)

KeSetEvent(Open->ReadEvent,0,FALSE);

/释放调用NdisOpenAdapter所建立的绑定与分配的资源/

NPF_CloseBinding(Open);

/释放所有的资源/

NPF_ReleaseOpenInstanceResources(Open);

/递减记录Open实例的计数器/

localNumOpenInstances=

InterlockedDecrement(&g_NumOpenedInstances);

if(localNumOpenInstances==0)

{

//在下一个NPF_Open中强制一个同步

//这是为了避免由休眠导致的同步问题

TIME_DESYNCHRONIZE(&G_Start_Time);

}

//完成该IRP,并且为成功的状态

Irp->IoStatus.Information=0;

Irp->IoStatus.Status=STATUS_SUCCESS;

IoCompleteRequest(Irp,IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}


上述代码中,NPF_Cleanup函数调用NPF_CloseBinding函数,并在此函数中调用NdisCloseAdapter库函数关闭网络适配器。接着调用NPF_CloseOpenInstance函数释放与该实例相关的资源。

(1)NPF_CloseOpenInstance

NPF_CloseOpenInstance函数用于释放与该Open实例相关的资源。


VOID NPF_CloseOpenInstance(IN POPEN_INSTANCE pOpen)

{

ULONG i=0;

NDIS_EVENT Event;

ASSERT(KeGetCurrentIrql()==PASSIVE_LEVEL);

NdisInitializeEvent(&Event);

NdisResetEvent(&Event);

NdisAcquireSpinLock(&pOpen->OpenInUseLock);

pOpen->ClosePending=TRUE;//不能再申请IRP操作

/等待,直到该运行实例上挂起的IRP个数为0/

while(pOpen->NumPendingIrps>0)

{

NdisReleaseSpinLock(&pOpen->OpenInUseLock);

NdisWaitEvent(&Event,1);//等待IRP执行完成的事件

NdisAcquireSpinLock(&pOpen->OpenInUseLock);

}

NdisReleaseSpinLock(&pOpen->OpenInUseLock);

}


(2)NPF_CloseBinding函数

NPF_CloseBinding函数主要用于解除调用NdisOpenAdapter库函数所建立的绑定与释放所分配的资源。其实现代码如下:


VOID NPF_CloseBinding(IN POPEN_INSTANCE pOpen)

{

NDIS_EVENT Event;

NDIS_STATUS Status;

NdisInitializeEvent(&Event);

NdisResetEvent(&Event);

NdisAcquireSpinLock(&pOpen->AdapterHandleLock);

while(pOpen->AdapterHandleUsageCounter>0)

{

NdisReleaseSpinLock(&pOpen->AdapterHandleLock);

NdisWaitEvent(&Event,1);

NdisAcquireSpinLock(&pOpen->AdapterHandleLock);

}

/现在使用计数为0/

while(pOpen->AdapterBindingStatus==ADAPTER_UNBINDING)

{

NdisReleaseSpinLock(&pOpen->AdapterHandleLock);

NdisWaitEvent(&Event,1);

NdisAcquireSpinLock(&pOpen->AdapterHandleLock);

}

/现在绑定状态可能为绑定也可能为未绑定/

if(pOpen->AdapterBindingStatus==ADAPTER_UNBOUND)

{

NdisReleaseSpinLock(&pOpen->AdapterHandleLock);

return;

}

pOpen->AdapterBindingStatus=ADAPTER_UNBINDING;

NdisReleaseSpinLock(&pOpen->AdapterHandleLock);

/执行释放过程/

NdisResetEvent(&pOpen->NdisOpenCloseCompleteEvent);

//解除调用NdisOpenAdapter库函数所建立的绑定与释放所分配的资源

NdisCloseAdapter(

&Status,

pOpen->AdapterHandle

);

//检测是否为挂起状态

if(Status==NDIS_STATUS_PENDING)

{//挂起,等待NdisOpenCloseCompleteEvent事件,

//在完成函数NPF_CloseAdapterComplete中设置该事件

NdisWaitEvent(&pOpen->NdisOpenCloseCompleteEvent,0);

}

else

{

;

}

/设置绑定状态为未绑定/

NdisAcquireSpinLock(&pOpen->AdapterHandleLock);

pOpen->AdapterBindingStatus=ADAPTER_UNBOUND;

NdisReleaseSpinLock(&pOpen->AdapterHandleLock);

}


(3)NPF_CloseAdapterComplete函数

NPF_CloseAdapterComplete函数用于结束适配器的关闭,为NDIS库中NdisCloseAdapter函数关联的回调函数,当NIC驱动程序完成一个关闭操作(在NPF_CloseBinding函数中调用NdisCloseAdapter库函数启动关闭操作)时此函数就会被NDIS调用。该函数原型如下:


VOID NPF_CloseAdapterComplete(

IN NDIS_HANDLE ProtocolBindingContext,

IN NDIS_STATUS Status

);


上述函数中,参数ProtocolBindingContext指向一个上下文区块,包含一个指向与当前实例关联的OPEN_INSTANCE结构体指针;参数Status描述NIC驱动程序关闭操作的最终状态。

NPF_CloseAdapterComplete函数的主要实现代码如下:


VOID NPF_CloseAdapterComplete(

IN NDIS_HANDLE ProtocolBindingContext,

IN NDIS_STATUS Status)

{

POPEN_INSTANCE Open;

PIRP Irp;

Open=(POPEN_INSTANCE)ProtocolBindingContext;

ASSERT(Open!=NULL);

/设置NdisOpenCloseCompleteEvent事件/

NdisSetEvent(&Open->NdisOpenCloseCompleteEvent);

TRACE_EXIT();

return;

}


(4)NPF_ReleaseOpenInstanceResources函数

NPF_ReleaseOpenInstanceResources函数用于释放在NPF_Open函数中所分配的一些资源。其主要实现代码如下:


VOID NPF_ReleaseOpenInstanceResources(POPEN_INSTANCE pOpen)

{

PKEVENT pEvent;

UINT i;

ASSERT(pOpen!=NULL);

ASSERT(KeGetCurrentIrql()==PASSIVE_LEVEL);

/释放为数据包接收与发送所分配的内存缓冲池/

NdisFreePacketPool(pOpen->PacketPool);

ifdef HAVE_BUGGY_TME_SUPPORT

/释放TME虚拟协处理器的内存/

pOpen->mem_ex.size=0;

if(pOpen->mem_ex.buffer!=NULL)

ExFreePool(pOpen->mem_ex.buffer);

endif//HAVE_BUGGY_TME_SUPPORT

/释放过滤器,如果存在/

if(pOpen->bpfprogram!=NULL)

ExFreePool(pOpen->bpfprogram);

//Jitted过滤器仅仅被x86(32位)架构支持

ifdefX86

/释放jitted过滤器,如果存在/

if(pOpen->Filter!=NULL)

BPF_Destroy_JIT_Filter(pOpen->Filter);

endif//X86

/递减ReadEvent事件对象的引用计数,并执行保留状态检查/

if(pOpen->ReadEvent!=NULL)

ObDereferenceObject(pOpen->ReadEvent);

/释放缓冲区/

//注意:该缓冲区在各CPU间被分割成片,

//但是所分配的大块内存的基地址仍然存储在

//第一个位置(pOpen->CpuData[0])

if(pOpen->Size>0)

ExFreePool(pOpen->CpuData[0].Buffer);

/释放各CPU缓冲区的自旋锁/

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

{

NdisFreeSpinLock(&Open->CpuData[i].BufferLock);

}

/释放存储转储文件名的字符串/

if(pOpen->DumpFileName.Buffer!=NULL)

ExFreePool(pOpen->DumpFileName.Buffer);

}