
简介面向Windows 2000/XP防火墙开发的源码资料包适合从事早期Windows内核网络驱动开发、安全软件逆向或驱动过滤机制学习的中高级开发者。资料围绕防火墙内核驱动实现展开包含两套独立的源码工程分别对应IP过滤驱动与防火墙钩子框架可帮助读者理解Windows 2000/XP系统下的数据包拦截、过滤规则挂载、驱动与应用程序通信等核心机制。包内另有CHM格式使用说明、HTML入门说明及文本说明便于对照源码梳理编译环境配置与调试流程。资源共5个文件以两个zip源码包为主辅以chm、htm、txt文档整体仅122KB结构紧凑。已有73人学习下载可满足快速获取经典防火墙驱动示例、研究旧版Windows平台网络安全实现的需求。1. Windows 2000/XP防火墙开发真正的起点是选对拦截面一台刚装完 Windows 2000 的系统插上网线半小时后跑起来一个陌生进程防火墙日志里跳出一行行外联记录——这是那个年代最典型的防火墙使用场景。开发 Windows 2000/XP 下的防火墙真正决定成败的不是界面而是把拦截做在哪一层。用户态 Hook 和 Winsock 层的方案实现快、却被木马轻松绕过内核态的 TDI 过滤驱动和 NDIS 中间层驱动才是当年个人防火墙的主流答案其中 TDI 过滤驱动可以直接拿到发起连接的进程恰好命中“防木马外联”这个最大诉求。这篇文章按我实际做过的路径讲清选型、最小驱动工程、规则引擎和进程绑定再把那些让你蓝屏到怀疑人生的坑过一遍。适合要维护老平台安全软件、或想补驱动开发基本功的人。2. 用TDI过滤驱动还是NDIS中间层Windows 2000/XP防火墙的第一道选择题2.1 为什么用户态方案在2000/XP下不靠谱当年想做防火墙脑子里最先冒出来的方案是用户态 API Hook把 ws2_32.dll 里的 connect、send、recv 全部挂钩应用层的网络调用不就都看住了吗确实能看住大部分老实程序但木马根本不走这套路径。那个年代最常见的绕过手段是直接打开 AFD 设备发 IRP绕开 Winsock 层做网络通信用户态 Hook 完全看不到。更麻烦的是系统热修补、杀毒软件冲突、应用自身也挂钩子都会让 Hook 互相踩踏你根本说不清是哪个环节失效。另一个用户态方案是 LSP往 Winsock 目录里塞一个分层服务提供者。所有 Winsock 应用都会经过你的 DLL听上去覆盖面更广但它同样只拦得住 Winsock 路径裸 Socket 绕过依旧存在。最致命的还不是绕过而是 Winsock 目录一旦被搞坏整台机器上不了网用户第一反应就是把你的防火墙卸了。所以做防火墙这件事用户态方案只能作为辅助手段真正的拦截点必须进内核。进了内核之后又面临下一道选择题TDI 过滤驱动和 NDIS 中间层驱动选谁。2.2 TDI过滤驱动的工作位置与原理TDI 全称 Transport Driver Interface夹在 Winsock 和协议栈之间。TCP/IP 协议栈对上层暴露三个设备\Device\Tcpip、\Device\Udp、\Device\RawIp。所有 TCP、UDP、ICMP 的连接请求和收发数据最终都要穿过这三个设备对应的 IRP 路径。TDI 过滤驱动做的事情是创建自己的过滤设备用 IoAttachDevice 把它附加到这三个设备上。附加之后原本直达 Tcpip.sys 的 IRP 会先经过你的过滤设备你在分发例程里看包、做判定、放行或者拒绝。拦截的关键点是 IRP_MJ_INTERNAL_DEVICE_CONTROL连接请求对应 TDI_CONNECT之后还能拦 TDI_SEND、TDI_RECEIVE、TDI_DISCONNECT。这套机制最大的好处是“按进程控制”变成了顺理成章的事IRP 是发起线程下发的进你的过滤设备时线程上下文还在用 IoGetCurrentProcess 就能拿到进程对象再解析出进程名。这意味着你可以写出“允许 IE 访问任意端口禁止其他所有进程外联”这种精确规则这是当年用户态方案做不到的。TDI 过滤驱动因此成了个人防火墙的主流选择开发难度比 NDIS 低一截蓝屏概率也小不少。2.3 NDIS中间层驱动更底层但离进程更远NDIS 中间层驱动的位置更靠下夹在协议栈和网卡驱动之间看到的是以太网帧而不是连接。它能做到的比 TDI 多帧级过滤、改包、虚拟网卡这类方案都得靠 NDIS 中间层才能实现。但代价也摆在明面上帧和进程之间没有任何直接对应关系你想知道一个网络连接是哪个进程发的必须自己维护一张“四元组到进程”的映射表在高并发下用锁串起来维护非常容易出错。NDIS 中间层驱动的调试也比 TDI 痛苦得多。位置太底层一个断点下去整个协议栈都停住现场很难复现开发周期长测试矩阵大对一个人或者小团队来说不太划算。如果你的需求只是“按进程控制外联 端口黑白名单”在 Windows 2000/XP 这个时代拿 NDIS 杀鸡属于用牛刀。2.4 一张决策表与我的默认选择核心需求推荐方案理由按进程控制外联、防木马回连TDI 过滤驱动直接拿到发起进程实现路径最短按端口/协议做黑白名单不关心进程两者皆可建议 TDI开发量小稳定性更好按 MAC、帧类型做细粒度过滤NDIS 中间层TDI 看不到帧位置决定能力需要改包、做虚拟网卡NDIS 中间层只有协议栈之下才能安全改帧团队没有驱动经验、时间紧TDI 用户态配置程序最小闭环能快速上线我的默认选择从来都是 TDI 过滤驱动。标题里这种老平台防火墙绝大多数产品做的都是“进程外联控制 端口黑白名单”TDI 恰好覆盖全部需求。NDIS 留给那些真要碰帧的方案去用。接下来就按 TDI 路线搭一个能跑的最小工程。3. 搭出能跑的最小防火墙TDI钩子、包捕获与用户态控制通道3.1 从DriverEntry开始设备对象、符号链接与IRP分发注册开发环境建议直接用 Windows XP 虚拟机加对应的老 DDK编译目标也放在虚拟机上双机用内核调试器连起来。单机调试蓝屏时排错效率太低这是血泪经验。驱动工程的骨架分三件事创建设备对象、附加到 TDI 设备、注册 IRP 分发例程。下面是 DriverEntry 的核心结构#include ntddk.h #include tdikrnl.h UNICODE_STRING g_DeviceName RTL_CONSTANT_STRING(L\\Device\\MyFirewall); UNICODE_STRING g_DosName RTL_CONSTANT_STRING(L\\DosDevices\\MyFirewall); PDEVICE_OBJECT g_FilterDevice NULL; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status; // 注册分发例程用户态通道走 DEVICE_CONTROL网络包走 INTERNAL_DEVICE_CONTROL DriverObject-DriverUnload MyFirewallUnload; DriverObject-MajorFunction[IRP_MJ_CREATE] MyFirewallDispatchPassThrough; DriverObject-MajorFunction[IRP_MJ_CLOSE] MyFirewallDispatchPassThrough; DriverObject-MajorFunction[IRP_MJ_DEVICE_CONTROL] MyFirewallDispatchControl; DriverObject-MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] MyFirewallDispatchTdi; // 创建控制设备用户态 CreateFile 打开的就是它 status IoCreateDevice(DriverObject, 0, g_DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, g_FilterDevice); if (!NT_SUCCESS(status)) return status; // 符号链接让用户态可以用 \\.\MyFirewall 访问 status IoCreateSymbolicLink(g_DosName, g_DeviceName); if (!NT_SUCCESS(status)) { IoDeleteDevice(g_FilterDevice); return status; } // 附加到三个 TDI 设备顺序不能乱 status AttachToTdiDevice(L\\Device\\Tcpip); if (!NT_SUCCESS(status)) return status; status AttachToTdiDevice(L\\Device\\Udp); if (!NT_SUCCESS(status)) return status; status AttachToTdiDevice(L\\Device\\RawIp); if (!NT_SUCCESS(status)) return status; return STATUS_SUCCESS; }逻辑说明IRP_MJ_DEVICE_CONTROL 是给用户态配置程序用的IRP_MJ_INTERNAL_DEVICE_CONTROL 是 TDI 层面网络请求真正走的通道两者必须分开。AttachToTdiDevice 负责附加顺序按 Tcpip、Udp、RawIp 依次来后文会给实现。参数说明IoCreateDevice 第三个参数必须是 \Device\ 开头的设备名第四个参数 FILE_DEVICE_UNKNOWN 表示通用设备类型第六个 FALSE 表示设备不独占允许 CreateFile 多次打开。IoCreateSymbolicLink 的 \DosDevices\ 前缀在老 DDK 里等价于 \??\用户态看到的路径就是 \\.\MyFirewall。3.2 挂到Tcpip设备栈IoAttachDevice的挂法附加操作是 TDI 过滤驱动里最容易写错的地方。IoAttachDevice 会把你的过滤设备压到目标设备栈的顶上之后所有发给 Tcpip 的 IRP 都会先经过你。PDEVICE_OBJECT g_AttachedDevices[8]; ULONG g_AttachedCount 0; NTSTATUS AttachToTdiDevice(PCWSTR targetName) { NTSTATUS status; UNICODE_STRING target; PDEVICE_OBJECT attached NULL; RtlInitUnicodeString(target, targetName); // 把 g_FilterDevice 附加到 target 设备栈attached 返回栈顶设备 status IoAttachDevice(g_FilterDevice, target, attached); if (NT_SUCCESS(status)) { g_AttachedDevices[g_AttachedCount] attached; } return status; }逻辑说明IoAttachDevice 成功后attached 就是原来栈顶的设备对象也就是 Tcpip.sys 创建的那个。后面 IoCallDriver 放行时必须用 attached而不是 g_FilterDevice 自己这个细节写错就直接蓝屏。参数说明targetName 必须是以 \Device\ 开头的完整设备名RtlInitUnicodeString 在运行时构造 UNICODE_STRING比 RTL_CONSTANT_STRING 更通用。g_AttachedDevices 数组保存所有 attached 设备卸载时要遍历它们逐个 Detach。3.3 在IRP_MJ_INTERNAL_DEVICE_CONTROL里认包附加完成后网络流量会以内部 IOCTL 的形式进到你的分发例程。这里要做的事是按 IoControlCode 认包TDI_CONNECT 是连接请求TDI_SEND 和 TDI_RECEIVE 是数据收发。NTSTATUS MyFirewallDispatchTdi(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION stack IoGetCurrentIrpStackLocation(Irp); ULONG code stack-Parameters.DeviceIoControl.IoControlCode; // 先把当前栈位置拷贝到下一设备放行时 IoCallDriver 需要它 IoCopyCurrentIrpStackLocationToNext(Irp); if (code TDI_CONNECT) { // 连接请求从 Type3InputBuffer 里解析出对端地址和端口 if (ShouldBlockConnection(Irp)) { Irp-IoStatus.Status STATUS_CONNECTION_REFUSED; Irp-IoStatus.Information 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_CONNECTION_REFUSED; } } else if (code TDI_SEND || code TDI_RECEIVE) { // 数据包内容在 Irp-MdlAddress 指向的缓冲区里 if (ShouldBlockData(Irp)) { Irp-IoStatus.Status STATUS_ACCESS_DENIED; Irp-IoStatus.Information 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_ACCESS_DENIED; } } // 放行把 IRP 交给栈顶的 Tcpip 设备 return IoCallDriver(g_AttachedDevices[0], Irp); }逻辑说明拒绝连接时返回 STATUS_CONNECTION_REFUSED上层应用拿到的错误表现是“连接被拒绝”比 ACCESS_DENIED 对老软件更友好。TDI_SEND 的数据不在 SystemBuffer 里而是挂在 IRP 的 MdlAddress 上想过滤内容要从 MDL 里取。参数说明IoCopyCurrentIrpStackLocationToNext 必须在 IoCallDriver 之前调用它是为下一层设备准备栈位置的关键步骤。g_AttachedDevices[0] 是 Tcpip 栈顶设备如果对一个已经 Detach 的设备调用 IoCallDriver会直接触发蓝屏。3.4 用户态下规则DeviceIoControl控制通道驱动侧拦住了还得把规则送进去。用户态程序通过 CreateFile 打开设备再用 DeviceIoControl 下规则。#include windows.h // CTL_CODE 生成 IOCTL设备类型必须与驱动创建时一致 #define MYFW_IOCTL_ADD_RULE \ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // PMYFW_RULE 结构体定义见第4章这里先用类型名占位 int AddRule(HANDLE hDevice, PMYFW_RULE rule) { DWORD bytesReturned 0; BOOL ok DeviceIoControl( hDevice, // CreateFile(\\\\.\\MyFirewall) 返回的句柄 MYFW_IOCTL_ADD_RULE, // 自定义 IOCTL 码 rule, sizeof(MYFW_RULE), // 输入缓冲区规则结构体 NULL, 0, // 输出缓冲区规则下发不需要返回数据 bytesReturned, NULL); // 同步调用无 OVERLAPPED return ok ? 0 : GetLastError(); }逻辑说明METHOD_BUFFERED 意味着系统会把输入缓冲区拷贝到内核的系统缓冲区里驱动在分发例程里通过 Irp-AssociatedIrp.SystemBuffer 读取。规则下发是低频操作这点拷贝开销完全可以接受换来的是不用自己处理 MDL 映射。参数说明CTL_CODE 的第一个参数 FILE_DEVICE_UNKNOWN 必须和 IoCreateDevice 时一致不一致时 DeviceIoControl 会返回 ERROR_INVALID_FUNCTION。Function 码用 0x801避开系统保留的 0x000 到 0x7FF。4. 规则引擎与进程绑定让防火墙按“谁在连”而不是“连哪里”做决定4.1 最小规则数据结构协议、方向、端口、动作规则引擎不用设计得很复杂个人防火墙的规则就是“谁、通过什么协议、往哪里连、动作是什么”。下面是内核侧和用户态共享的结构体定义typedef enum _MYFW_ACTION { MYFW_ACTION_ALLOW 0, // 放行不记日志 MYFW_ACTION_ALLOW_LOG, // 放行并记录 MYFW_ACTION_DENY, // 拒绝不记日志 MYFW_ACTION_DENY_LOG // 拒绝并记录 } MYFW_ACTION; typedef struct _MYFW_RULE { ULONG Flags; // bit0:启用, bit1:仅入站, bit2:仅出站 UCHAR Protocol; // IPPROTO_TCP6 / IPPROTO_UDP17 / 0通配 USHORT LocalPort; // 0通配 USHORT RemotePort; // 0通配 ULONG RemoteIp; // 对端IP0通配 WCHAR ProcessName[16]; // 进程名通配用 L* MYFW_ACTION Action; } MYFW_RULE;逻辑说明ProcessName 用 16 个宽字符是因为老系统里 EPROCESS 的 ImageFileName 最长 15 字节加结束符这个长度够用。Flags 里用位表示方向和启用状态不用单独字段是因为老驱动里结构体越紧凑越好。参数说明RemoteIp 用 0 做通配符两个端口也用 0 做通配。精度从高到低排列判定时先做精确匹配再回退到通配规则。4.2 从IRP里取出进程身份IoGetCurrentProcess与进程名解析在 TDI_CONNECT 的 IRP 里IoGetCurrentProcess 拿到的就是发起连接的进程对象这是 TDI 方案的核心优势。麻烦的是进程名解析Windows XP 里还没有导出 PsGetProcessImageFileName 这类现成接口老驱动通用做法是通过 PsInitialSystemProcess 和 ActiveProcessLinks 遍历进程链表。BOOLEAN GetProcessName(PEPROCESS Process, WCHAR* name, ULONG nameLen) { // XP 没有导出 PsGetProcessImageFileName常见做法是 // 1. 用 PsInitialSystemProcess 作为锚点它在 ntoskrnl 里导出 // 2. 通过内核符号拿到 ActiveProcessLinks 在 EPROCESS 里的偏移 // 3. 从 SystemProcess 开始沿双向链表遍历找到 Process 后读 ImageFileName // 4. 把 ANSI 的 ImageFileName 转成宽字符放进 name // // 不同 Service Pack 的 EPROCESS 偏移会变完整实现前必须校准偏移量 // 这里不写硬编码偏移发布前在 Win2000 和 XP 各测一遍。 return FALSE; }逻辑说明这段代码是整条链路里最需要单独测试的地方。偏移量一旦写错遍历链表会直接访问到非法地址造成蓝屏。我一般把 ActiveProcessLinks 偏移量做成注册表可配置项发布时按系统版本打表省去反复改代码的麻烦。参数说明nameLen 传入 16表示 WCHAR 数组长度。执行时机要注意TDI_CONNECT 大多在 PASSIVE_LEVEL遍历链表是安全的但 TDI_SEND 后续路径的 IRQL 可能升高在 DISPATCH_LEVEL 绝对不能遍历分页链表否则直接死锁或蓝屏。4.3 判定顺序与动作落点放行、拒绝、记录怎么编排规则表里的条目按精度从高到低排列判定时先精确后通配最后落到默认策略。默认策略对个人防火墙来说必须是“拒绝并记录”宁可误拦不可放行。原因很简单防火墙的产品价值在防住未知连接默认放行等于给木马留后门。优先级匹配范围典型规则0进程名协议本地端口远程端口远端IP精确放行指定进程的指定连接1协议端口级通配放行所有进程访问 80/4432全局默认策略拒绝并记录一切未命中连接动作落点上放行就是 IoCallDriver 把 IRP 交给 Tcpip拒绝就是把 IRP 的任务状态改成 STATUS_CONNECTION_REFUSED 然后直接 CompleteRequest记录则是往一个环形缓冲区里写入日志条目用户态定期读取。4.4 一个最小可用的判定函数把上述规则串起来就是一个最朴素的线性判定函数。规则量不大时完全够用量大了再考虑哈希索引MYFW_ACTION EvaluateRule(PEPROCESS Process, UCHAR Protocol, USHORT localPort, USHORT remotePort, ULONG remoteIp) { WCHAR name[16]; // 进程名解析失败时按拒绝处理保证默认安全 if (!GetProcessName(Process, name, 16)) { return MYFW_ACTION_DENY_LOG; } for (ULONG i 0; i g_RuleCount; i) { MYFW_RULE* r g_Rules[i]; if (!(r-Flags 0x01)) continue; // 未启用 if (r-Protocol ! 0 r-Protocol ! Protocol) continue; if (r-LocalPort ! 0 r-LocalPort ! localPort) continue; if (r-RemotePort ! 0 r-RemotePort ! remotePort) continue; if (r-RemoteIp ! 0 r-RemoteIp ! remoteIp) continue; // 进程名通配用 L*精确匹配逐字符比较 if (r-ProcessName[0] ! L* !MyProcessMatch(r-ProcessName, name, 16)) { continue; } return r-Action; } return MYFW_ACTION_DENY_LOG; // 默认拒绝并记录 }逻辑说明逐条过滤的顺序是启用位、协议、端口、远端 IP、进程名越粗的匹配越早跳过减少无效比较。进程名匹配用逐字符函数而不是 wcsicmp是为了不依赖内核导出的宽字符比较函数也更容易控制长度边界。参数说明MyProcessMatch 从两个字符串的第一个字符开始逐位比较遇到一方结束符就停止。EvaluateRule 的四个端口和 IP 参数在 TDI 分发例程里从 Type3InputBuffer 指向的 TRANSPORT_ADDRESS 解析出来后再传入解析逻辑属于分发层。5. Windows 2000/XP防火墙开发常见问题与排查五个翻车现场5.1 驱动启动不了错误31、错误2背后的服务配置现象驱动安装后设备管理器出现黄色感叹号系统提示“服务无法启动”事件日志里跟着一长串 NTSTATUS。原因过滤驱动依赖 Tcpip但注册表服务配置里没写 DependOnService导致系统引导阶段加载顺序不对IoAttachDevice 找不到 \Device\Tcpip 直接失败。另一种常见写法是把 Start 类型设成 SERVICE_DEMAND_START引导时没人拉起驱动自然起不来。解决服务类型写成 SERVICE_KERNEL_DRIVERStart 设为 SERVICE_SYSTEM_STARTDependOnService 必须包含 Tcpip。ImagePath 写成 \SystemRoot\System32\drivers\MyFirewall.sys不要写相对路径。5.2 卸载驱动就蓝屏Detach和DeleteDevice的先后顺序现象卸载驱动时蓝屏代码指向 IoDeleteDevice 或者 IoDetachDevice 附近多个测试机器在同一个位置复现。原因DriverUnload 里先删掉了过滤设备但设备栈上还挂着没处理完的 IRP或者对附加过的设备没有逐个 Detach直接 DeleteDevice栈关系断裂后 IoCallDriver 访问到野指针。解决卸载路径固定顺序是先置一个全局拦截开关告知分发例程不再拦截任何请求再把 g_AttachedDevices 里每个设备依次 IoDetachDevice最后才 IoDeleteDevice。正在卸载的时候不要留任何未决的定时器或工作项否则系统在卸载完成后还会回调你的代码。5.3 规则下发静默失败METHOD_BUFFERED与缓冲区方向的粗心现象用户态 DeviceIoControl 返回成功但驱动侧日志里没有新规则或者干脆返回 ERROR_INVALID_FUNCTION。原因CTL_CODE 里的 DeviceType 和 IoCreateDevice 时不一致系统直接拒绝。另一个常见问题是用了 METHOD_IN_DIRECT系统把用户态缓冲区映射成了 MDL分发例程里却还在读 Irp-AssociatedIrp.SystemBuffer拿到的是空数据。解决统一用 METHOD_BUFFEREDCTL_CODE 的 DeviceType 严格使用 FILE_DEVICE_UNKNOWN。驱动侧必须检查 Parameters.DeviceIoControl.InputBufferLength小于 sizeof(MYFW_RULE) 直接返回 STATUS_BUFFER_TOO_SMALL。规则表更新后加一个计数递增用户态可以对比确认规则确实生效。5.4 32位和64位的待遇完全不同签名强制与测试模式现象同一份驱动在 32 位 XP 上能装上拷到 64 位 XP 上系统直接拒绝加载没有“仍然继续”的选项。原因x64 体系对内核驱动有强制签名要求32 位 XP 里只是弹灰色警告x64 版本是硬性拦截。解决开发调试优先用 32 位 Windows 2000/XP 虚拟机把逻辑全部跑通后再处理 x64 的签名发布流程。别在一开始就在 x64 环境里调逻辑签名问题会干扰你对蓝屏原因的判断。发布前单独建一条 x64 构建任务把签名做成构建流水线的一步。5.5 Win2000与XP的隐藏差异TDI参数语义与RawIp行为现象同一份驱动和规则文件XP 下工作正常换到 Windows 2000 后某些连接拦不住尤其是 ICMP 表现不一致。原因Win2000 和 XP 的 TDI 接口存在细微差异RawIp 设备上的附加行为、TDI_CONNECT 部分参数语义在不同 Service Pack 里有变化直接用 XP 编译的驱动跑在 2000 上出问题并不奇怪。解决在 DriverEntry 里用 RtlGetVersion 或读取注册表 ProductName 做平台分叉为差异点维护一张兼容表。发布前在 2000 和 XP 上各跑一遍最小回归集TCP 外连、UDP、ICMP 每项测一轮测完再放量。6. 跑稳一个Windows 2000/XP防火墙三组回归检查与一个性能技巧6.1 三组回归检查检查项操作预期结果开机自启重启系统确认驱动自动加载规则从注册表恢复日志时间线完整规则无需重新下发网卡禁启用在系统属性里禁用/启用网卡十次进程外联仍被拦截无蓝屏规则热加载循环下发、删除规则 1000 次用户态全部返回成功内存池无持续增长这三组检查里网卡禁启用最容易出事。Tcpip 设备栈在网卡重启后可能被重建附加关系是否还成立直接决定了后续连接能不能拦得住。我习惯在这组检查时把内核调试器挂在旁边一旦蓝屏马上定位。6.2 一个被低估的性能技巧规则哈希索引规则条数超过一百条后线性遍历开始显示成本老 CPU 上尤其明显。常见做法是加一层端口哈希索引把精确端口规则先按端口分桶通配规则仍然走线性表#define MYFW_HASH_BUCKETS 64 #define MYFW_BUCKET_MAX 8 typedef struct _MYFW_BUCKET { ULONG count; MYFW_RULE* rules[MYFW_BUCKET_MAX]; } MYFW_BUCKET; ULONG MyFirewallHashPort(USHORT port) { return (ULONG)port % MYFW_HASH_BUCKETS; }逻辑说明判定函数先按本地端口取哈希桶桶内只放精确端口规则查不到再去线性表里匹配通配规则。哈希后的桶内比较最多 8 次比全表遍历快得多。还有一个抓包验证的注意点TDI 层丢弃包时SYN 包根本没出协议栈底层抓包看不到任何痕迹。这是正常现象不是驱动把包丢了。当年我在这种老平台上调规则最常用的排错手法是在判定函数里对每个决定打一个点用户态每 100 毫秒捞一次哪个点被连续命中问题就在哪里。这个习惯帮我省下了好几个通宵希望你也用得上。希望帮到你。本文还有配套的精品资源点击获取