协议驱动的开发流程:
首先,一个协议驱动调用函数ndisRegisterProtocol()先把自己注册为协议驱动,此举的意义告诉windows,我是一个ndis协议驱动,并将约定好的回调函数的列表告诉windows,这样以后当有与网络相关的事件发生时,windows会调用相应的回调函数,并且在参数中传入适当的信息,当然这些回调函数是我们要自己实现的。
NdisRegisterProtocol(
(PNDIS_STATUS)&status,
&Globals.NdisProtocolHandle,
&protocolChar,
sizeof(NDIS50_PROTOCOL_CHARACTERISTICS));
其中,第三个参数protocolChar中存放的是一系列的回调函数指针,
protocolChar.MajorNdisVersion = 5;
protocolChar.MinorNdisVersion = 0;
protocolChar.Name = protoName;
protocolChar.OpenAdapterCompleteHandler = NdisProtOpenAdapterComplete;
protocolChar.CloseAdapterCompleteHandler = NdisProtCloseAdapterComplete;
protocolChar.SendCompleteHandler = NdisProtSendComplete;
protocolChar.TransferDataCompleteHandler = NdisProtTransferDataComplete;
protocolChar.ResetCompleteHandler = NdisProtResetComplete;
protocolChar.RequestCompleteHandler = NdisProtRequestComplete;
protocolChar.ReceiveHandler = NdisProtReceive; //当网卡接到数据时,系统会
//调用此函数或者ReceivePacketHandler,把接收到的数据传进来。
protocolChar.ReceiveCompleteHandler = NdisProtReceiveComplete;
protocolChar.StatusHandler = NdisProtStatus;
protocolChar.StatusCompleteHandler = NdisProtStatusComplete;
protocolChar.BindAdapterHandler = NdisProtBindAdapter;//当系统发现一个网卡时,调用//此函数。传入网卡信息。
protocolChar.UnbindAdapterHandler = NdisProtUnbindAdapter;
protocolChar.UnloadHandler = NULL;
protocolChar.ReceivePacketHandler = NdisProtReceivePacket;//当网卡接到数据时,系统会//调用此函数或者NdisProtReceive,把接收到的数据传进来。
protocolChar.PnPEventHandler = NdisProtPnPEventHandler;
NdisRegisterProtocol()的第二个参数Globals.NdisProtocolHandle(NDIS_HANDLE)中保存的系统返回的ndis_handle, Pointer to a caller-supplied variable in which thisfunction returns a handle representing the registered driver.
但是这个参数在后面并没有什么用。
回调函数详解:
VOID
NdisProtBindAdapter(
OUT PNDIS_STATUS pStatus,
IN NDIS_HANDLE BindContext,
//系统传进来的ndis_handle,和ndisregisterprotocol中返回的ndis_handle是一个吗?(可以用windbg调//试看看)如果不是,那么它是做什么的?在我们的代码中没有使用到这个handle,不需要理会。
IN PNDIS_STRING pDeviceName,
IN PVOID SystemSpecific1,
IN PVOID SystemSpecific2
)
在这个函数里,必须要把这个网卡的相关信息传进来。
这个pDeviceName是\DEVICE\{GUID}这种格式吗?需要调试观察.
函数内部主要做的几件事,一是连接上下文变量的创建和赋值,然后是ndisopenadpter,然后是创建用来发送和接收的包池,然后是调用ndisRequest()获取一些参数,比如网卡的最大发送帧,MAC地址等。
关于连接上下文: 它是这样一个数据结构:
typedef struct _NDISPROT_OPEN_CONTEXT
{
LIST_ENTRY Link; // Link into global list
ULONG Flags; // State information
ULONG RefCount;
NPROT_LOCK Lock;
PFILE_OBJECT pFileObject; // Set onOPEN_DEVICE
NDIS_HANDLE BindingHandle;
NDIS_HANDLE SendPacketPool;
NDIS_HANDLE SendBufferPool;
NDIS_HANDLE RecvPacketPool;
NDIS_HANDLE RecvBufferPool;
ULONG MacOptions;
ULONG MaxFrameSize;
LIST_ENTRY PendedWrites; // pended WriteIRPs
ULONG PendedSendCount;
LIST_ENTRY PendedReads; // pended Read IRPs
ULONG PendedReadCount;
LIST_ENTRY RecvPktQueue; // queued rcvpackets
ULONG RecvPktCount;
NET_DEVICE_POWER_STATE PowerState;
NDIS_EVENT PoweredUpEvent; // signalled iffPowerState is D0
NDIS_STRING DeviceName; // used inNdisOpenAdapter
NDIS_STRING DeviceDescr; // friendly name
NDIS_STATUS BindStatus; // forOpen/CloseAdapter
NPROT_EVENT BindEvent; // forOpen/CloseAdapter
BOOLEAN bRunningOnWin9x;// TRUE ifWin98/SE/ME, FALSE if NT
ULONG oc_sig; // Signature for sanity
UCHAR CurrentAddress[NPROT_MAC_ADDR_LEN];
PIRP StatusIndicationIrp;
} NDISPROT_OPEN_CONTEXT, *PNDISPROT_OPEN_CONTEXT;
对于系统中的每一块网卡,OS都会调用ndisprot中的NdisProtBindAdapter回调函数,并在其中传入网卡名字,我们在这里会创建一个连接上下文变量,这个变量是new来的,所以会一直存在。另外,为了管理,我们需要把第一块网卡对应的连接上下文变量,做成一个链表,其中用到了list_entry变量。
LIST_ENTRY用法:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
可以看到,在list_entry变量中,只有两个指针,没有存储数据在地方,那么它是如何应用的呢?
我们在这里结合pContext链表来说明:
首先有个系统全局变量:NDISPROT_GLOBALS Globals;
其中有个变量LIST_ENTRY OpenList;
在driverEntry中,会先调用NPROT_INIT_LIST_HEAD(&Globals.OpenList);来初始化这个变量。
(初始化后,OpenList中的Flink,Blink应该都指向OpenList变量自己?需要调试验证。)
在pContext中,第一个变量是LIST_ENTRY Link; // Link into global list
调用
NPROT_INSERT_TAIL_LIST(&Globals.OpenList, &pOpenContext->Link);
那么Globals.OpenList.Blink则指向了pOpenContext->Link.
当程序需要遍历所有的pContext链表时,
即:ndisprotLookupDevice()函数的实现,
for (pEnt = Globals.OpenList.Flink;
pEnt != &Globals.OpenList;
pEnt = pEnt->Flink)
{
pOpenContext = CONTAINING_RECORD(pEnt,NDISPROT_OPEN_CONTEXT, Link);
NPROT_STRUCT_ASSERT(pOpenContext, oc);
//
// Check if this has the name we are looking for.
//
if ((pOpenContext->DeviceName.Length== BindingInfoLength) &&
NPROT_MEM_CMP(pOpenContext->DeviceName.Buffer, pBindingInfo,BindingInfoLength))
{
NPROT_REF_OPEN(pOpenContext); // ref added by LookupDevice
break;
}
pOpenContext = NULL;
}
其中重点是:
pOpenContext =CONTAINING_RECORD(pEnt, NDISPROT_OPEN_CONTEXT, Link);
这个宏找到了Link所在的内存,而link所在的内存正好是pcontext的第一个字节,所以就可以访问pcontext中的所有变量了。
当我们调用NdisOpenAdapter()时候,其中的第三个参数,ndis_handlebindinghandle:
Pointer to a caller-supplied variable in which NDISreturns a handle representing a successful binding between the caller and thegiven physical or virtual NIC specified at AdapterName.
现在看来,ndis_handle可以代表很多东西。
如果NdisOpenAdapter()的返回值是pending,
NdisOpenAdapter(
&Status,
&OpenErrorCode,
&pOpenContext->BindingHandle,
//Pointer to a caller-supplied variable in which NDIS returns a handlerepresenting a successful binding between the caller and the given physical orvirtual NIC specified at AdapterName.
//这个参数非常重要,以后要发送或者接收数据时,要指定这个HANDLE。
//为什么是这个handle,而不是ndisregisterprotocol()返回的handle呢?因为那个handle只代表协议驱动,不能根据它找到网卡!
sizeof(MediumArray) / sizeof(NDIS_MEDIUM),
Globals.NdisProtocolHandle,//
(NDIS_HANDLE)pOpenContext,
//系统对这个参数做什么了?
//没做什么,只是系统把这个值保存下来了,以后系统处理某个网卡时,可以把这个上下文
//直接作为参数传回来给我们?比如后面的解除绑定的时候。
//其实如果系统不记录下来也行,我们也可以自己记录这个值。
&pOpenContext->DeviceName,
0,
NULL);
指派网卡的时机,以及做的事情:
因为一个协议驱动可以绑定好几块网卡,一块网卡也可以绑定好几个协议。
当一块网卡和一个协议之间绑定的时候NdisOpenAdapter,会生成一个ndis_handle.,它被存在pOpenContext->BindingHandle中,以后当我们要发送数据时候,调用NdisSendPackets(pOpenContext->BindingHandle,&pNdisPacket, 1);其中的第一个参数就是这个handle.
在系统中每一个协议和网卡绑定就会有一个pOpenContext, 其中存放着BindingHandle,还有网卡名字,
要使用哪个网卡来发数,只有使用者才知道,然后通过上位机程序,将网卡名字用deviceIOcontrl()发给驱动,驱动根据这个名字找到对应的pOpenContext, 放在fileObject->FsContext中,因为当我们用creteFile()创建一个文件后,系统会为它创建一个fileObject,这是一个在系统中唯一的值. 下次当我们对该文件调用writeFile()时候,系统还是使用这个fileObject,也就是当我们从驱动的irp中取出fileObject->FsConText,里面还是我们在指派网卡的时候放进去的那个值。
处理读请求(IRP_MJ_READ),即收包:
读请求最张的目标是用从接收缓冲区中把包拷贝给应用层:
NPROT_COPY_MEM(pDst, pSrc, BytesToCopy);
其中的pDst是:
pDst =MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
pSrc:从pOpenContext中就可以得到。
pRcvPacketEntry =pOpenContext->RecvPktQueue.Flink;
pNdisBuffer =NDIS_PACKET_FIRST_NDIS_BUFFER(pRcvPacket);
NdisQueryBufferSafe(pNdisBuffer, &pSrc,&BytesAvailable, NormalPagePriority);
处理写请求(IRP_MJ_WRITE),即发包:
NdisAllocatePacket( &Status,&pNdisPacket, pOpenContext->SendPacketPool);
pNdisBuffer = pIrp->MdlAddress;
NdisChainBufferAtFront(pNdisPacket,pNdisBuffer);
NdisSendPackets(pOpenContext->BindingHandle,&pNdisPacket, 1);
处理读请求是在接收到上层应用程序的读命令后,把协议驱动缓冲区中的数据包传给上位机的buffer中。
那么协议驱动缓冲区的数据是由网卡发来的,这个过程不是全自动的,需要我们编写接收包的回调处理来接收网卡接收到的数据包:
当网卡接收到数据包后,会调用回调函数,NdisProtReceive()或者NdisProtReceivePacket().
然后把收到的数据以及一些信息传进来,我们把它们放入接收缓冲区。