简介:这是一份面向Windows内核网络驱动开发者的NDIS 6.0 Filter驱动案例代码,基于Win10 x64平台,使用VS 17.11.4与WDK 10.0.26100.1构建。在基础Filter框架上新增了发送OID请求、发送自定义ICMP数据包以及接收数据包三项功能,并补充了查询网卡MAC地址的实现,适合具备一定C++与驱动基础、希望深入理解NDIS过滤驱动收发流程的中高级开发者参考。资源包共16个文件,约37KB,以c与h源码为主体,涵盖Filter主体逻辑、设备管理、调试输出与功能实现等模块,另含vcxproj、sln、inf、rc及演示用cpp文件,便于直接编译与调试。目前已有141人学习。通过阅读代码可掌握内核OID请求的发送与接收、内核资源的分配与回收、数据包构造与收发等核心技能,是理解Windows内核网络编程的实用范例。
1. 从抓包到改包:一个 NDIS 6.0 Filter 驱动能帮你做到什么
如果你在 Windows 上做过网络过滤,大概率经历过这样的场景:想抓本机收发的每一个以太网帧,用 WinPcap 或 Npcap 确实能抓到,但一旦想在内核层直接改包、丢包、甚至伪造一个响应,用户态那套就力不从心了。这个项目就是冲着这个痛点来的——它是一套基于 Windows 10 的 NDIS 6.0 Filter 驱动,在基础过滤框架之上,额外补上了收发自定义数据包和查询网卡 MAC 地址这两块能力。换句话说,它不只是一个被动的“看客”,而是一个能主动往网卡里塞包、也能把网卡身份信息读出来的内核模块。适合谁?适合已经写过 WDF/KMDF 小驱动、想往网络协议栈深处走一步的 C++ 开发者,也适合做网络行为分析、协议仿真、内网资产探测这类工具的同学。你拿到源码后,用 Visual Studio 加 WDK 就能编译加载,不需要额外买硬件。
2. 架构拆解:Filter 驱动在协议栈里站在哪、怎么拦
2.1 NDIS 6.0 Filter 的挂载位置与数据流向
要理解这份代码,先得把 NDIS Filter 在协议栈里的位置摆正。Windows 网络栈从上到下大致是:应用层 → 协议驱动(TCPIP.sys)→Filter 驱动(就是本项目)→ 微端口驱动(网卡厂商的 miniport)→ 物理网卡。Filter 驱动是“夹心层”,它注册到 NDIS 后,NDIS 会在协议驱动和微端口驱动之间插入一个 filter 实例。所有上下行的包都会经过你注册的回调函数。
这个项目里,核心的挂载逻辑在DriverEntry和FilterAttach两个函数里。DriverEntry负责向 NDIS 注册一组NDIS_FILTER_DRIVER_CHARACTERISTICS,里面填的就是各种回调的入口地址。FilterAttach则在每个网卡上被调用一次,用来分配适配器上下文、设置过滤属性。
// DriverEntry 中注册 Filter 驱动特征 NDIS_FILTER_DRIVER_CHARACTERISTICS filterChars; NdisZeroMemory(&filterChars, sizeof(filterChars)); filterChars.Header.Type = NDIS_OBJECT_TYPE_FILTER_DRIVER_CHARACTERISTICS; filterChars.Header.Size = NDIS_SIZEOF_FILTER_DRIVER_CHARACTERISTICS_REVISION_1; filterChars.Header.Revision = NDIS_FILTER_DRIVER_CHARACTERISTICS_REVISION_1; filterChars.MajorNdisVersion = 6; filterChars.MinorNdisVersion = 0; filterChars.MajorDriverVersion = 1; filterChars.MinorDriverVersion = 0; filterChars.Flags = 0; // 关键回调:附着、分离、收发、状态查询 filterChars.AttachHandler = FilterAttach; filterChars.DetachHandler = FilterDetach; filterChars.RestartHandler = FilterRestart; filterChars.PauseHandler = FilterPause; filterChars.SendNetBufferListsHandler = FilterSendNetBufferLists; filterChars.ReturnNetBufferListsHandler = FilterReturnNetBufferLists; filterChars.ReceiveNetBufferListsHandler = FilterReceiveNetBufferLists; filterChars.OidRequestHandler = FilterOidRequest; filterChars.OidRequestCompleteHandler = FilterOidRequestComplete; filterChars.CancelOidRequestHandler = FilterCancelOidRequest; // 用项目名和唯一服务名注册 NdisRegisterFilterDriver(&filterChars, &g_FilterDriverHandle);这段代码里几个参数值得注意:MajorNdisVersion = 6和MinorNdisVersion = 0明确声明了这是 NDIS 6.0 驱动,Windows 10 完全兼容;Flags设为 0 表示不启用可选的旁路或只读模式,意味着这个 filter 会参与完整的收发路径。AttachHandler是每个网卡绑定时都会走的入口,后面查 MAC 地址、发自定义包,上下文都从这里拿。
2.2 适配器上下文:把网卡句柄和 MAC 地址存下来
Filter 驱动在每个网卡上都会有一个独立的上下文结构。这个项目里定义了一个FILTER_ADAPTER_CONTEXT,里面至少存了 filter 句柄、物理设备对象指针,以及一个用来保存 MAC 地址的数组。为什么要在 attach 阶段就把 MAC 查出来?因为后面发自定义包时,以太网帧头需要填源 MAC,如果每次都去查 OID 会拖慢速度,而且有些 OID 请求在特定电源状态下会失败。
typedef struct _FILTER_ADAPTER_CONTEXT { NDIS_HANDLE FilterHandle; // Filter 驱动句柄 NDIS_HANDLE MiniportHandle; // 底层微端口句柄 PDEVICE_OBJECT PhysicalDevice; // 物理设备对象 UCHAR CurrentMac[6]; // 当前网卡 MAC BOOLEAN MacValid; // MAC 是否已成功获取 NDIS_SPIN_LOCK Lock; // 保护上下文字段 } FILTER_ADAPTER_CONTEXT, *PFILTER_ADAPTER_CONTEXT;FilterAttach里会调用NdisAllocateMemoryWithTagPriority分配这块内存,然后立刻发起一次 OID 查询去拿 MAC。查询用的是NdisOidRequest,OID 码是OID_802_3_CURRENT_ADDRESS。这个 OID 返回的就是当前网卡生效的 6 字节 MAC,注意不是永久地址,如果网卡支持 MAC 随机化,这里拿到的是随机化之后的地址。查询是异步的,完成回调里把结果拷进CurrentMac并置MacValid = TRUE。
2.3 收发路径:SendNetBufferLists 与 ReceiveNetBufferLists 的改写点
Filter 驱动最核心的两个数据路径回调就是FilterSendNetBufferLists和FilterReceiveNetBufferLists。前者处理本机往外发的包,后者处理从网卡收上来的包。这个项目在这两个函数里都留了改写点,你可以在这里做丢包、改包、镜像转发。
// 发送路径:本机发出的包会先到这里 VOID FilterSendNetBufferLists( NDIS_HANDLE FilterModuleContext, PNET_BUFFER_LIST NetBufferLists, NDIS_PORT_NUMBER PortNumber, ULONG SendFlags) { PFILTER_ADAPTER_CONTEXT ctx = (PFILTER_ADAPTER_CONTEXT)FilterModuleContext; PNET_BUFFER_LIST currentNbl = NetBufferLists; while (currentNbl != NULL) { PNET_BUFFER nb = NET_BUFFER_LIST_FIRST_NB(currentNbl); // 在这里可以解析以太网帧头,判断是否要丢弃或修改 // 例如:如果目的 MAC 是某个特定值,直接标记为丢弃 // 实际项目中这里会调用自定义的包处理函数 currentNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); } // 处理完后继续往下层微端口发送 NdisFSendNetBufferLists(ctx->FilterHandle, NetBufferLists, PortNumber, SendFlags); }FilterModuleContext就是 attach 时创建的适配器上下文,NetBufferLists是一个链表,每个节点是一个NET_BUFFER_LIST,里面挂着实际的NET_BUFFER数据。SendFlags常见的有NDIS_SEND_FLAGS_DISPATCH_LEVEL,表示当前在 DISPATCH_LEVEL 中断级别,这时候不能调分页内存。接收路径类似,只是方向相反,而且接收路径里可以调用NdisFIndicateReceiveNetBufferLists把包重新指示给上层协议驱动。
注意:在
FilterSendNetBufferLists里改包时,如果修改了数据长度,必须同步更新NET_BUFFER的DataLength和NET_BUFFER_LIST的相关字段,否则 NDIS 校验会失败,直接蓝屏。
3. 环境搭建与编译:从零把驱动跑起来
3.1 工具链准备:VS + WDK 的版本匹配
这个项目是 C++ 写的,依赖 WDK(Windows Driver Kit)。在 Windows 10 上,推荐用 Visual Studio 2019 或 2022,搭配对应版本的 WDK。版本匹配是个玄学问题,我踩过的坑是:VS 2022 配 WDK 10.0.22621 能正常编译 NDIS 6.0 驱动,但如果你用 VS 2017 配老版本 WDK,NdisRegisterFilterDriver的签名可能对不上。安装时勾选“使用 C++ 的桌面开发”和“Windows 驱动开发”两个工作负载,WDK 会自动带上。
装完之后,在 VS 里新建项目时选“Kernel Mode Driver (KMDF)”或者“Empty WDM Driver”,然后把项目里的.vcxproj配置改成 NDIS Filter 类型。关键配置项在项目属性 → Driver Settings → Target OS Version 选 Windows 10,Driver Type 选 NDIS Filter。如果你拿到的是现成的.sln,直接打开就行,但记得检查 WDK 版本号是否和你本机安装的一致。
3.2 编译配置:inf 文件与签名设置
驱动编译产物是一个.sys文件加一个.inf文件。.inf里定义了驱动的安装方式、服务名、以及要绑定的网卡类型。这个项目的 inf 里,FilterType一般设为1(表示修改型 filter),FilterMediaTypes设为ethernet。如果你想让驱动在所有网卡上生效,FilterMediaTypes可以留空或写ethernet覆盖有线,无线网卡需要额外加native802_11。
; 项目 inf 文件关键片段 [Manufacturer] %ManufacturerName% = Standard, NTamd64 [Standard.NTamd64] %FilterServiceName% = FilterInstall, NDIS_FILTER [FilterInstall] Characteristics = 0x1 ; NCF_HAS_UI 可选 FilterType = 1 ; 修改型 filter FilterMediaTypes = ethernet编译时如果开了测试签名,需要在管理员命令行里执行bcdedit /set testsigning on然后重启。正式签名需要 EV 证书,个人开发阶段用测试签名就够了。注意:Windows 10 1809 之后,测试签名模式下加载驱动仍然可能被 Secure Boot 拦截,如果遇到,进 BIOS 关掉 Secure Boot 再试。
3.3 安装与加载:netcfg 与 sc 命令的配合
编译出.sys和.inf后,安装驱动最稳的方式是用netcfg命令,而不是直接右键 inf 安装。因为 NDIS Filter 需要注册到网络组件里,右键安装有时候不会正确绑定到网卡。
# 以管理员身份运行 # 安装 filter 驱动,-i 指定 inf,-c 指定组件类型 netcfg -v -l "你的驱动.inf" -c f -i "你的服务名" # 查看是否安装成功 netcfg -s n # 如果安装失败,用 sc 删除残留服务再重试 sc delete 你的服务名-c f表示组件类型是 filter,-i后面跟服务名,这个服务名必须和 inf 里FilterServiceName一致。安装成功后,在“网络连接”里右键任意网卡 → 属性,应该能看到你的 filter 驱动出现在列表里,并且默认是勾选状态。如果没出现,检查 inf 里的FilterMediaTypes是否匹配当前网卡类型。
提示:卸载驱动前先在网卡属性里取消勾选,然后用
netcfg -u 服务名卸载,直接删文件会导致网卡属性里残留无效条目。
4. 核心功能实现:发自定义包与查 MAC 的代码细节
4.1 构造并发送以太网帧:NdisFSendNetBufferLists 的完整用法
发自定义包是这个项目区别于普通 filter 的关键。普通 filter 只能转发或丢弃已有的包,而这里可以凭空造一个包发出去。实现思路是:分配一个NET_BUFFER_LIST,挂上一个NET_BUFFER,把以太网帧数据填进去,然后调用NdisFSendNetBufferLists往下层发。
// 发送一个自定义以太网帧 NDIS_STATUS SendCustomEthernetFrame( PFILTER_ADAPTER_CONTEXT ctx, PUCHAR DestMac, PUCHAR SourceMac, USHORT EtherType, PUCHAR Payload, ULONG PayloadLen) { PNET_BUFFER_LIST nbl = NULL; PNET_BUFFER nb = NULL; PMDL mdl = NULL; PVOID dataBuffer = NULL; ULONG totalLen = 14 + PayloadLen; // 14 字节以太网头 // 1. 分配 NBL 和 NB nbl = NdisAllocateNetBufferList(g_NblPoolHandle, 0, 0); if (!nbl) return NDIS_STATUS_RESOURCES; // 2. 分配数据缓冲区 dataBuffer = NdisAllocateMemoryWithTagPriority( ctx->FilterHandle, totalLen, 'pktS', NormalPriority); if (!dataBuffer) { NdisFreeNetBufferList(nbl); return NDIS_STATUS_RESOURCES; } // 3. 填充以太网帧头 PUCHAR p = (PUCHAR)dataBuffer; NdisMoveMemory(p, DestMac, 6); // 目的 MAC NdisMoveMemory(p + 6, SourceMac, 6); // 源 MAC p[12] = (UCHAR)(EtherType >> 8); // 类型字段高字节 p[13] = (UCHAR)(EtherType & 0xFF); // 类型字段低字节 if (PayloadLen > 0 && Payload) NdisMoveMemory(p + 14, Payload, PayloadLen); // 4. 创建 MDL 并挂到 NB 上 mdl = IoAllocateMdl(dataBuffer, totalLen, FALSE, FALSE, NULL); if (!mdl) { /* 清理并返回 */ } MmBuildMdlForNonPagedPool(mdl); nb = NdisAllocateNetBuffer(g_NbPoolHandle, mdl, 0, totalLen); NET_BUFFER_LIST_FIRST_NB(nbl) = nb; NET_BUFFER_DATA_LENGTH(nb) = totalLen; // 5. 发送 NdisFSendNetBufferLists(ctx->FilterHandle, nbl, NDIS_DEFAULT_PORT_NUMBER, 0); return NDIS_STATUS_SUCCESS; }逐段说明:第一步用NdisAllocateNetBufferList从 NBL 池里拿一个空链表节点,池句柄在FilterAttach时用NdisAllocateNetBufferListPool创建。第二步分配实际的数据缓冲区,tag'pktS'是自定义的,方便在内存池调试时识别。第三步按以太网 II 帧格式填头,目的 MAC 和源 MAC 各 6 字节,类型字段 2 字节大端。第四步创建 MDL 描述这块内存,MmBuildMdlForNonPagedPool把 MDL 映射到非分页池的物理页。第五步调用NdisFSendNetBufferLists把包交给下层微端口。注意发送完成后,NBL 和内存的释放是在FilterSendNetBufferListsComplete回调里做的,不能在这里直接释放。
4.2 查询网卡 MAC 地址:OID_802_3_CURRENT_ADDRESS 的异步处理
查 MAC 用的是标准 OID 请求。在FilterAttach里发起,完成回调里取结果。这个项目把查询封装成了一个函数,方便在需要时重新查询。
// 发起 MAC 地址查询 NDIS_STATUS QueryMacAddress(PFILTER_ADAPTER_CONTEXT ctx) { NDIS_OID_REQUEST oidReq; NdisZeroMemory(&oidReq, sizeof(oidReq)); oidReq.Header.Type = NDIS_OBJECT_TYPE_OID_REQUEST; oidReq.Header.Revision = NDIS_OID_REQUEST_REVISION_1; oidReq.Header.Size = NDIS_SIZEOF_OID_REQUEST_REVISION_1; oidReq.RequestType = NdisRequestQueryInformation; oidReq.DATA.QUERY_INFORMATION.Oid = OID_802_3_CURRENT_ADDRESS; oidReq.DATA.QUERY_INFORMATION.InformationBuffer = ctx->CurrentMac; oidReq.DATA.QUERY_INFORMATION.InformationBufferLength = 6; return NdisFOidRequest(ctx->FilterHandle, &oidReq); }NdisFOidRequest是异步的,返回NDIS_STATUS_PENDING表示请求已下发,结果会在FilterOidRequestComplete回调里返回。在完成回调里,检查oidReq->DATA.QUERY_INFORMATION.BytesWritten是否等于 6,是的话把MacValid置 TRUE。这里有个细节:InformationBuffer直接指向ctx->CurrentMac,所以完成时数据已经写进去了,不需要额外拷贝。但要注意,如果查询失败,CurrentMac里的内容可能是脏数据,所以必须用MacValid标志位来保护。
注意:
OID_802_3_CURRENT_ADDRESS返回的是当前生效的 MAC,如果网卡开了 MAC 随机化,这个值会变。想要永久 MAC 得查OID_802_3_PERMANENT_ADDRESS,但那个 OID 不是所有网卡都支持。
4.3 收包路径的过滤逻辑:在 ReceiveNetBufferLists 里做判断
收包路径的改写点比发包更常用,因为很多分析工具需要在包到达协议栈之前就截获它。这个项目在FilterReceiveNetBufferLists里留了一个过滤钩子,你可以根据以太网类型、源 MAC、目的 MAC 来决定是放行、丢弃还是修改。
VOID FilterReceiveNetBufferLists( NDIS_HANDLE FilterModuleContext, PNET_BUFFER_LIST NetBufferLists, NDIS_PORT_NUMBER PortNumber, ULONG NumberOfNetBufferLists, ULONG ReceiveFlags) { PFILTER_ADAPTER_CONTEXT ctx = (PFILTER_ADAPTER_CONTEXT)FilterModuleContext; PNET_BUFFER_LIST currentNbl = NetBufferLists; PNET_BUFFER_LIST dropList = NULL; while (currentNbl != NULL) { PNET_BUFFER nb = NET_BUFFER_LIST_FIRST_NB(currentNbl); PUCHAR frame = (PUCHAR)NdisGetDataBuffer(nb, 14, NULL, 1, 0); if (frame) { USHORT etherType = (frame[12] << 8) | frame[13]; // 示例:丢弃所有 ARP 包(实际项目按需改) if (etherType == 0x0806) { // 标记为丢弃,从链表中摘除 // 实际代码里会维护一个 dropList 最后统一释放 } } currentNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); } // 放行的包继续往上指示 NdisFIndicateReceiveNetBufferLists(ctx->FilterHandle, NetBufferLists, PortNumber, NumberOfNetBufferLists, ReceiveFlags); }NdisGetDataBuffer用来获取 NB 里的连续数据指针,第二个参数 14 表示只关心前 14 字节(以太网头长度)。ReceiveFlags里如果带了NDIS_RECEIVE_FLAGS_RESOURCES,表示 NDIS 在调用你之后会立即回收 NBL,这时候你不能异步处理,必须同步完成。这个项目里所有处理都是同步的,所以没这个问题。如果要异步处理,得先调NdisCopyNetBufferList拷贝一份再慢慢处理。
5. 避坑与排查:蓝屏、绑定失败、MAC 读不到怎么办
5.1 加载即蓝屏:IRQL 与分页内存的冲突
现象:驱动用netcfg安装后,网卡一启用就蓝屏,错误码IRQL_NOT_LESS_OR_EQUAL。原因:在FilterSendNetBufferLists或FilterReceiveNetBufferLists里调用了只能在 PASSIVE_LEVEL 用的函数,比如NdisOidRequest(同步版)或者访问了分页内存。这两个回调默认在 DISPATCH_LEVEL 执行。解决:把所有 OID 请求改成NdisFOidRequest异步版,所有内存分配用NdisAllocateMemoryWithTagPriority并确保不访问分页池。如果必须在 PASSIVE_LEVEL 做事,用NdisQueueWorkItem丢到工作队列里。
5.2 网卡属性里看不到驱动:inf 的 FilterMediaTypes 不匹配
现象:netcfg安装返回成功,但网卡属性里没有出现 filter 驱动。原因:inf 里FilterMediaTypes写的是ethernet,但你当前网卡是无线网卡,类型是native802_11,不匹配。解决:把FilterMediaTypes改成ethernet, native802_11或者直接留空表示所有类型。改完 inf 后必须先用netcfg -u 服务名卸载,再重新安装,否则缓存不会更新。
5.3 MAC 地址读出来全是 0:OID 完成回调没等就用了
现象:FilterAttach里调了查询函数,但后面发自定义包时源 MAC 填的是全 0。原因:NdisFOidRequest是异步的,FilterAttach返回时查询还没完成,CurrentMac还是初始的 0。解决:在FilterRestart里再查一次,或者用MacValid标志位判断,没查到之前不发包。更稳的做法是在FilterOidRequestComplete里发一个事件通知,等 MAC 有效后再开放发包接口。
5.4 发包后系统卡死:NBL 释放时机不对
现象:调用SendCustomEthernetFrame后,系统逐渐卡顿,最终死机。原因:在FilterSendNetBufferListsComplete回调里没有正确释放 NBL 和 MDL,导致内存泄漏,非分页池耗尽。解决:发送完成后,NDIS 会调用FilterSendNetBufferListsComplete,在这个回调里用NdisFreeNetBufferList释放 NBL,用IoFreeMdl释放 MDL,用NdisFreeMemory释放数据缓冲区。顺序不能错,先释放 MDL 再释放内存。
5.5 测试签名模式下驱动仍被拦截:Secure Boot 没关
现象:bcdedit /set testsigning on已执行,重启后驱动加载仍然报签名错误。原因:主板 BIOS 里的 Secure Boot 处于开启状态,它会拦截所有测试签名驱动。解决:进 BIOS 关闭 Secure Boot,或者用正式 EV 证书签名。开发阶段关 Secure Boot 是最省事的,但注意关掉之后 BitLocker 可能会要求恢复密钥,提前准备好。
6. 二次开发:把收发能力封装成可复用的内核接口
拿到这份代码后,最自然的改造方向是把“发自定义包”和“查 MAC”封装成两个稳定的内核 API,让上层其他驱动或用户态程序通过 IOCTL 调用。我一般会这么做:在FilterAttach里创建一个设备对象,注册IRP_MJ_DEVICE_CONTROL的处理例程,然后定义两个 IOCTL 码,一个用来触发发包,一个用来读 MAC。用户态用DeviceIoControl传一个结构体下来,内核里解析后调用SendCustomEthernetFrame。
// IOCTL 处理例程片段 NTSTATUS FilterDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); PFILTER_ADAPTER_CONTEXT ctx = (PFILTER_ADAPTER_CONTEXT)DeviceObject->DeviceExtension; NTSTATUS status = STATUS_SUCCESS; ULONG bytesReturned = 0; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_SEND_CUSTOM_FRAME: { // 输入缓冲区里是一个自定义帧描述结构 PCUSTOM_FRAME_DESC desc = (PCUSTOM_FRAME_DESC)Irp->AssociatedIrp.SystemBuffer; if (desc && desc->PayloadLength <= MAX_PAYLOAD) { status = SendCustomEthernetFrame(ctx, desc->DestMac, desc->SourceMac, desc->EtherType, desc->Payload, desc->PayloadLength); } else { status = STATUS_INVALID_PARAMETER; } break; } case IOCTL_GET_MAC_ADDRESS: { if (ctx->MacValid) { RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, ctx->CurrentMac, 6); bytesReturned = 6; } else { status = STATUS_DEVICE_NOT_READY; } break; } default: status = STATUS_INVALID_DEVICE_REQUEST; } Irp->IoStatus.Status = status; Irp->IoStatus.Information = bytesReturned; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; }这段代码的关键点:SystemBuffer是用户态和内核态共享的缓冲区,METHOD_BUFFERED模式下 IOCTL 会自动做拷贝。IOCTL_SEND_CUSTOM_FRAME的输入结构里包含目的 MAC、源 MAC、以太类型和负载,负载长度要设上限,防止用户态传一个超大 buffer 把非分页池打爆。IOCTL_GET_MAC_ADDRESS直接返回 6 字节,如果MacValid为假就返回STATUS_DEVICE_NOT_READY,让用户态重试。
验证方法很简单:写一个用户态控制台程序,用CreateFile打开\\\\.\\YourFilterDeviceName,然后DeviceIoControl发一个 ARP 请求帧出去,同时在另一台机器上用抓包工具看能不能收到。如果收到了,说明发包链路通了;再调IOCTL_GET_MAC_ADDRESS,对比ipconfig /all里的 MAC,一致就说明查询逻辑没问题。
还有一个改造技巧:把过滤规则做成可配置的。现在丢包逻辑是硬编码的,我一般会在设备扩展里加一个规则数组,用户态通过 IOCTL 下发规则,内核里遍历匹配。规则结构可以很简单:{ EtherType, SrcMacMask, DstMacMask, Action },Action 支持放行、丢弃、修改。这样不用每次改逻辑都重新编译驱动,调试效率高很多。
从那以后我每次写 NDIS Filter,都强制在FilterAttach里先把 MAC 查询和 NBL 池创建做完,再开放任何发包接口,不然异步时序问题会让你在蓝屏和重启之间反复横跳。希望帮到你。
本文还有配套的精品资源,点击获取