ndis协议驱动开发

协议驱动的开发流程:

首先,一个协议驱动调用函数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().

然后把收到的数据以及一些信息传进来,我们把它们放入接收缓冲区。


  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值