缓冲区池
Filter驱动要发送数据,除了实现这两个回调之外,还需要分配一个NET_BUFFER_LIST池,用于从池中分配NET_BUFFER_LIST结构,注意内核代码必须仅从Pool中分配NET_BUFFER_LIST以及NET_BUFFER等缓冲区结构。
分配
一般会使用下面的代码来分配缓冲区池:
cpp
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:
cpp
// 释放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 将数据返回到基础驱动程序。:
cpp
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 DBG
ULONG ReturnFlags;
#endif
DEBUGP(DL_TRACE, "===>ReceiveNetBufferList: NetBufferLists = %p.\n", NetBufferLists);
do
{
DispatchLevel = NDIS_TEST_RECEIVE_AT_DISPATCH_LEVEL(ReceiveFlags);
#if DBG
FILTER_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);
#endif
ASSERT(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_TEST
CopyNetBufferList(pFilter, NetBufferLists);
#endif
NdisFIndicateReceiveNetBufferLists(
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);
}
数据包的访问
下面的代码用于将数据取出来,代码中并未处理缓冲区不足的情况,这只是一个案例演示:
cpp
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_LIST
while (pCurrNbl)
{
// 2. 获取一个NET_BUFFER
pCurrBuff = NET_BUFFER_LIST_FIRST_NB(pCurrNbl);
while (pCurrBuff)
{
// 2.1 获取偏移量
ulOffset = NET_BUFFER_DATA_OFFSET(pCurrBuff);
// 2.2. 获取NML
pMdl = 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)
{
// 获取真正的数据地址 pData
pData = 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;
}
接收之后就是原始的数据缓冲区了。