如何把NDIS Filter框架利用到日常的Windows驱动开发工作中

以前,我在看雪发表了一篇文章,文章如下。

【原创】NDIS中间层驱动开发在Win7系统下和Windows XP系统下的区别
 http://bbs.pediy.com/showthread.php?t=137545


当时,我只写了一个大概,抛砖引玉,讲述了NDIS Filter框架,告诉大家NDIS Filter大概怎么用,有什么样的变动。


最近这几年,Windows8和Windows8.1操作系统逐渐地成为主流,被越来越多人使用。而网络上有关NDIS驱动开发的文章,还都是停留在NDIS Passthru。


基于NDIS Filter框架的网络数据包,它的格式是以NET_BUFFER_LIST、NET_BUFFER、MDL为主导的。
一个NET_BUFFER_LIST是由一个NET_BUFFER链表组成, 而1个NET_BUFFER是由1个MDL链表组成。但网络数据包含了 多个NET_BUFFER_LIST
 
网络数据结构如下图所示:

如何把NDIS Filter框架利用到日常的Windows驱动开发工作中_第1张图片

你可以按照我的下述2种程序代码的实现方法,来获取数据包的内容。你可以把代码直接添加到你的NDIS Filter工程代码里面。


下面的代码,我测试通过。

代码编译环境:
Visual Studio 2013
WDK8.1


测试通过的Windows系统有:
32位Windows7
64位Windows7

32位Windows8
64位Windows8

32位Windows8.1
64位Windows8.1

WindowsXP,不支持 NDIS Filter框架。



这个帖子的附件是一个完整的NDIS Filter工程代码,可直接下载,编译。必须在Visual Studio 2013和WDK8.1环境下进行。
基本上不蓝屏幕。如果发生蓝屏的情况,请不要着急,你可以在代码中找到如下的宏定义:
#define   MAX_BUFFER_SIZE  1024*8

你把这个MAX_BUFFER_SIZE 宏修改成你所想要的大小。这样就不蓝屏了。我建议最好是更大一些。最好大于 1024*8

代码在编译的时候,会出现错误:
  error C2220: warning treated as error - no 'object' file generated  D:\NdisLwfDemo\NdisLwfDemo\filter.c  1328

你在Visual Studio 2013环境下配置。图如下:

如何把NDIS Filter框架利用到日常的Windows驱动开发工作中_第2张图片


你把Warning Level修改成:   Turn Off All Warnings(/W0)
这样,就编译正确了。





第一种方法:

VOID
PSQueryPacketLength(
        IN PNET_BUFFER_LIST Packet,
        OUT OPTIONAL PUINT32 Length
       )

{
  PNET_BUFFER pNetBuffer = NET_BUFFER_LIST_FIRST_NB(Packet);
  BOOLEAN FLAG = FALSE;

  do 
  {
    if (NULL == Length)
    {
      break;
    }

    *Length = 0;

    while (pNetBuffer)
    {
      *Length += NET_BUFFER_DATA_LENGTH(pNetBuffer);
      pNetBuffer = NET_BUFFER_NEXT_NB(pNetBuffer);
    }
  } while (FLAG);
  
  return;
}


