NDIS 6过滤驱动克隆Net Buffer List后运行异常缓慢问题排查
问题背景
开发NDIS 6过滤驱动用于拦截编辑Windows本机网络数据包,仅实现FilterSendNetBufferLists、FilterSendNetBufferListsComplete、FilterReceiveNetBufferLists、FilterReturnNetBufferLists四个核心函数处理DNS数据包,其余NDIS函数设为NULL或保留模板。
发送侧流程正常:克隆NetBufferLists(NdisAllocateCloneNetBufferList)→ 修改后下发(NdisFSendNetBufferLists)→ 完成回调中释放克隆包(NdisFreeCloneNetBufferList),无异常。
接收侧存在两个核心问题:
- 驱动运行后网络极度缓慢(可ping通但浏览器无法加载页面),仅修改特定DNS包,排除修改逻辑影响
- 尝试释放接收侧克隆包触发系统崩溃,Windbg报错:
An attempt was made to access a pageable (or completely invalid) address at an interrupt request level (IRQL) that is too high.
栈信息末尾包含:ndis!ndisMSendCompleteNetBufferListsToOpen+0x6c ndis!ndisMSendCompleteNetBufferListsInternal+0x31e ndis!NdisMSendNetBufferListsComplete+0x24b
尝试释放/返回原始NetBufferLists会触发双重释放崩溃,已知接收侧内存应由NDIS回收,但无法定位卡顿原因。
一、接收侧核心逻辑修正(解决网络卡顿)
网络卡顿的根源是原始NetBufferList未被正确归还,导致下层驱动缓冲区池耗尽,修正流程如下:
- 克隆原始包后,立即调用
NdisFReturnNetBufferLists将原始包返回给NDIS,禁止持有原始包占用缓冲区 - 克隆包属于驱动自行分配的资源,需由驱动主动释放,不能通过
NdisFReturnNetBufferLists返回给NDIS
正确的接收侧流程:
- 在
FilterReceiveNetBufferLists中,接收原始NetBufferList链 - 调用
NdisAllocateCloneNetBufferList克隆目标DNS包(仅克隆需要修改的包,避免无意义资源消耗) - 修改克隆包中的DNS数据
- 调用
NdisFIndicateReceiveNetBufferLists将克隆包上传至上层协议栈 - 立即调用
NdisFReturnNetBufferLists将原始NetBufferList返回给下层驱动 - 在
FilterReturnNetBufferLists中,接收上层返回的克隆包,将其放入工作线程队列,后续在PASSIVE_LEVEL下调用NdisFreeCloneNetBufferList释放
二、内存释放的IRQL合规性(解决系统崩溃)
Windbg的IRQL过高报错,说明你在高于DISPATCH_LEVEL的IRQL下执行了分页内存操作,修正要点:
NdisFreeCloneNetBufferList必须在IRQL ≤ DISPATCH_LEVEL下调用,而FilterReturnNetBufferLists可能在DPC_LEVEL触发(高于DISPATCH_LEVEL)- 解决方法:
- 在
FilterReturnNetBufferLists中,将需要释放的克隆包添加到驱动的工作队列,使用IoQueueWorkItem调度到工作线程执行释放操作 - 克隆包时,如果需要在高IRQL下操作,确保
NdisAllocateCloneNetBufferList的PoolType参数指定为NonPagedPoolNx,避免分页内存访问问题
- 在
三、针对性排查步骤
- 缓冲区池耗尽验证:使用Windbg命令
!ndis nb查看NetBufferList的分配、释放、未归还统计,确认是否有大量原始包未被归还 - 处理延迟排查:检查
FilterReceiveNetBufferLists中是否存在耗时操作(如未优化的DNS包解析),将复杂逻辑异步到工作线程,避免阻塞接收路径 - 标志位检查:调用
NdisFIndicateReceiveNetBufferLists时,设置正确的标志位(如NDIS_RECEIVE_FLAGS_RESOURCES),让NDIS知道驱动已放弃原始包的所有权,减少额外处理开销
四、NDIS接收侧内存管理核心规则
- 原始包所有权:接收侧的原始NetBufferList属于下层驱动,过滤驱动必须在处理完成后立即返回,禁止长期持有
- 克隆包所有权:通过
NdisAllocateCloneNetBufferList创建的克隆包,所有权属于过滤驱动,必须由驱动主动调用NdisFreeCloneNetBufferList释放,不能返回给NDIS - IRQL约束:
NdisAllocateCloneNetBufferList:可在IRQL ≤ DISPATCH_LEVEL调用(取决于PoolType参数)NdisFreeCloneNetBufferList:必须在IRQL ≤ DISPATCH_LEVEL调用,高IRQL下需异步到工作线程执行
内容的提问来源于stack exchange,提问作者eng0130

