windows 驱动实例分析系列: NDIS 6.0的Filter 驱动改造(二)

缓冲区池

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;
}

接收之后就是原始的数据缓冲区了。

相关推荐
会写代码的孙悟空1 小时前
windows下解决端口被占用,但是找不到占用端口的应用程序;以一种访问权限不允许的方式做了一个访问套接字的尝试;搜索可用端口
运维·网络·windows
喜欢打篮球的普通人3 小时前
2024 Rust现代实用教程:1.3获取rust的库国内源以及windows下的操作
开发语言·windows·rust
sukalot4 小时前
windows 驱动实例分析系列: NDIS 6.0的Filter 驱动改造(四)
windows·单片机·嵌入式硬件
微雨盈萍cbb6 小时前
Windows 上更新OpenSSL 到 1.1.1
windows
askah66446 小时前
无法启动此程序win10玩游戏找不到d3dx9_43.dll缺失的五种常用有效解决方法
windows·游戏·电脑·dll丢失·1024程序员节
admin_23316 小时前
rsync部署 附报错解决、配置详解及Windows脚本
windows
cpp_learners18 小时前
Windows环境 ffmpeg 命令使用介绍
windows·ffmpeg·ffmpeg命令
lqqjuly1 天前
Windows 11在conda虚拟环境中安装cuda、cudann
pytorch·windows·python·算法·conda