BOOLEAN
GetOneNetBufferListData(
      __in PNET_BUFFER_LIST Packet, 
      __out PUCHAR* pBuf, 
      __out PUINT32 BufLen
             )
{
  NDIS_STATUS Status = NDIS_STATUS_SUCCESS;
  PNET_BUFFER_LIST pCurrentNBL = NULL;
  PNET_BUFFER pCurrentNB = NULL;
  int lTotalLength = 0;
  PMDL pMdl = NULL;  
  PUCHAR pData = NULL;
  int lOffset = 0;
  int lDataBufferLen = 0;
  int lBytesToCopy = 0;
  int lMdlOffset = 0;

  pCurrentNBL = Packet;
  PSQueryPacketLength(pCurrentNBL,(PUINT32)&lTotalLength);


  if (lTotalLength != 0)
  {
    pBuf=ExAllocatePoolWithTag(NonPagedPool,lTotalLength,'tag1');
    if (NULL == *pBuf)
    {
      DEBUGP(DL_WARN,("%s: Allocate memory failed! Status=0x%08x\n",__FUNCTION__,Status));
      return FALSE;
    }
  }
  else
  {
    DEBUGP(DL_WARN,("%s: The packet is an invalid packet!\n",__FUNCTION__));
    return FALSE;
  }

  NdisZeroMemory(pBuf,lTotalLength);
  *BufLen = (UINT32)lTotalLength;

  pCurrentNB = NET_BUFFER_LIST_FIRST_NB(pCurrentNBL);

  while (pCurrentNB)
  {
    pMdl = NET_BUFFER_CURRENT_MDL(pCurrentNB);
    lMdlOffset = NET_BUFFER_CURRENT_MDL_OFFSET(pCurrentNB);

    while(pMdl)
    {

      NdisQueryMdl( pMdl,(PVOID *)&pData,&lDataBufferLen,NormalPagePriority );

      //NBL->DataLength <= MDL1->ByteCount+ MDL2->ByteCount+........+ MDLx->ByteCount  -  NBL->CurrentMDLOffset.
      //这里 Data length 不等于 Buffer Length
      lBytesToCopy =min( lDataBufferLen - lMdlOffset, lTotalLength);

      NdisMoveMemory(
        (pBuf+ lOffset), 
        pData + lMdlOffset, 
        lBytesToCopy 
        );
      lTotalLength -= lBytesToCopy;
      lOffset += lBytesToCopy;

      //一般来说,只有第一个MDL的buffer才有偏移,其余的都没有。
      //如果offset大于了buflen,那么第一个MDL就没有存在的意义了。

      lMdlOffset = 0;

      NdisGetNextMdl(pMdl, &pMdl); //pMdl = pMdl->Next;
    } 

    pCurrentNB = NET_BUFFER_NEXT_NB(pCurrentNB);

  }//end while(pCurrentNB)    

  //当while循环结束以后,pBuf保存的是:一整个NET_BUFFER_LIST里面的数据内容。

  
  ExFreePoolWithTag(pBuf, 'tag1');//不需要pBuf了,这里把它释放掉


  return TRUE;
}

//////////////////////////////////////////////////////////

第二种方法:



/*******************************************************************
 GetNetBufferData函数的功能:
 从1个NET_BUFFER里面获取数据。1个NET_BUFFER里面含有1个或者多个的MDL
 *******************************************************************/
VOID GetNetBufferData(
  PNET_BUFFER    NetBuffer,
  PUCHAR      OutputBuffer,
  ULONG      OutputBufferSize,
  PULONG      OutputBytesCopied
  )

