以前,我在看雪发表了一篇文章,文章如下。
【原创】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链表组成,而每一个NET_BUFFER结点是由1个MDL链表组成。但网络数据包含了 多个NET_BUFFER_LIST
网络数据结构如下图所示:
你可以按照我的下述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环境下配置。图如下:
你把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个函数的功能替换//////////////////////////////////////////
这里是完整的工程代码, 希望能够给你带来好运。
NdisLwfDemo.rar
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课