ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于WFP的Windows流量监控与转发系统源码实战解析

基于WFP的Windows流量监控与转发系统源码实战解析 简介一套围绕 Windows 过滤平台WFP的流量转发与监控实现源码包面向具备 C/C 基础、希望在内核态或驱动层做网络数据包拦截、转发与统计的开发者也可用于企业级网络监控系统的二次开发参考。压缩包共 155 个文件主体为 65 个头文件与 52 个 C 源文件另有 6 个 C 文件及 Visual Studio 工程配置等便于直接编译调试或移植。整包仅 444KB代码紧凑、结构清晰尤其适合学习 WFP 框架下驱动拦截、数据包处理、流量统计等关键机制。目前已有 512 人浏览学习可作为入门 WFP 网络监控开发的实用参考资料。通过研读源码可以掌握流量定向转发、异常流量识别、连接级带宽统计等实现思路为自建监控系统提供可落地的代码基础。1. WFP 流量转发与监控这套源码包能直接改出你要的网络监控系统一台 Windows 服务器上想搞 WFP 流量转发和 WFP 流量监控最头疼的不是写不出代码而是不知道在哪一层动手。市面上的网络监控系统要么只做旁路抓包要么只能看整机带宽想定位到进程级、同时把指定流量转走ETW 和性能计数器都使不上劲。这份源码包解决的就是这个中间地带它基于 Windows Filtering Platform 的 callout 机制把「转发」和「监控」做成了两个可拆分的内核模块外壳用户态再配一个下发规则和读取统计的控制端拼起来就是一个能用的流量监控系统。适合手里已有 C/C 和 WDK 基础、想直接改出一套属于自己的网络监控系统的开发者和安全工程师拿回去改参数比从零写快得多。第 1 行说到了这套包的定位下面进入正题先讲为什么选 WFP再讲转发与监控分别怎么落地最后把坑一次性说清。整个源码包按驱动 用户态控制端的结构组织下文所有代码段都可以对照 rar 包里的工程找到对应文件只是命名可能略作简化。2. 为什么是 WFP对比 TDI 与 NDIS Hook把选型账一次算清2.1 三条技术路线的本质差别Windows 上做流量转发和监控绕不开三套框架老旧的 TDI、底层的 NDIS Hook、以及微软主推的 WFP。先给一张选型对照表后面所有代码取舍都建立在这张表上方案工作位置过滤粒度稳定性与维护成本适合场景TDI 过滤驱动传输层入口连接级别拿不到完整数据包内容微软已标记弃用驱动模型老蓝屏黑匣子古董系统维护、老代码兼容NDIS 中间层 / Hook网卡驱动边界能拿到以太网帧粒度最细要自己处理分片、校验和、链路状态协议栈差异大商业防火墙常用但开发周期长WFP callout网络栈各阶段按 Layer 订阅能拿包也能拿连接信息微软官方主推架构清晰签名合规即可稳定运行流量转发、流量监控、访问控制一体WinDivert / WinPcap用户态旁路用户态旁路抓包与注入免驱但需要安装 NPCAP改不了系统连接状态抓包分析、轻量转发工具当时我拿到这份资源包第一眼看到 WFP callout 驱动的目录结构就知道选型是对的它把「看包」和「管连接」分开驱动里不需要自己去维护网卡绑定和链路状态网络栈的各个阶段都开放了挂载点。这是 TDI 给不了的也是 NDIS 中间层开发成本最高的部分。对做流量监控系统来说WFP 把脏活都干了你只需要回答三个问题在哪个 Layer 看、看什么条件、看了之后干什么。2.2 WFP 的四层关键概念Layer、Filter、Callout、InjectionWFP 的模型可以压缩成一句话Filter 决定哪些流量进 CalloutCallout 决定对这些流量做什么Injection 是 Callout 动手改包后的投递通道。Layer 是它们共同的舞台。资源包驱动里实际用到的 Layer 主要有这几个理解它们对后续改参数至关重要FWPM_LAYER_ALE_AUTH_CONNECT_V4TCP/UDP 连接建立时的授权点。这个层能拿到发起连接的进程 ID 和完整五元组是做会话表、转发决策的首选位置。FWPM_LAYER_IP_FORWARD_V4路由转发点也就是系统决定把包从哪个网卡发出去的时机。做网关型转发经常挂在这里。FWPM_LAYER_STREAM_V4TCP 数据面。能拿到重排后的流数据片段适合做流量统计不适合做按包改址。FWPM_LAYER_OUTBOUND_NETWORK_V4 / INBOUND_NETWORK_V4贴近网卡的包级出入口能看到完整的 IP 包但拿不到进程信息。很多人在 WFP 里翻车不是代码写错而是挂错了层。比如想在连接建立时按进程转发却把 Filter 挂到 OUTBOUND_NETWORK 层结果 processId 永远是 0。这套源码包里转发和统计分别挂了不同的层正是这个原因。2.3 这套源码包的模块与工作流解压 rar 后典型的工程布局是一个内核驱动工程.sys负责注册 callout、做转发和统计一个用户态控制端工程.exe负责打开引擎、下发 Filter、读出统计再加一套构建脚本和说明文档。工作流大致是这样控制端启动后通过 Filtering Platform 的用户态 API 把「哪个进程、哪个目标地址要被转发」下发给引擎驱动里的 callout 收到匹配流量后克隆包、改目标地址、注入到指定方向同时另一组 callout 在流层按连接累计字节数写入 per-flow 结构体控制端再通过 DeviceIoControl 周期把统计取走刷新到界面或写日志。这个结构的好处是转发和监控两个功能解耦你可以只保留监控、去掉转发也可以反过来只做转发。后面第 3 章和第 4 章分别拆这两条线第 5 章再把合在一起时的坑讲透。3. 流量转发落地Callout 分类函数里的克隆、改址与注入3.1 驱动入口先注册转发 Callout再看怎么下发规则转发功能的核心是一个注册在 ALE_AUTH_CONNECT_V4 层的 callout。驱动入口先创建注入句柄再注册 callout最后把 callout ID 暴露给用户态让控制端按 ID 绑定 Filter。代码骨架如下NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING regPath) { FWPS_CALLOUT callout {0}; NTSTATUS status; // 1. 创建网络层注入句柄后面所有克隆包的注入都要用它 status FwpsInjectionHandleCreate( AF_INET, FWPS_INJECTION_TYPE_NETWORK, gInjectHandle); if (!NT_SUCCESS(status)) { return status; } // 2. 注册 callout分类函数负责转发决策flowDelete 负责清理会话 callout.flags 0; callout.classifyFn ClassifyRedirect; callout.notifyFn NotifyFn; callout.flowDeleteFn FlowDeleteFn; status FwpsCalloutRegister(driver, callout, gRedirectCalloutId); if (!NT_SUCCESS(status)) { FwpsInjectionHandleDestroy(gInjectHandle); return status; } // 3. 记录 callout key用户态通过 GUID 找到它 gRedirectCalloutKey REDIRECT_CALLOUT_KEY; return STATUS_SUCCESS; }逻辑说明FwpsInjectionHandleCreate 的参数中AF_INET 表示只处理 IPv4FWPS_INJECTION_TYPE_NETWORK 表示注入类型是网络层注入。FwpsCalloutRegister 把 classify、notify、flowDelete 三个回调绑进框架其中 classifyFn 是最核心的它决定了每一个匹配到的包怎么处理。这里的 gInjectHandle 是全局句柄必须在驱动卸载时销毁否则卸载后内核里会挂着无效句柄。参数说明callout.flags 一般保持 0classifyFn 不能为 NULL否则驱动加载后在过滤路径上会直接抛异常。flowDeleteFn 是流结束时的清理回调统计模块会在第 4 章用到它转发模块里主要用于释放节点避免后续内存泄漏。3.2 分类函数里的关键时刻克隆、改址、注入classify 函数的职责是判断当前包是否需要转发如果命中了配置的规则就克隆一份包、修改头部目标地址、用注入 API 把它投递到本机指定端口。注意这里不是拦截原包而是让原包继续走克隆包走转发路径。这样业务连接本身不受影响只是多了一份副本被导到调试服务。void ClassifyRedirect( const FWPS_INCOMING_VALUES* inFixedValues, const FWPS_INCOMING_METADATA_VALUES* inMetaValues, void* layerData, const void* classifyContext, const FWPS_FILTER* filter, UINT64 flowContext, FWPS_CLASSIFY_OUT* classifyOutput) { UINT32 pid 0; UINT32 remoteAddr 0; // 关键先把注入态排除掉否则注入的克隆包会再次触发自己 if (FwpsQueryPacketInjectionState( gInjectHandle, layerData, NULL) ! FWPS_PACKET_NOT_INJECTED) { classifyOutput-actionType FWP_ACTION_PERMIT; return; } // 只有请求了 metadata 字段时才能拿到 PID if (inMetaValues-currentMetadataValues FWPS_METADATA_FIELD_PROCESS_ID) { pid inMetaValues-processId; } remoteAddr inFixedValues-layerFields[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS].value.uint32; // 命中转发规则克隆、改址、注入 if (ShouldRedirect(pid, remoteAddr)) { NET_BUFFER_LIST* nblClone NULL; NTSTATUS status FwpsAllocateCloneNetBufferList( (NET_BUFFER_LIST*)layerData, NULL, NULL, 0, nblClone); if (NT_SUCCESS(status)) { ModifyPacketDestination(nblClone, gRedirectTargetIp, gRedirectTargetPort); FwpsInjectNetworkReceiveAsync( gInjectHandle, NULL, 0, nblClone); } } // 原包永远放行转发不影响正常通信 classifyOutput-actionType FWP_ACTION_PERMIT; }逻辑说明FwpsQueryPacketInjectionState 检查当前包是否由本驱动注入这是防止无限递归的第一道防线。FwpsAllocateCloneNetBufferList 克隆的是 NET_BUFFER_LIST 结构不是 memcpy 整个数据包所以成本可控。ModifyPacketDestination 是本资源的辅助函数它改写 IP 头目的地址和端口并重新计算 IP 校验和与 UDP/TCP 校验和用 FwpsInjectNetworkReceiveAsync 而不是 Send是因为目标地址是本机 127.0.0.1网络层接收注入才能让回环包正常进入本地协议栈。参数说明gRedirectTargetIp 和 gRedirectTargetPort 是编译期写死的默认转发目标实际运行时这些值由用户态通过 DeviceIoControl 动态下发。clone 包注入失败时需要调用 FwpsFreeCloneNetBufferList 释放否则每丢一个包就漏一块非分页内存长时间运行会触发内存压力。3.3 用户态下发规则Filter 才是真正的条件闸门驱动里的 callout 不会自己跑起来必须有用户态在指定 Layer 上挂一条 Filter把流量引到 callout。这套源码包控制端的核心逻辑是打开引擎、开启事务、组装条件、加 Filter、提交事务。HANDLE engine NULL; NTSTATUS status FwpmEngineOpen(NULL, RPC_C_AUTHN_WINNT, NULL, NULL, engine); if (!NT_SUCCESS(status)) return; status FwpmTransactionBegin(engine, NULL); FWPM_FILTER filter {0}; filter.layerKey FWPM_LAYER_ALE_AUTH_CONNECT_V4; filter.displayData.name LRedirect Rule; filter.action.type FWP_ACTION_CALLOUT_TERMINATING; filter.action.calloutKey REDIRECT_CALLOUT_KEY; filter.numFilterConditions 2; filter.filterCondition conditions; // conditions[0]: FWPM_CONDITION_ALE_APP_ID进程路径类型 FWP_BYTE_BLOB // conditions[1]: FWPM_CONDITION_IP_REMOTE_ADDRESS目标地址类型 FWP_UINT32 status FwpmFilterAdd(engine, filter, NULL, ruleId); status FwpmTransactionCommit(engine);逻辑说明layerKey 指定在 ALE_AUTH_CONNECT_V4 层监听FWP_ACTION_CALLOUT_TERMINATING 表示让 callout 的 classify 决定最终动作而不是简单地放行或阻止。conditions 是条件数组第一条用 ALE_APP_ID 匹配进程路径第二条用 IP_REMOTE_ADDRESS 匹配目标网段。两个条件用 AND 语义叠加只有同时命中的流量才进 callout。参数说明WFP 不支持直接用 PID 做过滤条件PID 只在 metadata 里给 callout 读取。想精确匹配某个进程标准做法是把进程全路径转成 FWP_BYTE_BLOB。注意路径大小写必须与实际一致很多规则失效的案例都是路径里反斜杠或大小写不匹配导致的。另外Filter 的优先级由 weight 决定默认权重足够用但如果你同时挂了多条转发规则建议显式设置 filter.weight避免两条规则抢流量时行为不确定。4. 流量监控做实per-flow 计数、进程归属与用户态读取4.1 统计结构体一张散列表管住所有连接流量监控要回答的核心问题是这台机器上哪个进程在跑流量、跑了多少、目标是谁。驱动里最常见做法是维护一张 per-flow 散列表key 是五元组value 是统计结构体。classify 每收到一个流数据片段就查表累加。typedef struct _FLOW_STATS { UINT64 processedBytes; // 累计 payload 字节数 UINT64 lastSeenTicks; // 最后一次活跃时间用于超时清理 UINT32 pid; // 进程 ID UINT32 remoteAddr; // 远端地址 UINT32 remotePort; // 远端端口 } FLOW_STATS;这段结构体是监控系统的地基。processedBytes 记录累计字节数lastSeenTicks 由 KeQueryTickCount 得到用于用户态判断一个连接是否已超时pid 要在流层拿但流层不一定有进程元数据于是资源包做法是在 ALE 层的 callout 里查好 PID 后写入会话表stream 层查表拿到。这种方式比在每个包上都去过滤 metadata 效率高得多。4.2 流层 classify累加计数最怕抢锁统计类 callout 注册在 FWPS_LAYER_STREAM_V4此时 layerData 是 FWPS_STREAM_DATA里面有当前流片段的 dataLength。收到片段后按五元组查表查到就累加。由于 classify 可能同时在多个 CPU 上并发执行累加必须用 Interlocked 系列原子操作不能直接stats-processedBytes len。static FLOW_STATS* GetOrCreateFlowStats( const FWPS_INCOMING_VALUES* inFixedValues, const FWPS_INCOMING_METADATA_VALUES* inMetaValues) { FLOW_STATS* stats NULL; FLOW_KEY key {0}; key.remoteAddr inFixedValues-layerFields[FWPS_FIELD_STREAM_V4_IP_REMOTE_ADDRESS].value.uint32; key.remotePort inFixedValues-layerFields[FWPS_FIELD_STREAM_V4_IP_REMOTE_PORT].value.uint16; key.localPort inFixedValues-layerFields[FWPS_FIELD_STREAM_V4_IP_LOCAL_PORT].value.uint16; stats LookupFlowStats(key); if (stats NULL) { stats ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(FLOW_STATS), FTSW); if (stats ! NULL) { RtlZeroMemory(stats, sizeof(FLOW_STATS)); stats-pid GetPidFromSessionTable(inFixedValues, inMetaValues); InsertFlowStats(key, stats); } } return stats; }逻辑说明LookupFlowStats 用自旋锁保护的散列表查节点查不到就分配新节点并填充 PID。这里把 key 缩小为「远端 IP 两端端口」因为对绝大多数流量监控需求来说协议类型和本机 IP 不会影响统计归属少两个字段能显著降低散列冲突。需要注意统计对象是 TCP 流不是 IP 包所以入参字段用的是 STREAM_V4 层字段名。参数说明ExAllocatePoolWithTag 的 Tag FTSW 是四个可见字符用于驱动卸载时排查内存泄漏。NonPagedPoolNx 在 WDK 10 中推荐替代 NonPagedPool。流结束时的清理动作放在 flowDeleteFn 回调里删除散列节点并释放内存。流层 classify 里累加的逻辑很短但有个隐藏点这里统计的是 payload 字节数不含 IP 头和 TCP 头。如果你想看到「网卡上的真实字节数」要自己在用户态按平均头长折算或者改挂到 OUTBOUND_NETWORK 层统计完整 IP 包长度。两种口径各有拥趸做网络监控系统我一般以流层 payload 为准因为业务流量统计通常关心的是应用层数据量而且不容易被分片干扰。4.3 用户态读取DeviceIoControl 与结构体对齐内核往统计表里写用户态怎么拿这套包采用最直接的 DeviceIoControl 方式控制端周期性调用一次 IOCTL内核把当前所有活跃连接统计拷到 output buffer用户态解析并展示。内核里填充的规则是先拷一个 count再跟着 count 个 FLOW_STATS 结构体。typedef struct _FLOW_STATS_IOCTL_OUT { ULONG count; FLOW_STATS entries[MAX_FLOW_ENTRIES]; } FLOW_STATS_IOCTL_OUT; FLOW_STATS_IOCTL_OUT out {0}; DWORD bytesReturned 0; BOOL ok DeviceIoControl( hDevice, IOCTL_WFP_GET_FLOW_STATS, NULL, 0, out, sizeof(out), bytesReturned, NULL); if (ok bytesReturned sizeof(ULONG)) { for (ULONG i 0; i out.count; i) { FLOW_STATS* s out.entries[i]; printf(pid%u remote%u.%u.%u.%u:%u bytes%llu\n, s-pid, (s-remoteAddr 24) 0xFF, (s-remoteAddr 16) 0xFF, (s-remoteAddr 8) 0xFF, s-remoteAddr 0xFF, s-remotePort, s-processedBytes); } }逻辑说明DeviceIoControl 的 input buffer 传 NULL因为本次只读不写output buffer 要求固定大小。内核驱动里对应的 IRP_MJ_DEVICE_CONTROL 处理函数会先检查 output buffer 长度是否够再填充数据。用户态拿到 count 后逐一格式化输出。这里最实战的经验是结构体对齐。FLOW_STATS 里有 UINT64 字段Windows 上默认 8 字节对齐如果结构体里字段顺序不当内核态和用户态编译器对齐规则不一致拷贝过来 remotePort 可能整体错位。解决方法是把 UINT64 字段放最前面后面全部是 UINT32 和 UINT16并用#pragma pack保持两边一致。看起来是小事但这类字段偏移错误排查起来极费时间。5. WFP 开发避坑签名、注入循环、回环与卸载的五条血泪记录5.1 驱动装不上开始服务后报 577代码毫无问题现象用 sc create 创建服务成功sc start 直接失败系统事件日志里看到驱动程序返回 577或者提示「无法验证此文件数字签名」。在开发机上最典型因为我们用的是 Debug 构建的 .sys。原因64 位 Windows 默认强制加载有 WHQL 签名的驱动Debug 构建的 WFP 驱动没有签名系统拒绝加载。这不是代码问题是签名策略问题。解决开发环境打开测试签名模式后重启然后在管理员的 cmd 里执行 bcdedit生效后再重新加载驱动。bcdedit /set testsigning on shutdown /r /t 0重启后桌面右下角有「测试模式」水印属于正常现象。真正要分发时再做 WHQL 或微软 attestation 签名。不要为了绕过签名去动系统保护机制那也是给自己挖坑。5.2 注入的包又触发自己的 Callout转发风暴和 CPU 拉满现象只转发一条连接结果驱动所在进程 CPU 长期 100%统计计数暴涨到十亿级行为完全失控。原因克隆包注入回网络栈后会再次经过同一条 Filter再一次进入 ClassifyRedirect。如果没有注入态判断就形成「匹配 → 克隆注入 → 再匹配 → 再克隆注入」的无限循环。解决classify 函数第一行就调用 FwpsQueryPacketInjectionState只要不是 FWPS_PACKET_NOT_INJECTED直接放行返回。代码第 3 章已经体现。这条判断是 WFP 开发里最重要的保护没有之一凡是做注入的 callout 都必须先过这关。5.3 转发目标是 127.0.0.1包却消失不见现象把 ModifyPacketDestination 的目标改成 127.0.0.1:8080 后目标服务始终收不到任何数据原包转发正常但克隆包像被丢进黑洞。原因发送方向的注入句柄对应的是出站网络路径而本机回环地址的数据包需要进入本地接收路径。用 FwpsInjectNetworkSendAsync 注入一个目的地址为 127.0.0.1 的包协议栈不认它为本地投递直接丢弃。这是 WFP 初学者最容易犯的错微软文档里其实写得很清楚只是英文文档没人逐句读。解决转发到本机统一用 FwpsInjectNetworkReceiveAsync 做接收注入注入 API 的 semantic 要和目标地址方向匹配。后来我习惯在驱动里封装一个 InjectToLocal 函数内部固定调 Receive 注入从根上避开这个坑。如果你要把包转到另一台机器而不是本机才走 Send 注入路径。5.4 卸载驱动蓝屏问题多半在清理顺序现象驱动安装、转发、监控都正常一卸载立刻蓝屏dump 指向 callout classify 函数地址重启后一切恢复再次卸载再次蓝屏。这是给测试同事留下心理阴影的典型故障。原因驱动卸载时如果还有 flow 上下文没清理或者还有线程正在 classify 内部执行FwpsCalloutUnregister 返回后回调函数地址仍然可能被引用此时 DriverUnload 释放了驱动映像网络栈某个 DPC 里跳进已卸载的内存蓝屏就是必然。解决卸载顺序严格按三步走先在用户态 FwpmFilterDeleteById 删掉所有规则确保没新流量再进 callout再在驱动里 FwpsCalloutUnregister等框架确认回调不再被调用最后 FwpsInjectionHandleDestroy 销毁句柄。flowDeleteFn 里把散列表节点清掉不要在 DriverUnload 里直接 ExFreePool 所有统计节点。顺序写反的驱动蓝屏概率极高。5.5 流量统计翻倍上下行重复计数与注入包干扰现象用网卡计数器校验发现监控系统统计的字节数比真实流量多 30% 到 100%时准时不准没有稳定规律。原因第一个来源是上下行各挂了一层统计 callout同一份 TCP 内容在发送和接收路径各计一次第二个来源是转发模块的克隆包又走回来虽然它被放行但如果统计 callout 没做注入态检查会把克隆包的流量也算进去。解决统计模块同样做注入态过滤并且明确统计口径要流量监控系统的「业务流量」就只统计连接发起方向另一个方向用对端回包计数单独记录要全链路字节数就选 INBOUND/OUTBOUND 其中一个方向统一计。定了口径再做校验数字才能对上。另外sp3 之后的 WFP 上分片重组包的 dataLength 和原始包不同不要试图逐个包还原 payload直接按流层累计最稳。6. 最后一公里把 WFP 转发与监控拼成一个能跑的系统6.1 拼装时先定好三个绑定关系把第 3 章和第 4 章的代码合到一起首先要明确内核与用户态的契约。这张表写清楚后面改起来才不用两头查契约项说明设备名与符号链接控制端 CreateFile 打开的设备路径固定字符串IOCTL 码转发规则设置、统计读取各占一个 IOCTL_CODE用 CTL_CODE 宏定义结构体布局FLOW_STATS、RULE_SETTING 的字段顺序与对齐两边完全一致规则下发的同步语义每次配置变更后控制端做一次 FwpmFilterAdd驱动端不额外缓存拼装时我采用的方式是控制端先打开设备拿到驱动版本号然后下发规则、读统计。顺序不能反过来否则先读统计时内核还没有初始化散列表返回空列表容易误判成采集失败。6.2 验证方法用一条真实连接走完整个链路拿到这套源码包后别急着改功能先把基本链路跑通。我会用一条 curl 命令同时验证转发和监控在控制端配置一个测试进程路径的转发规则目标指向本地 8080 端口然后从该进程发起一次 HTTP 请求同时观察控制端界面。curl.exe http://192.168.10.20/test -o nul正常情况下控制端会看到一个 PID 正在上行且字节数持续增长同时本地 8080 调试服务收到一个目标地址被改写过的请求副本。原请求本身仍然返回成功因为原包没有被拦截。如果请求返回失败说明转发逻辑把原包影响了如果本地 8080 没收到优先检查注入态和注入方向这两条避坑记录。我一般还会配合 PowerShell 的 Get-NetUDPEndpoint 主动确认端口监听状态驱动层面则索要一份运行日志。日志里每命中一条规则就打一行 DbgPrint用 DbgView 观察能大大缩短排查路径。6.3 这份源码包值得改的三个点第一个值得改的是把转发目标从代码写死改成配置表。用 JSON 或注册表存一份规则列表控制端启动时读入并批量下发比每改一次目标就重编一次驱动舒服得多。第二个是用户态读取统计改事件驱动。现有 DeviceIoControl 轮询方式简单可控但频繁调用会浪费 CPU可以改成驱动在统计表变化超过阈值时触发一个通知事件控制端通过 WaitForSingleObject 等待省掉空轮询。第三个是补 IPv6 支持。这套包默认只处理 IPv4条件字段都是 V4 后缀。把 Layer 换成 V6 版本、地址字段改成 16 字节类型后转发和监控可以平滑支持 IPv6 网络改动量集中在一个头文件里是性价比最高的扩展。从那以后我每次拿到 WFP 相关源码包第一件事都是打开 DriverUnload 检查清理顺序然后在每个 classify 第一行确认注入态判断最后再用一条 curl 把转发和统计同时过一遍。这三步走通了这个资源才算真正到手。希望帮到你。本文还有配套的精品资源点击获取
返回列表