{

  PMDL  Mdl = NetBuffer->CurrentMdl;
  *OutputBytesCopied = 0;

  if (NetBuffer->DataLength > OutputBufferSize)
  {
#if DBG
    DbgPrint("Not enough output buffer space, in: %d, out : %d\n", 
      NetBuffer->DataLength, 
      OutputBufferSize);

#endif
    return;
  }


  NdisMoveMemory(OutputBuffer,
    (PUCHAR)MmGetSystemAddressForMdlSafe(Mdl, LowPagePriority) + NetBuffer->CurrentMdlOffset,
    Mdl->ByteCount - NetBuffer->CurrentMdlOffset);

  
  OutputBuffer += Mdl->ByteCount - NetBuffer->CurrentMdlOffset;
  *OutputBytesCopied += Mdl->ByteCount - NetBuffer->CurrentMdlOffset;


  //
  //循环 MDL链表,获取每一个结点的数据,数据被保存到 OutputBuffer里面
  //OutputBuffer的空间不断地扩大。

  //当链表不为空, 并且 OutputBuffer的长度 < 1个NET_BUFFER的总长度

  while ( ((Mdl = Mdl->Next)!=NULL) && (*OutputBytesCopied < NetBuffer->DataLength) )
  {
    NdisMoveMemory(OutputBuffer,
      MmGetSystemAddressForMdlSafe(Mdl, LowPagePriority),
      Mdl->ByteCount);

    OutputBuffer += Mdl->ByteCount;          //数据被保存到 OutputBuffer里面
    *OutputBytesCopied += Mdl->ByteCount;    //OutputBuffer的空间不断地扩大
  }

  if (Mdl != NULL)
  {
    NdisMoveMemory(OutputBuffer,
      MmGetSystemAddressForMdlSafe(Mdl, LowPagePriority),
      NetBuffer->DataLength);

    OutputBuffer += Mdl->ByteCount;
    *OutputBytesCopied += Mdl->ByteCount;
  }


#if DBG
  DbgPrint("buffer copied: %d bytes\n", *OutputBytesCopied);
#endif

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

上面的2种方法,只是获取数据。


而原本NDIS Filter框架提供的这4个函数默认的代码,都没有做任何操作,没有实质性质的功能,
这4个函数原本默认的代码,我们都不需要, 因为我们的驱动程序需要处理网络数据包。

这4个函数分别是:

FilterSendNetBufferListsComplete
FilterSendNetBufferLists
FilterReturnNetBufferLists
FilterReceiveNetBufferLists


在我们的NDIS Filter工程里面,我们需要修改掉这4个函数的功能,把它们替换成我们所想要的功能。

比如,你在发送数据包或者接收数据包的时候,需要实现自己的一些业务逻辑,而不是去使用Microsoft NDIS Filter提供的原始代码的功能。


这4个函数的功能被替换如下:

//////////////////////////////开始把4个函数的功能替换成我们自己的代码///////////////////////////////////



/**************************************************************
  FilterSendNetBufferListsComplete函数的功能:
   NDIS调用 FilterSendNetBufferListsComplete 把发送的结构和数据返还给 Filter Driver。NDIS可以收集多次NdisFSendNetBufferLists发送的结构和数据形成一个单链表传递给FilterSendNetBufferListsComplete。除非到NDIS调用FilterSendNetBufferListsComplete,否则一个发送请求的当前状态总是未知的。

   一个过滤驱动是不能在NDIS调用FilterSendNetBufferListsComplete返回结构之前对NET_BUFFER_LIST和其关联的数据做检查的。FilterSendNetBufferListsComplete要完成一个发送请求完成后的任何必要的后继处理。当NDIS调用FilterSendNetBufferListsComplete时,Filter Driver就重新获地对结构及结构相关资源的所有权。可以在 FilterSendNetBufferListsComplete中释放相关的资源和准备下一个NdisFSendNetBufferLists调用。 
  
   NDIS总是按照过滤驱动调用NdisFSendNetBufferLists提交的顺序传递给下层驱动,但是回返FilterSendNetBufferListsComplete 的顺序则是任意的。Filter Driver可以请求一个回环发送请求,只要把NdisFSendNetBufferLists的SendFlags设置成NDIS_SEND_FLAGS_CHECK_FOR_LOOPBACK就行了。NDIS会引发一个包含发送数据的接收包指示。
   
    
   一个Filter Driver应该对自己引发的发送请求保持跟踪并确保在完成时不调用NdisFSendNetBufferComplete例程。 
  **************************************************************/

_Use_decl_annotations_
VOID
FilterSendNetBufferListsComplete(
  NDIS_HANDLE         FilterModuleContext,
  PNET_BUFFER_LIST    NetBufferLists,
  ULONG               SendCompleteFlags
  )

{
    PMS_FILTER         pFilter = (PMS_FILTER)FilterModuleContext;

#if DBG
    DbgPrint(">>> FilterSendNetBufferListsComplete: %p\n\n", NetBufferLists);
#endif

    NdisFSendNetBufferListsComplete(pFilter->FilterHandle, NetBufferLists, SendCompleteFlags);

}


/*************************************************************
 FilterSendNetBufferLists函数的功能:
   NDIS调用一个Filter Driver的FilterSendNetBufferLists例程来过滤上层驱动的发送请求。Filter Driver不能改变其它驱动传来的NET_BUFFER_LIST结构中的SourceHandle成员的值。它可以过滤数据并发送过滤的数据到下层驱动。
   对每一个提交到FilterSendNetBufferLists的NDIS_BUFFER_LIST,我们可做下面的操作。
    
   1)可以把缓冲区通过 NdisFSendBufferLists 传递给下层驱动,NDIS 保证上下文空间对FilterDriver的有效性。过滤驱动可以在发送前修改缓冲区的内容。可以像处理自己引发的发送请求的缓冲区一样处理这个缓冲区。   
   2)可以调用 NdisFSendNetBufferListsComplete 拒绝传递这个包 
   3)排队缓冲区内容到本地的供以后处理。例如要在一定超时后处理或要接收到特定包后才处理等。如果支持这种处理方式就要支持取消请求的操作。     
   4)可以拷贝缓冲区并引发一个发送请求。它类似自己引发一个发送请求,但必须先调用 NdisFSendNetBufferComplete返回上层驱动的缓冲区。
       
   发送请求在驱动栈继续完成,当一个微端口驱动调用NdisMSendNetBufferListsComplete完成一个发送请求时,NDIS会调用微端口
   驱动之上最近的Filter Driver的FilterSendNetBufferLists例程。
   
   在一个发送操作完成后,Filter Driver可以做在FilterSendNetBufferLists中所有修改的相反操作。FilterSendNetBufferListsComplete返回一个NET_BUFFER_LIST结构的单链表和发送请求的最终状态给上层的驱动。当最顶层的 Filter Module的FilterSendNetBufferListsComplete被调用完成后NDIS会调用引发发送请求的协议驱动的ProtocolSendNetBufferListsComplete。如果Filter Driver不提供FilterSendNetBufferLists它还是可以引发一个发送操作的,但它必须提供一个FilterSendNetBufferListsComplete并且不能在这个例程里把这个事件传递给上层驱动。

   一个Filter Driver可以传递或过滤一个上层驱动的回环请求,要传递一个回环请求,NDIS会设置FilterSendNetBufferLists的SendFlags参数为NDIS_SEND_FLAGS_CHECK_FOR_LOOPBACK,Filter Driver在调用NdisFSendNetBufferLists时把这个标记传给它即可。在回环请求的情况下NDIS会指示一个包含发送数据的接收包。

   通常情况下,如果一个Filter Driver修改的任何行为不是NDIS提供的标准服务,那么它应该当自己为NDIS提供相应的服务。例如,如果一个Filter Driver修改了一个硬件地址请求,就必须处理直接到这个新地址回环包。在这种情况下, 因为Filter Driver已经更改了地址NDIS是不能提供一个回环服务的。
   还有就是如果Filter Driver设置了混杂模式那它就不能传递额外的数据给上层接收。 
 **************************************************************/
_Use_decl_annotations_
VOID
FilterSendNetBufferLists(
  NDIS_HANDLE         FilterModuleContext,
  PNET_BUFFER_LIST    NetBufferLists,
  NDIS_PORT_NUMBER    PortNumber,
  ULONG               SendFlags
  )

{
  PMS_FILTER          pFilter = (PMS_FILTER)FilterModuleContext;
  PNET_BUFFER      NetBuffer;
  UCHAR        TempBuffer[MAX_BUFFER_SIZE];
  ULONG        BytesCopied;
  pEthHdr        EthernetHeader;
  pIPHdr        IpHeader;

#if DBG
  DbgPrint(">>> FilterSendNetBufferLists: %p\n", NetBufferLists);
#endif


  for (NetBuffer = NetBufferLists->FirstNetBuffer;
    NetBuffer!= NULL;
    NetBuffer = NetBuffer->Next)

  {
    GetNetBufferData(NetBuffer, TempBuffer, MAX_BUFFER_SIZE, &BytesCopied);
    if (BytesCopied == 0)
    {
#if DBG
      DbgPrint("Net buffer catch error\n");
#endif
    }
    else
    {
      EthernetHeader = TempBuffer;
      if (EthernetHeader->Type == 0x0800)//以太网类型
      {
        IpHeader = EthernetHeader + sizeof(EthHdr); //去掉以太网头,抓出IP头
#if DBG
        DbgPrint("Get ip packet\n");
#endif
      }
    }

  }

  NdisFSendNetBufferLists(pFilter->FilterHandle, NetBufferLists, PortNumber, SendFlags);
}



/*************************************************************
  FilterReturnNetBufferLists函数的功能:
   如果Filter Driver设置了NdisFIndicateReceiveNetBufferLists的状态为NDIS_STATUS_SUCCESS, NDIS通过驱动的FilterReturnNetBufferLists
   返回指示数据。在这种情况下 Filter Driver失去了对NET_BUFFER_LIST的所有权,直到FilterReturnNetBufferLists被调用。

   Filter Driver调用NdisFIndicateNetBufferLists 传递接收指示给驱动栈上的上层驱动,如果上层驱动保留了对缓冲区(NET_BUFFER_LIST)的所有权,NDIS会调用Filter Driver的FilterReturnNetBufferLists 例程。
   
   在FilterReturnNetBufferLists中应该撤消在接收路径上(如在 FilterReciveNetBufferLists中做的一些处理)的操作。当最底层的Filter Module完成对缓冲区(NET_BUFFER_LIST)的处理后,NDIS把缓冲区返回给微端口驱动。如果FilterReceiveNetBufferLists的ReceiveFlags没有设置NDIS_RECEIVE_FLAGS_RESOURCES标记, FilterDriver调用NdisFReturnNetBufferList返回这个缓冲区数据,如果设置了FilterReceiveNetBufferLists直接返回时就把缓冲区返还给了下层微端口驱动。  
  ***************************************************************/
_Use_decl_annotations_
VOID
FilterReturnNetBufferLists(
  NDIS_HANDLE         FilterModuleContext,
  PNET_BUFFER_LIST    NetBufferLists,
  ULONG               ReturnFlags
  )

{
  PMS_FILTER          pFilter = (PMS_FILTER)FilterModuleContext;
#if DBG
  DbgPrint(">>> FilterReturnNetBufferLists: %p\n\n", NetBufferLists);
#endif
  NdisFReturnNetBufferLists(pFilter->FilterHandle, NetBufferLists, ReturnFlags);

}



/***************************************************************
  FilterReceiveNetBufferLists函数的功能:
   Filter Driver调用 NdisFIndicateReceiveNetBufferLists来指示发送数据。这个函数通过NET_BUFFER_LIST结构给上层驱动指示数据。Filter Driver可以从池中分配这个结构。如果Filter Driver设置了NdisFIndicateReceiveNetBufferLists的状态为 NDIS_STATUS_SUCCESS, NDIS通过驱动的FilterReturnNetBufferLists返回指示数据。在这种情况下Filter Driver失去了对NET_BUFFER_LIST的所有权直到FilterReturnNetBufferLists被调用。如果Filter Driver在调用NdisFIndicateReceiveNetBufferLists时设置ReceiveFlags为NDIS_RECEIVE_FLAGS_RESOURCES,在函数返回后Filter Driver会立即恢复对NET_BUFFER_LIST的所有权,这时Filter Driver必须立即处理这个NET_BUFFER_LIST的返回,因为NDIS在这种情况下是不会调用FilterReturnNetBufferLists返回NET_BUFFER_LIST结构的。 

   注意: 一个Filter Driver应该跟踪自己引发的接收指示确保它在FilterReturnNetBufferLists
   中不调用NdisFReturnNetBufferLists。 
  ***************************************************************/
_Use_decl_annotations_
VOID
FilterReceiveNetBufferLists(
  NDIS_HANDLE         FilterModuleContext,
  PNET_BUFFER_LIST    NetBufferLists,
  NDIS_PORT_NUMBER    PortNumber,
  ULONG               NumberOfNetBufferLists,
  ULONG               ReceiveFlags
  )

{
  PMS_FILTER          pFilter = (PMS_FILTER)FilterModuleContext;
  PNET_BUFFER          NetBuffer;
  UCHAR            TempBuffer[MAX_BUFFER_SIZE];
  ULONG            BytesCopied;
  pEthHdr            EthernetHeader;
  pIPHdr            IpHeader;

#if DBG
  DbgPrint(">>> FilterReceiveNetBufferLists: %p\n", NetBufferLists, ReceiveFlags, NumberOfNetBufferLists);
#endif

  do
  {

    for (NetBuffer = NetBufferLists->FirstNetBuffer;
      NetBuffer != NULL;
      NetBuffer = NetBuffer->Next)

    {
      GetNetBufferData(NetBuffer, TempBuffer, MAX_BUFFER_SIZE, &BytesCopied);
      if (BytesCopied == 0)
      {
#if DBG
        DbgPrint("Net buffer catch error\n");
#endif
      }
      else
      {
        EthernetHeader = TempBuffer;
        if (EthernetHeader->Type == 0x0800)
        {
          IpHeader = EthernetHeader + sizeof(EthHdr);
#if DBG
          DbgPrint("IP packet exist\n");
#endif
        }
      }

    }

  } while (FALSE);


/************************************************************
 调用 NdisFIndicateReceiveNetBufferLists来指示发送数据。

 如果Filter Driver设置了NdisFIndicateReceiveNetBufferLists的状态为NDIS_STATUS_SUCCESS, NDIS通过驱动的FilterReturnNetBufferLists 返回指示数据。
     
 如果Filter Driver设置了NdisFIndicateReceiveNetBufferLists的ReceiveFlags值为NDIS_RECEIVE_FLAGS_RESOURCES,
 那么在函数返回后Filter Driver会立即恢复对NET_BUFFER_LIST的所有权,这时Filter Driver必须立即处理这个NET_BUFFER_LIST的返回。
 在这种情况下是不会调用FilterReturnNetBufferLists返回NET_BUFFER_LIST结构的。
 ************************************************************/

  NdisFIndicateReceiveNetBufferLists(
    pFilter->FilterHandle,
    NetBufferLists,
    PortNumber,
    NumberOfNetBufferLists,
    ReceiveFlags);

}

////////////////////////////////结束原本4个函数的功能替换//////////////////////////////////////////


这里是完整的工程代码, 希望能够给你带来好运。

你可能感兴趣的:(如何把NDIS Filter框架利用到日常的Windows驱动开发工作中)