缓冲区池
Filter驱动要发送数据,除了实现这两个回调之外,还需要分配一个NET_BUFFER_LIST池,用于从池中分配NET_BUFFER_LIST结构,注意内核代码必须仅从Pool中分配NET_BUFFER_LIST以及NET_BUFFER等缓冲区结构。
分配
一般会使用下面的代码来分配缓冲区池:
NDIS_HANDLE AllocateListPool(NDIS_HANDLE NdisHandle)
{NET_BUFFER_LIST_POOL_PARAMETERS PoolParameters;NdisZeroMemory(&PoolParameters, sizeof(NET_BUFFER_LIST_POOL_PARAMETERS));PoolParameters.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;PoolParameters.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;PoolParameters.Header.Size = sizeof(PoolParameters);PoolParameters.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT ;PoolParameters.ContextSize = sizeof(FILTER_SEND_NETBUFLIST_RSVD);PoolParameters.fAllocateNetBuffer = TRUE;PoolParameters.PoolTag = FILTER_ALLOC_TAG;return NdisAllocateNetBufferListPool(NdisHandle, &PoolParameters);
}
后续可以用这个句柄分配一个NET_BUFFER_LIST结构,并且包含一个NET_BUFFER,这样的话就避免了调用NdisAllocateNetBufferPool分配NET_BUFFER的麻烦,但是NET_BUFFER_LIST可以挂接多个NET_BUFFER,这也是NdisAllocateNetBufferPool存在的意义。
释放
可以调用下面的代码释放分配的Pool:
// 释放NET_BUFFER_LIST池
void ReleasePool(NDIS_HANDLE pNdisPoolHandle)
{if(NULL != pNdisPoolHandle){NdisFreeNetBufferListPool(pNdisPoolHandle);pNdisPoolHandle = NULL;}
}
在释放 NET_BUFFER_LIST 结构池之前,必须释放池中的所有NET_BUFFER_LIST结构,注意:释放结构池不等于释放池中的所有结构。
数据包的接收
相比起发送,接收要简单很多,有两个例程和接受相关,分别是FilterReceiveNetBufferLists以及FilterReturnNetBufferLists函数,前者用于接受数据包,后者则是将数据包返回基础驱动程序。
如果 NDIS 传递给FilterReturnNetBufferLists中的 ReceiveFlags 中NDIS_RECEIVE_FLAGS_RESOURCES标志 指示的FilterReceiveNetBufferLists 函数未设置,Filter 驱动程序必须调用 NdisFReturnNetBufferLists 函数以返回 NET_BUFFER_LIST 结构和关联的数据。 在 Filter 驱动程序调用 NdisFReturnNetBufferLists 后,NDIS 将数据返回到基础驱动程序。:
VOID
FilterReceiveNetBufferLists(NDIS_HANDLE FilterModuleContext,PNET_BUFFER_LIST NetBufferLists,NDIS_PORT_NUMBER PortNumber,ULONG NumberOfNetBufferLists,ULONG ReceiveFlags)
/*++Routine Description:FilerEReceiveNetBufferLists是Filter驱动程序的可选函数。如果提供,此功能处理接收底层发出的指示NIC或更低级别的Filter驱动程序。此函数也可以处理环回的结果。如果此处理程序为NULL,NDIS将跳过调用此处理程序在处理接收指示时的处理,并将调用在堆栈中下一个更高的指示驱动。一个不提供FilterReceiveNetBufferLists处理程序无法提供FilterReturnNetBufferLists处理程序,无法启动原始接收指示本身。Arguments:FilterModuleContext - 上下文.NetBufferLists - 数据包PortNumber - 接收端口ReceiveFlags -N.B.: 检查 NDIS_TEST_RECEIVE_CANNOT_PEND 中的 ReceiveFlags 非常重要。这控制接收指示是同步还是异步函数调用。--*/
{PMS_FILTER pFilter = (PMS_FILTER)FilterModuleContext;BOOLEAN DispatchLevel;ULONG Ref;BOOLEAN bFalse = FALSE;
#if DBGULONG ReturnFlags;
#endifDEBUGP(DL_TRACE, "===>ReceiveNetBufferList: NetBufferLists = %p.\n", NetBufferLists);do{DispatchLevel = NDIS_TEST_RECEIVE_AT_DISPATCH_LEVEL(ReceiveFlags);
#if DBGFILTER_ACQUIRE_LOCK(&pFilter->Lock, DispatchLevel);if (pFilter->State != FilterRunning){FILTER_RELEASE_LOCK(&pFilter->Lock, DispatchLevel);if (NDIS_TEST_RECEIVE_CAN_PEND(ReceiveFlags)){ReturnFlags = 0;if (NDIS_TEST_RECEIVE_AT_DISPATCH_LEVEL(ReceiveFlags)){NDIS_SET_RETURN_FLAG(ReturnFlags, NDIS_RETURN_FLAGS_DISPATCH_LEVEL);}NdisFReturnNetBufferLists(pFilter->FilterHandle, NetBufferLists, ReturnFlags);}break;}FILTER_RELEASE_LOCK(&pFilter->Lock, DispatchLevel);
#endifASSERT(NumberOfNetBufferLists >= 1);//// 如果您想丢弃接收到的数据包,则必须小心地// 修改 NBL 链,如下所示://// if NDIS_TEST_RECEIVE_CANNOT_PEND(ReceiveFlags):// 对于每个未丢弃的 NBL,暂时将其从// 链接列表中断开,并使用// NdisFIndicateReceiveNetBufferLists 和// NDIS_RECEIVE_FLAGS_RESOURCES 标志集单独指示它。// 然后立即// 将 NBL 重新链接到链中。当所有 NBL 都已// 指示时,您可以从此函数返回。// // 否则(NDIS_TEST_RECEIVE_CANNOT_PEND 为 FALSE):// 将 NBL 的链接列表分为两个链:// 一个链包含要丢弃的数据包,另一个链中包含其他所有内容。// 使用 NdisFReturnNetBufferLists 返回第一个链,并使用 NdisFIndicateReceiveNetBufferLists 指示其余链。//// 注意:在以太网数据包的接收路径上,一个 NBL 将只有一个 NB。因此(假设您正在以太网上接收,或者连接到 Native WiFi 上方)您不必担心丢弃一个 NB,而是尝试指示同一 NBL 上的其余 NB。// 换句话说,如果第一个 NB 应该被丢弃,则丢弃整个 NBL。// 如果您想修改数据包,并且可以快速完成,您可以在此处进行修改。但是,请确保您保存了足够的信息以撤消 FilterReturnNetBufferLists 处理程序中的修改。// 如有必要,将 NetBufferLists 排队在本地结构中以供稍后处理。但是,不要将它们排队“太久”,否则系统的性能可能会下降。如果您需要无限期地保留 NBL,则分配内存,执行深度复制,然后返回原始 NBL。if (pFilter->TrackReceives){FILTER_ACQUIRE_LOCK(&pFilter->Lock, DispatchLevel);pFilter->OutstandingRcvs += NumberOfNetBufferLists;Ref = pFilter->OutstandingRcvs;FILTER_LOG_RCV_REF(1, pFilter, NetBufferLists, Ref);FILTER_RELEASE_LOCK(&pFilter->Lock, DispatchLevel);}
#ifdef FILTER_TESTCopyNetBufferList(pFilter, NetBufferLists);
#endifNdisFIndicateReceiveNetBufferLists(pFilter->FilterHandle,NetBufferLists,PortNumber,NumberOfNetBufferLists,ReceiveFlags);if (NDIS_TEST_RECEIVE_CANNOT_PEND(ReceiveFlags) &&pFilter->TrackReceives){FILTER_ACQUIRE_LOCK(&pFilter->Lock, DispatchLevel);pFilter->OutstandingRcvs -= NumberOfNetBufferLists;Ref = pFilter->OutstandingRcvs;FILTER_LOG_RCV_REF(2, pFilter, NetBufferLists, Ref);FILTER_RELEASE_LOCK(&pFilter->Lock, DispatchLevel);}} while (bFalse);DEBUGP(DL_TRACE, "<===ReceiveNetBufferList: Flags = %8x.\n", ReceiveFlags);}
数据包的访问
下面的代码用于将数据取出来,代码中并未处理缓冲区不足的情况,这只是一个案例演示:
NDIS_STATUS
CopyNetBufferList(
IN PMS_FILTER pFilter,
IN PNET_BUFFER_LIST NetBufferLists
)
{PUCHAR pData = NULL;PNET_BUFFER_LIST pCurrNbl = NetBufferLists;PNET_BUFFER pCurrBuff = NULL;PNET_BUFFER pNextBuff = NULL;PMDL pMdl = NULL;ULONG ulOffset = 0;int nDataLen = 0;BOOLEAN bFlags = !(KeGetCurrentIrql() == PASSIVE_LEVEL);// 1. 循环处理NET_BUFFER_LISTwhile (pCurrNbl){// 2. 获取一个NET_BUFFERpCurrBuff = NET_BUFFER_LIST_FIRST_NB(pCurrNbl);while (pCurrBuff){// 2.1 获取偏移量ulOffset = NET_BUFFER_DATA_OFFSET(pCurrBuff);// 2.2. 获取NMLpMdl = NET_BUFFER_FIRST_MDL(pCurrBuff);// 2.3. 获取长度nDataLen = NET_BUFFER_DATA_LENGTH(pCurrBuff);if (pMdl == NULL || nDataLen == 0) continue;// 从MDL中获取虚拟地址pData = (UCHAR*)MmGetSystemAddressForMdlSafe(pMdl, NormalPagePriority);pNextBuff = NET_BUFFER_NEXT_NB(pCurrBuff);if (NULL == pNextBuff){// 获取真正的数据地址 pDatapData = pData + ulOffset;nDataLen = nDataLen - ulOffset;// 避免越界nDataLen = nDataLen > _DATA_SIZE ? _DATA_SIZE : nDataLen;// 由于上层随时可以读取数据,故需要增加自旋锁FILTER_ACQUIRE_LOCK(&pFilter->DataLock, bFlags);NdisZeroMemory(pFilter->Data, _DATA_SIZE);NdisMoveMemory(pFilter->Data, pData, nDataLen);FILTER_RELEASE_LOCK(&pFilter->DataLock, bFlags);}pCurrBuff = pNextBuff;}pCurrNbl = NET_BUFFER_LIST_NEXT_NBL(pCurrNbl);}return 0;
}
接收之后就是原始的数据缓冲区了。