多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Windows NDIS协议驱动开发:从绑定外网接口到收发数据包

Windows NDIS协议驱动开发:从绑定外网接口到收发数据包 简介面向Windows底层网络驱动开发者的实践型资源包聚焦NDIS网络驱动接口标准与协议驱动的完整开发流程涵盖TCP/IP等协议数据的封装解析、外网接口驱动拨号、无线及虚拟适配器的接入方式适合已有C/C基础、希望入门WDM/WDK驱动编程或从事网卡/协议栈底层工作的开发人员。包内共31个文件体积仅69KB包含协议驱动的C源码ndisprot.cpp、send.cpp、recv.cpp及对应.h、编译后的.sys驱动模块、Visual C工程文件.dsp/.dsw、INF安装描述脚本以及示例应用DriverDemo、ProcApp这些文件各司其职源码用于理解驱动逻辑sys文件可直接加载调试工程文件便于二次编译INF辅助驱动安装应用工程演示了驱动与应用层通信的完整链路。资源还附有第8章教学目录和www.pudn.com.txt文档详细说明驱动初始化、I/O请求处理、中断处理、NDIS回调函数注册与调度等主题学习者可对照代码边读边练快速搭建协议驱动框架。当前已有77人学习下载对正在做网络驱动课程设计、毕业设计或产品驱动评估的技术人员是一份精炼且可直接参考的代码级资料。1. windows网络驱动接口标准到底管什么一个协议驱动的自白做网络功能开发的人迟早会撞上“windows网络驱动接口标准”这个绕不开的名词——它就是 NDISNetwork Driver Interface Specification微软为 Windows 网络驱动划出的一道统一边界。你手头的 .rar 里大概率是一份协议驱动Protocol Driver源码或文档包而这类驱动最常见的诉求是让 Windows 能把原始以太网帧、IP 报文或者某个自定义协议的数据从网卡直接送到你的代码里而不是经过系统自带的 TCP/IP 协议栈。这篇文章就讲清楚 NDIS 协议驱动怎么从零写起来、绑到外网接口上、收发数据以及那些文档里不会写但跑起来必踩的坑。适合要做网络监控、隧道、报文采集、虚拟网卡配套驱动的开发者纯应用层调 socket 的工程师可以先收藏等需要绕过协议栈看裸包时再回来看。2. 协议驱动在 NDIS 里的位置数据怎么从网卡走到你的代码2.1 NDIS 的三种驱动角色协议驱动到底算哪一种NDIS 把网络驱动分成三类网卡驱动Miniport Driver管硬件过滤驱动Filter Driver在中间拦数据协议驱动Protocol Driver则站在最上层直接对接系统里的协议栈位置。很多人第一次看会绕晕——协议驱动不是去实现 TCP/IP而是把自己注册成一个协议的消费者网卡收到数据后NDIS 把报文交给协议驱动的接收回调协议驱动要发包也通过 NDIS 把报文交给网卡驱动发出去。这里有个关键认知协议驱动和 TCP/IP 协议栈是平级的关系。你写一个协议驱动Windows 会把你也当成一个协议栈来对待。比如 TCP/IP 栈绑定在网卡上你的协议驱动也可以同时绑定在同一块网卡上同一个包会复制一份给你一份给 TCP/IP 栈。这就是很多抓包工具、NDIS 隧道能实现的基础——你不需要修改系统协议栈只需要旁听或者插一脚。那和外网接口驱动有什么关系标题里的外网接口驱动在 Windows 语境下通常指真实物理网卡的接口区别于 loopback 之类的虚拟接口。协议驱动要工作必须绑定到一个已启动的网卡接口上。NDIS 里这个过程叫 Binding绑定协议驱动向 NDIS 报告我关心什么类型的接口NDIS 负责把匹配的网卡和你的协议驱动牵上线。绑定关系可以在注册表里查也可以动态断开和重连。2.2 一个数据包的完整旅程从网线到你的 ReceiveHandler我把一次接收过程拆成四步方便你理解后面代码的每个回调对应哪里。第一步网卡收到物理信号Miniport 驱动把它转成 NDIS 的 NET_BUFFER_LIST 结构简称 NBL上报给 NDIS。第二步NDIS 按绑定关系把这个 NBL 复制或分发给上层——发给 TCP/IP 栈同时发给你的协议驱动的 ProtocolReceiveNetBufferLists 回调。第三步你的回调拿到 NBL检查数据、剥离头部、拷贝到自己的缓冲区然后调用 NdisReturnNetBufferLists或 NdisReturnNetBufferLists 的变体把 NBL 还给 NDIS。发送方向对称你的驱动调用 NdisSendNetBufferLists在较新 NDIS 版本里是 NdisSendNetBufferLists把要发的 NBL 交给 NDISNDIS 再调度给网卡驱动。网卡发完后Miniport 驱动会回调你的 ProtocolSendNetBufferListsComplete通知你包已经发出去了缓冲区可以回收了。这一步最容易被新手漏掉——发完不处理 Complete 回调等于包是送出去了但你的内存永远收不回来跑几天就内存暴涨。提示NDIS 6 之后Vista 及以后接收和发送的数据结构统一成 NET_BUFFER_LIST。NDIS 5 时代的 OOB 数据、包描述符已经进了历史教科书源码如果是老的你需要做结构转换。2.3 选型判断什么时候必须写协议驱动什么时候用过滤驱动就行新手最常见的问题不是不会写而是选错路。如果你的目标只是看看某个进程发了什么包Windows 有现成的 ETWEvent Tracing for Windows抓包根本不需要驱动如果想拦断或修改数据包那过滤驱动Filter Driver是更轻量的选择——它不用注册成协议直接挂在网卡和协议栈之间像个中间人。协议驱动的优势在于它是一个独立的端点它可以接收网卡上所有的入站包包括广播、组播、不是发给本机的包只要网卡没过滤掉这对网络监控、网络分析、旁路采集类工具特别关键。另外还有一种适用场景是用户态要裸帧、系统不用这套协议比如做自定义二层协议的设备接入你不希望 Windows 的 TCP/IP 栈去解析它只想让自己的程序通过 IOCTL 和驱动通信拿到原始帧。此时协议驱动是最干净的方案因为 NDIS 允许你通过绑定设置只把特定 EtherType以太网类型的帧分给你其他的让系统协议栈处理。过滤驱动虽然也能干但要在数据路径上做更多手工判断。后面第 4 章的代码就以接收自定义 EtherType 裸帧为例这是协议驱动最典型的外网接口应用。3. WDK 开发环境与最小协议驱动让驱动先能被加载3.1 搭建 Windows 驱动开发环境别在版本上折腾太久开发协议驱动环境其实比写代码更劝退。常见做法是一台装有 Visual Studio 的开发者机器或虚拟机安装 Windows Driver KitWDK——WDK 提供驱动开发的头文件、库和构建工具另外准备一台单独的测试机用于加载和调试驱动。测试机最好是独立虚拟机因为协议驱动写不好崩的是整个系统网络栈宿主机直接断网你连远程调试都做不了。有一个血泪经验值得先讲WDK 版本必须匹配你目标系统的 Windows 版本。如果你在 Win10 1809 上编译的驱动拿到 Win11 上加载最常见的结果是INF 里写的版本范围不对驱动服务根本起不来。我一般会先把测试机的 Windows 版本定死再去装匹配的 WDK。另一个常见坑是符号Symbol问题调试协议驱动强烈建议配微软符号服务器否则 WinDbg 里看 NDIS 内部结构全是问号等于让调试难度翻倍。3.2 用框架生成一个裸的协议驱动工程打开 Visual Studio新建项目时选Empty WDM Driver或者Kernel Mode Driver: Empty这比从零手写 SOURCES 文件省事。建好之后你会得到一个空驱动的骨架一个 DriverEntry 入口和一个卸载函数。协议驱动和普通驱动的主要差异在这几个地方DriverEntry 里要分配并填充 NDIS_PROTOCOL_DRIVER_CHARACTERISTICS 结构然后调用 NdisRegisterProtocolDriver 注册自己必须实现至少一组协议回调Bind、Open、Receive、SendComplete 等卸载时要 NdisDeregisterProtocolDriver。先看一段最简 DriverEntry注意这里我并不贴完整可编译工程而是讲清关键结构完整工程建议用 WDK 自带的 passthru 示例改造WDK 安装目录下有现成的协议驱动样例。// 简化版协议驱动的 DriverEntry 核心流程 NDIS_STATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NDIS_PROTOCOL_DRIVER_CHARACTERISTICS ProtoChars; NDIS_HANDLE NdisHandle NULL; NDIS_STATUS Status; // 1. 清零并填充协议驱动特征结构 NdisZeroMemory(ProtoChars, sizeof(ProtoChars)); ProtoChars.Header.Type NDIS_OBJECT_TYPE_PROTOCOL_DRIVER; ProtoChars.Header.Size sizeof(ProtoChars); ProtoChars.Header.Revision NDIS_PROTOCOL_DRIVER_CHARACTERISTICS_REVISION_2; // 2. 关键回调函数指针协议驱动必须注册绑定和接收 ProtoChars.MajorVersion 6; ProtoChars.MinorVersion 0; ProtoChars.BindAdapterHandler ProtocolBindAdapter; ProtoChars.UnbindAdapterHandler ProtocolUnbindAdapter; ProtoChars.OpenAdapterCompleteHandler ProtocolOpenAdapterComplete; ProtoChars.CloseAdapterCompleteHandler ProtocolCloseAdapterComplete; ProtoChars.ReceiveNetBufferListsHandler ProtocolReceiveNetBufferLists; ProtoChars.SendNetBufferListsCompleteHandler ProtocolSendNetBufferListsComplete; ProtoChars.Name NDIS_STRING_CONST(MyPacketProto); ProtoChars.DriverObject DriverObject; // 3. 注册为 NDIS 协议驱动 Status NdisRegisterProtocolDriver(DriverObject, ProtoChars, NdisHandle); if (Status ! NDIS_STATUS_SUCCESS) { return Status; } // 保存 NdisHandle 到驱动全局变量后面绑定适配器要用 g_ProtocolHandle NdisHandle; return NDIS_STATUS_SUCCESS; }逻辑说明这一步做的事情是告诉 NDIS 我是谁。NDIS_PROTOCOL_DRIVER_CHARACTERISTICS 是协议驱动的身份证里面的每个 Handler 都是 NDIS 在特定事件发生时回调你的入口。 BindAdapterHandler 必须注册否则 NDIS 永远不知道你的驱动要绑到哪块网卡上ReceiveNetBufferListsHandler 和 SendNetBufferListsCompleteHandler 是收发路径的核心。如果这些回调没注册齐全NdisRegisterProtocolDriver 会返回参数错误驱动加载失败。参数说明Header.Revision 应该用当前 WDK 支持的版本宏这里写 2 是 NDIS 6.x 的常见值MajorVersion/MinorVersion 标识你实现的 NDIS 版本6.0 对应 Vista/Win7 时代新驱动可以填 6.40Win10 1709具体以你装的 WDK 头文件为准。名字 Name 会成为注册表里看到的关键词尽量起一个有辨识度的名字比如PacketMonProto后面排错时在注册表里找它容易得多。3.3 INF 和安装让 Windows 把你的驱动当成协议驱动加载驱动编译出来是个 .sys 文件但 Windows 不会平白无故加载它你需要一个 INF 安装文件。协议驱动的 INF 和普通驱动的结构很不一样。最关键的是要声明这是协议驱动类名 NetTrans 或 NetService并在 DDInstall 节里用特征 GUID 注册。[Version] Signature $WINDOWS NT$ Class NetTrans ClassGuid {4D36E975-E325-11CE-BFC1-08002BE10318} Provider %ManufacturerName% DriverVer 08/28/2024,1.0.0.0 [Manufacturer] %ManufacturerName% DeviceList, NTamd64 [DeviceList.NTamd64] %DeviceDescription% Install, UMI\MyProto [Install.NT] CopyFiles CopyFiles [Install.NT.Services] AddService MyPacketProto, 0x00000002, Service_Inst [Service_Inst] DisplayName MyPacketProto ServiceType 1 StartType 1 ErrorControl 1 ServiceBinary %12%\MyPacketProto.sys逻辑说明Class 用 NetTrans 表示这是传输层协议驱动ClassGuid 是 NDIS 协议驱动专用的类 GUID。AddService 后面的 0x00000002 表示覆盖已有服务如果你的驱动重装后不生效检查这里。StartType 1SYSTEM_START表示系统启动时加载协议驱动建议用这个值因为它在网卡初始化之前就要注册好等网卡起来后才能绑定。ServiceBinary 用 %12% (系统目录 \system32\drivers) 来放置 sys 文件。安装步骤把编译好的 .sys 和 .inf 拷到一个文件夹右键 INF 选安装然后在设备管理器里找到网络协议分类或直接拷贝到系统盘后用devcon工具或RUNDLL32安装。这一步是第一个翻车高发区协议驱动安装后不会出现在网络适配器里而是在网络协议里很多人装完在网卡列表里找不到驱动以为失败其实它和 TCP/IP 协议同列需要展开网络协议分类才能看到。4. 协议驱动核心骨架绑定、接收、发送三步走4.1 BindAdapter让驱动和你的外网网卡绑定写完 DriverEntry下一步是真正绑定到网卡。NDIS 调用你的 ProtocolBindAdapter 时给你传一个绑定上下文NDIS_HANDLE BindContext和网卡的特征信息。你在绑定时要决定是否接受这块网卡比如只绑定物理外网接口跳过 loopback然后发起 NdisOpenAdapter。先看绑定处理器NDIS_STATUS ProtocolBindAdapter(NDIS_HANDLE NdisBindContext, NDIS_HANDLE ProtocolHandle) { PADAPT pAdapt NULL; NDIS_OPEN_PARAMETERS OpenParams; NDIS_STRING BaseName NDIS_STRING_CONST(\\Device\\MyProto_); NDIS_STATUS Status; // 1. 分配适配器上下文保存这块网卡绑定后的状态 pAdapt NdisAllocateMemoryWithTagPriority( NDIS_HANDLE(NdisBindContext), sizeof(ADAPT), taPM, LowPoolPriority); if (pAdapt NULL) return NDIS_STATUS_RESOURCES; pAdapt-BindContext NdisBindContext; pAdapt-ProtocolHandle ProtocolHandle; // 2. 初始化打开参数指定这是协议驱动的打开操作 NdisZeroMemory(OpenParams, sizeof(OpenParams)); OpenParams.Header.Type NDIS_OBJECT_TYPE_OPEN_PARAMETERS; OpenParams.Header.Revision NDIS_OPEN_PARAMETERS_REVISION_1; OpenParams.Header.Size sizeof(OpenParams); OpenParams.AdapterName NULL; // 使用 NDIS 传入的默认适配器名 OpenParams.ProtocolCharacteristics NULL; // 3. 发起异步打开适配器 Status NdisOpenAdapterEx( pAdapt, // 上下文句柄 pAdapt-NdisAdapterHandle, // 返回的适配器句柄 OpenParams, NdisBindContext, ProtocolHandle, 0); if (Status ! NDIS_STATUS_PENDING) { // 即时成功 return ProtocolOpenAdapterComplete(pAdapt, Status, NDIS_STATUS_SUCCESS); } return NDIS_STATUS_PENDING; }逻辑说明关键是 NdisOpenAdapterEx 的异步语义。这个 API 可能立即返回成功也可能返回 NDIS_STATUS_PENDING 表示稍后完成真正的结果会通过你注册的 OpenAdapterCompleteHandler 回调告诉你。所以这里不能看到 PENDING 就算失败那是新手最常犯的判断错误。pAdapt 是你自己定义的每个适配器上下文结构体里面保存句柄、统计计数、锁等每次打开一个网卡就分配一个。参数说明NdisAllocateMemoryWithTagPriority 的 tag 参数taPM是四字符内存标签调试内存泄漏时WinDbg 里搜这个 tag 就能找到你的泄漏缓冲区。NdisOpenAdapterEx 的最后一个参数为 0 表示默认媒体类型如果你只关心以太网可以在打开时指定 NdisMedium802_3 来过滤非以太网接口。4.2 ReceiveNetBufferLists数据到了怎么摘出来绑定成功后网卡的所有入站数据会走这个接收回调。这里每一步都影响性能和稳定性void ProtocolReceiveNetBufferLists( NDIS_HANDLE ProtocolBindingContext, // pAdapt NET_BUFFER_LIST *NBLChain, // 一次可能给一串包 ULONG PortNumber, ULONG NumberOfNbls, // 包数量 ULONG ReceiveFlags) // 标志位含 NDIS_RECEIVE_FLAGS_RESOURCES { PADAPT pAdapt (PADAPT)ProtocolBindingContext; NET_BUFFER_LIST *pNbl NBLChain; BOOLEAN bReturnNow TRUE; while (pNbl ! NULL) { NET_BUFFER *pNb NET_BUFFER_LIST_FIRST_NB(pNbl); ULONG DataLength NET_BUFFER_DATA_LENGTH(pNb); PVOID pData NET_BUFFER_DATA(pNb); // 在这里处理你的数据可以拷贝到环形缓冲、上报用户态等 ProcessRawPacket(pAdapt, pData, DataLength); pNbl NET_BUFFER_LIST_NEXT_NBL(pNbl); } // 资源紧张时网卡等不了必须立即归还 NBL if (ReceiveFlags NDIS_RECEIVE_FLAGS_RESOURCES) { NdisReturnNetBufferLists(pAdapt-NdisAdapterHandle, NBLChain, 0); } // 否则你可以选择稍后返回但整条链要一次性还 }逻辑说明NBLChain 是一个链表一次回调可能带着多个数据包你要遍历整条链处理。NET_BUFFER_LIST_FIRST_NB 找到包数据。很多新手踩过一个包就 break导致丢包。关于 NdisReturnNetBufferLists 的时机NDIS_RECEIVE_FLAGS_RESOURCES 标志为真时意味着网卡驱动内存吃紧你必须在回调内立刻返回绝对不允许缓存否则可以放到工作线程延迟返回但这样会引入额外锁和生命周期管理的复杂度。早期版本可以简单点一律立即返回处理时做数据拷贝。参数说明对原始帧而言NET_BUFFER_DATA 指向的缓冲区开头就是以太网头目的 MAC、源 MAC、EtherType。你要解析的是自定义 EtherType就先按 12 字节偏移读取 EtherType再决定后续。ProcessRawPacket 里建议只做浅拷贝、把数据放进队列让工作线程慢慢处理不要在接收路径里做重活否则丢包率会直线上升。4.3 SendNetBufferLists要主动发包先从用户态把包送进来协议驱动的发送路径通常从用户态发起。常见做法是驱动暴露一个设备对象比如 \Device\MyProto用户态程序用 WriteFile/DeviceIoControl 把待发送的原始帧塞进来。驱动收到后分配 NET_BUFFER_LIST填充数据然后 NdisSendNetBufferLists。看发送函数的骨架NDIS_STATUS SendRawFrame(PADAPT pAdapt, PVOID Data, ULONG Length) { NET_BUFFER_LIST *pNbl; NET_BUFFER *pNb; PMDL pMdl; NDIS_STATUS Status; // 1. 从发送池分配 NBL在初始化时用 NdisAllocateNetBufferListPool 建好 pNbl NdisAllocateNetBufferList(g_SendPool, 0, 0); if (pNbl NULL) return NDIS_STATUS_RESOURCES; // 2. 从 NBL 拿到 NET_BUFFER再将数据封进 MDL pNb NET_BUFFER_LIST_FIRST_NB(pNbl); // 3. 用 NdisAllocateMdl 分配 MDL锁定用户态缓冲区 pMdl NdisAllocateMdl(pAdapt, Data, Length); if (pMdl NULL) { NdisFreeNetBufferList(pNbl); return NDIS_STATUS_RESOURCES; } // 4. 把 MDL 关联到 NET_BUFFER NET_BUFFER_DATA_LENGTH(pNb) Length; NET_BUFFER_CURRENT_MDL(pNb) pMdl; // 5. 排队发送内部会在大量包时做标记NDIS_SEND_FLAGS_DISPATCH_LEVEL pNbl-SourceHandle pAdapt-NdisAdapterHandle; Status NdisSendNetBufferLists(pAdapt-NdisAdapterHandle, pNbl, 0, 0); return Status; }逻辑说明发送的关键是 MDL内存描述符列表。内核驱动不能直接访问用户态缓冲区需要用 MDL 锁定用户内存的物理页才能给网卡 DMA。NdisAllocateMdl 专门做这件事。注意发送完的回收不是立即执行而是由网卡驱动回调你的 ProtocolSendNetBufferListsComplete你在那个回调里释放 MDL 和 NBL。这两个分配必须配对否则一次发送泄漏两个对象。参数说明g_SendPool 在 DriverEntry 或 BindAdapter 时用 NdisAllocateNetBufferListPool 创建参数指定了预分配的 NBL 数量。NdisSendNetBufferLists 是异步的返回 NC_SUCCESS 只代表包已被接受排队不代表已经上线路。如果你需要知道发送是否成功要在 Complete 回调里查 Status。5. 协议驱动开发避坑指南绑定不上、收不到包、蓝屏怎么查5.1 驱动装上了但网卡没有绑定协议驱动现象注册表有驱动网络协议列表里没有它原因很集中INF 里 Class 写错写成 Net 或 NetService 而不是 NetTrans。另一个原因是 INF 里 AddService 的 StartType 设置成了 3手动启动但 NDIS 协议驱动必须在网卡枚举前注册。解决改 INF把 Class 改成 NetTransStartType 改成 1重装。如果注册表 HKLM\SYSTEM\CurrentControlSet\Services\ 下能看到服务但状态是 1说明驱动加载失败先用sc query MyPacketProto看服务状态码再去事件查看器里找 NDIS 报的绑定警告。5.2 协议驱动绑定上了但 ReceiveNetBufferLists 永远不触发现象网卡收包正常你的回调却静的像黑匣子第一个排查点是绑定对象如果你绑定到无线网卡或某些虚拟网卡这类接口遵循 Native 802.11 / 虚拟端口语义可能不会把普通数据帧送给你。所以目标网卡一定要选真实的以太网物理接口或至少选一个能用抓包工具看到裸帧的接口。第二个排查点是 EtherType网卡或其驱动有自己的接收过滤默认情况下不是发给本机 MAC 的单播包或者网卡不认识的协议类型会被硬件直接丢弃根本到不了你的驱动。解决方法是查看网卡高级属性里的协议过滤或让对端发广播帧测试——广播帧一般都会送上来。可以用 Wireshark 在应用层同步抓包对比如果 Wireshark 能看到而你的驱动看不到那问题在绑定层如果两者都看不到是硬件过滤或网卡侧的问题。5.3 BSOD 蓝屏最常见的是 IRQL_NOT_LESS_OR_EQUAL 或内存池标记违规发展一个协议驱动的过程中蓝屏概率最高的三个点一是在 Dispatch_LEVEL 上调用需要 PASSIVE_LEVEL 的 API比如直接 memset 一个分页内存指针二是在接收回调里调用了 NdisReturnNetBufferLists 两次一次在回调里、一次在别的线程对象被重复释放三是 SendComplete 回调里释放了还在用的 NBL造成 use-after-free。建议做法接收回调里只做轻量拷贝并把 NBL 引用加一工作线程处理完再释放发送路径一定要给 NBL 打上自己的内存 tagWinDbg 里用!poolfind taPM或!verifier开启驱动验证器Driver Verifier来追问题。Driver Verifier 是全套路标协议驱动开发期间建议对自研驱动全量开启它会把可用性问题提前引爆比上线后翻车好受一百倍。5.4 用 WDK 新版本编译旧协议驱动源码一堆报错现象头文件结构变了、API 改名了NDIS 6 以来的变化主要在三块一是 NET_BUFFER_LIST 结构体不再直接暴露内部字段要求用宏访问比如 NET_BUFFER_FIRST_NB二是老的 Init 类函数NdisOpenAdapter被 Ex 版本取代NdisOpenAdapterEx三是 OOB 数据和包描述符没了帧信息都在 NET_BUFFER_LIST 里用独立结构挂载。解决顺序是先看 WDK 里的 passthru 示例samples\net\ndis\passthru如果你的代码是基于 NDIS 5 的把初始化代码逐段对比改不要试图在新 WDK 里保留老的 INFO 文件构建方式直接用 Visual Studio 工程重写驱动入口结构更省事。5.5 驱动卸载后网卡无法正常收发网络数据现象拔掉驱动后 TCP/IP 不通重启才恢复原因是绑定逻辑不干净——你绑定网卡后NDIS 可能把网卡的某些能力切换成了协议驱动特有模式比如当你打开 NdisMedium802_3 时要求独占某些帧卸载时没有干净地关闭适配器和归还 NBL。解决卸载前先在你的驱动里调用 NdisCloseAdapterEx 并等待 Close 完成同时确保所有 Receive 的 NBL 都已通过 NdisReturnNetBufferLists 归还再调用 NdisDeregisterProtocolDriver。如果实在发生半挂状态先在设备管理器里禁用再启用网卡驱动层会重新触发一次绑定和解除流程比重启快。6. 最后一章验证协议驱动正确性的三个方法以及把驱动从能跑做成能用开发完驱动不是终点验证正确性才算交付。我给三个由浅入深的验证手段。第一层是静态验证用 Driver Verifier 开启内核内存和 IRQL 检查在测试机上跑一轮全套收发测试确保无告警或蓝屏。第二层是动态验证用 WinDbg 内核调试在 ProtocolReceiveNetBufferLists 入口下断点通过!ndiskd.nbl查看 NBL 的 MDL 和缓冲区内容确认你拿到的确实是网卡送达的原始帧同时用!ndiskd.protocol查看你的协议驱动实例是否被绑定到正确的物理网卡接口上。第三层是业务验证在用户态程序里实现一个环回测试通过 IOCTL 构造一个自定义 EtherType 的包发给驱动驱动收到后原样发回网卡外网口或直接回环用户态再统计收到的包数和字节数和发送端比对每一个字节不一致都说明你的 MDL 关联或数据拷贝有 OffByOne 错误。进阶方向上有三个常见加分项值得投入一是把立即返回 NBL 队列拷贝改成内存池 环形缓冲区彻底消除接收路径上的锁竞争包吞吐能提升两三倍二是通过 IOCTL 动态配置 EtherType 白名单让驱动只关注你关心的协议帧避免把网卡上的所有流量都复制上来浪费 CPU三是加入 send 和 receive 两个方向的统计计数包数、字节数、丢弃数排查问题时这是最直接的证据。我自己的教训是当年做类似外网接口驱动的时候因为偷懒没有在卸载时完整归还 NBL测试机上那个网卡三天后彻底假死Wireshark 还能看到包、但系统 TCP/IP 却收不到任何数据最后只能禁用再启用网卡。所以现在我的习惯是每一次改动先把绑定 — 收发 — 卸载全链路跑一遍特别是卸载一定要确认所有从 NDIS 借来的对象都还回去了这比功能跑通重要得多。想清楚这些你手里的协议驱动源码包就能从能编译变成能上线。希望帮到你。本文还有配套的精品资源点击获取
返回列表