7.2.2 关闭适配器的实现
WinPcap关闭适配器的实现主要依赖于wpcap.dll库中的pcap_cleanup_win32函数,而该函数主要依赖于Packet.dll库提供的PacketCloseAdapter函数,至于最终的适配器关闭是在内核空间NPF_Cleanup函数中调用NDIS库NdisCloseAdapter函数来完成的。
关闭适配器的主要函数调用关系图如图7-8所示。下面将详细介绍WinPcap中关闭适配器函数的具体设计与实现。
图 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);
}