☰
clumsy 网络劣化工具详解:基于 WinDivert 的 Windows 网络故障注入实战指南
2026/10/15 2:33:12 网站建设 项目流程
  • 网络
  • 测试

【免费下载链接】clumsy

clumsy makes your network condition on Windows significantly worse, but in a controlled and interactive manner.

项目地址:https://gitcode.com/gh_mirrors/cl/clumsy
点击查看免费下载

导读

clumsy 是一款运行在 Windows 上的开源网络故障注入(Network Fault Injection)工具,它利用 WinDivert 内核级数据包捕获/注入能力,在系统层面拦截真实网络流量,按需执行延迟、丢包、篡改、限速等操作后再放行,从而"可控地让网络变得更糟"。本指南以 README.md 为骨架,结合 src 目录下的源码实现与 etc/config.txt 过滤器预设,完整讲解其工作原理、过滤规则、八个故障注入模块的参数语义、命令行调用方式以及本地验证方法,帮助你用它追踪坏网络下的怪异 Bug,或评估应用在弱网环境下的表现。

本文所描述的能力、参数范围与行为均以当前仓库(clumsy 0.3,参见 common.h 中CLUMSY_VERSION)的实际源码与配置文件为准。

一、它是什么:让网络"受控地变差"的调试利器

README 用一句话概括了工具定位:"clumsy makes your network condition on Windows significantly worse, but in a managed and interactive manner"——即"以可控且可交互的方式,让 Windows 上的网络状况显著变差"。

其技术路线非常直接:借助 WinDivert 停止流转中的网络数据包(stops living network packets)并将其捕获,按需施加 lag(延迟)/ drop(丢包)/ tamper(篡改)等操作,再把它们发送出去(send them away)。用途也很明确:

  • 追踪与"坏网络"相关的诡异 Bug;
  • 在弱网/劣化连接下评估你的应用。

与常见方案(模拟器、代理、DNS 劫持等)相比,README 特别强调它具备以下特性:

  • 无需安装(No installation):绿色软件,解压即用;
  • 无需代理配置,也无需改动应用代码:不依赖任何代理层;
  • 系统级网络捕获,对任何应用生效:捕获发生在系统网络栈层面,而不是某个进程内部;
  • 离线(本地回环)也能用:例如 localhost 到 localhost 的连接同样可以被捕获和劣化;
  • 应用持续运行的同时,clumsy 可以随时开始/停止:无需重启被测应用;
  • 交互式控制劣化程度:提供足够的可视反馈,让你随时知道当前正在发生什么。

从源码结构看,这一"随时启停"的能力由 src/main.c 的uiStartCb/uiStopCb实现:点击 Start 后调用divertStart()启动 WinDivert 句柄并创建后台线程,点击 Stop 后调用divertStop()关闭句柄、等待线程退出,整个过程被测进程无感知。

二、核心工作原理:从拦截到放行的完整数据通路

2.1 WinDivert:内核级数据包拦截引擎

clumsy 的捕获能力完全依赖 WinDivert(仓库在 external 目录内置了WinDivert-2.2.0-A/B/C三份发行包)。WinDivert 允许用户态程序从网络栈中"拦截"数据包,修改后再重新注入(reinject)。clumsy 在这一层之上构建了完整的数据包处理管线:

  1. 捕获:WinDivertRecv从内核持续读取匹配过滤器的数据包;
  2. 排队:捕获到的包被包装成PacketNode节点,追加进双向链表;
  3. 处理:按模块注册顺序,由各故障注入模块对链表中的包逐个施加操作;
  4. 发送:处理完的包通过WinDivertSend重新注入回网络栈。

这一流程对应 src/divert.c 的divertReadLoop(接收循环)与 src/divert.c 的divertClockLoop(时钟循环):接收线程每捕获一个包就挂到链表上并触发一次"消费步骤";时钟线程则以 40ms 为周期(CLOCK_WAITMS,见 src/divert.c)反复消费链表,确保即使没有新包到达,滞留包也能被及时放行,保持网络响应性。两个线程通过互斥锁(mutex)串行化对链表的访问。

2.2 数据包节点:模块间流转的载体

src/common.h 定义了PacketNode结构:

typedef struct _NODE { char *packet; // 原始包数据 UINT packetLen; // 包长度 WINDIVERT_ADDRESS addr; // WinDivert 地址信息(含方向 Outbound/Inbound) DWORD timestamp; // 时间戳(仅 Lag 模块用到,创建节点时不填) struct _NODE *prev, *next; // 双向链表指针 } PacketNode;

src/packet.c 提供了createNode/freeNode/insertBefore/insertAfter/appendNode/popNode等链表原语,所有模块都通过这套接口搬运数据包。源码注释也坦承"在循环里用 malloc 不是性能最佳方案"(src/packet.c),但对调试工具而言可接受。

2.3 模块调度:严格有序的处理管线

src/main.c 用数组定义了 8 个模块的处理顺序,数组顺序即处理优先级:

Module* modules[MODULE_CNT] = { &lagModule, // 延迟 &dropModule, // 丢包 &throttleModule, // 节流 &dupModule, // 重复 &oodModule, // 乱序 &tamperModule, // 篡改 &resetModule, // TCP RST &bandwidthModule, // 带宽限制 };

每次消费步骤(divertConsumeStep,见 src/divert.c)会遍历所有模块:模块开启且上次未开启时调用其startUp(),模块关闭时调用closeDown()并允许其冲刷缓冲的包;每个模块的process()返回是否有包被处理,用于点亮 GUI 中的状态指示灯。

2.4 方向控制:Inbound / Outbound 语义

每个模块都带 Inbound / Outbound 复选框,由 src/common.h 的checkDirection()统一判断:

static INLINE_FUNCTION BOOL checkDirection(BOOL outboundPacket, short handleInbound, short handleOutbound) { return (handleInbound && !outboundPacket) || (handleOutbound && outboundPacket); }

也就是说,Inbound 勾选 = 处理入站包,Outbound 勾选 = 处理出站包。注意源码与界面注释(src/main.c)都特别强调:捕获 localhost(回环)流量时,过滤器不能包含 inbound 条件,例如想过滤回环 UDP 必须写成udp and outbound而不是udp(详见下文过滤器章节)。

三、过滤器:config.txt 预设与 WinDivert 过滤语言

3.1 过滤器的加载与格式

启动时,clumsy 会从可执行文件同目录下的 etc/config.txt 加载过滤器预设(见 src/main.c 的loadConfig())。文件格式为filter-name:filter-text,每行一条,#开头为注释。加载成功后,预设会填充进 GUI 顶部的 Presets 下拉框,选中某条预设即自动填入过滤器输入框。

提示:构建脚本会在编译后把 etc/config.txt 拷贝到输出目录(见 genie.lua),因此最终随程序分发的是拷贝副本,你可以在其中自定义常用过滤器。

3.2 官方预设逐条解析(完整继承)

配置文件开头明确指出:过滤器语法遵循 WinDivert 过滤语言(Filter Language)。以下为仓库自带预设及语义:

回环(Loopback)流量——注释特别说明:回环包只能用outbound条件过滤:

localhost ipv4 all: outbound and loopback localhost ipv4 tcp: tcp and outbound and loopback localhost ipv4 udp: udp and outbound and loopback

更通用的方向过滤:

all sending packets: outbound # 所有出站包 all receiving packets: inbound # 所有入站包

针对特定 IP 的过滤:

all ipv4 against specific ip: ip.DstAddr == 198.51.100.1 or ip.SrcAddr == 198.51.100.1 tcp ipv4 against specific ip: tcp and (ip.DstAddr == 198.51.100.1 or ip.SrcAddr == 198.51.100.1) udp ipv4 against specific ip: udp and (ip.DstAddr == 198.51.100.1 or ip.SrcAddr == 198.51.100.1)

针对特定端口的过滤(注意同时覆盖 TCP 与 UDP 的源/目的端口):

all ipv4 against port: tcp.DstPort == 12354 or tcp.SrcPort == 12354 or udp.DstPort == 12354 or udp.SrcPort == 12354 tcp ipv4 against port: tcp and (tcp.DstPort == 12354 or tcp.SrcPort == 12354) udp ipv4 against port: udp and (udp.DstPort == 12354 or udp.SrcPort == 12354)

IPv6 支持(注释指出 IPv6 虽受支持但通常不太实用):

ipv6 all: ipv6

自定义预留(被注释掉,供你按需启用):

# http requests ONLY (data transmit on other ports): outbound and tcp.DstPort == 80

3.3 过滤器在源码中的落地

  • 过滤器输入框回调uiFilterTextCb(src/main.c)会在你手动编辑过滤器时取消 Presets 下拉框选中态;
  • uiListSelectCb(src/main.c)在选中某条预设时把对应 filter-text 填入输入框;
  • Start 时,输入框文本原样传给divertStart(filter, buf),最终作为WinDivertOpen的过滤器参数(src/divert.c)。若语法错误,WinDivertOpen返回ERROR_INVALID_PARAMETER,界面提示 "filter syntax error";若打开设备失败(如非管理员运行),则提示错误码并要求以管理员身份运行。

另外,src/main.c 还内置了兜底逻辑:当 config.txt 缺失或内容为空/格式非法时,自动填入一条"回环包"过滤器(outbound and ip.DstAddr >= 127.0.0.1 and ip.DstAddr <= 127.255.255.255),保证程序始终有一个可用的起始过滤器。

四、八大故障注入模块:参数、取值范围与实现原理

以下参数取值范围与默认值均取自各模块源码。所有百分比类参数内部以 [0, 10000] 的整数表示(calcChance见 src/utils.c),界面显示为百分比并在 [0.0, 100.0] 间自动归一。

4.1 Lag —— 网络延迟

  • 参数:Delay(ms),范围 [0, 15000],默认 50ms;Inbound / Outbound 方向开关默认全开。
  • 实现(src/lag.c):按方向筛选符合条件的包,记录入队时间戳(timeGetTime()),放入专用缓冲区;当当前时间 > 时间戳 + lagTime时把包放回发送队列,实现固定时延。缓冲区最多保留 2000 个包(KEEP_AT_MOST),一旦打满会一次性冲刷 800 个(FLUSH_WHEN_FULL)防止无界积压。关闭模块时会冲刷全部滞留包。

4.2 Drop —— 丢包

  • 参数:Chance(%),默认 10.0;方向开关默认全开。
  • 实现(src/drop.c):命中概率且方向匹配的包直接freeNode销毁,即"永久丢失",是模拟无线网络丢包最直接的途径。概率判定由calcChance完成:chance == 10000时必定丢弃,否则以rand() % 10000 < chance判定。

4.3 Throttle —— 节流/突发中断

  • 参数:Timeframe(ms) 范围 [0, 1000](默认 30)、Chance(%)(默认 10.0)、Drop Throttled 开关、方向开关。
  • 实现(src/throttle.c):以概率触发一次"节流窗口"——在 time frame 期间持续把包收进缓冲区(最多 1000 个),窗口结束(时间差超过 frame 或缓冲打满)后,要么统一放行(clearBufPackets),要么若勾选Drop Throttled则统一丢弃(dropBufPackets)。这一功能模拟的是网络短暂中断后流量突发/被丢弃的现象。

4.4 Duplicate —— 数据包重复

  • 参数:Count(副本数)范围 [2, 50],默认 2;Chance(%) 默认 10.0;方向开关。
  • 实现(src/duplicate.c):对命中的包调用createNode克隆count-1份,并通过insertBefore插在原包之前,形成连续的重复包序列。

4.5 Out of order —— 乱序

  • 参数:Chance(%) 默认 10.0;方向开关。
  • 实现(src/ood.c):两种策略——链表只有一个包时,以概率把该包"摘下"暂存(oodPacket),等后续包到来或超过KEEP_TURNS_MAX(10 轮)放弃等待后再放回队首,造成该包相对后包乱序;链表有多个包时,以概率对相邻两个方向匹配的包执行swapNode交换位置。关闭模块时会把暂存的包放回队列,避免丢包。

4.6 Tamper —— 数据篡改

  • 参数:Chance(%) 默认 10.0、Redo Checksum 开关(默认开启);方向开关。
  • 实现(src/tamper.c):先用WinDivertHelperParsePacket解析出 TCP/UDP 载荷(payload),用 8 种循环位模式(0x64, 0x13, 0x88, 0x40, 0x1F, 0xA0, 0xAA, 0x55)对载荷做异或破坏——短包(≤4 字节)整体篡改,长包只篡改中间 1/4 载荷,以避开头部/尾部的校验和区域。若勾选 Redo Checksum,篡改后调用WinDivertHelperCalcChecksums重算校验和(源码注释 FIXME 提示校验和重算仍可能存在问题,实战中如遇异常可尝试关闭该开关)。

4.7 Set TCP RST —— 重置 TCP 连接

  • 参数:Chance(%) 默认 0;RST next packet按钮;方向开关。
  • 实现(src/reset.c):命中后把 TCP 头中的Rst标志位置 1 并重算校验和,模拟连接被对方 RST 强行中断。与其它模块不同,它默认概率为 0,而是提供一个"RST next packet"按钮(resetSetRSTNextButtonCb)——点击后setNextCount递增,下一个方向匹配的 TCP 包无论概率如何都会被置 RST,适合做"按下即中断"的确定性演示。注意它要求包长大于 IP 头 + TCP 头的最小尺寸(TCP_MIN_SIZE),因此只作用于真实 TCP 数据包。

4.8 Bandwidth —— 带宽限制

  • 参数:Limit(KB/s) 范围 [0, 99999],默认 10KB/s;方向开关。
  • 实现(src/bandwidth.c):实现了一个滑动窗口速率统计器(CRateStats,窗口 1000ms),对每个包先计算"当前窗口已累计字节数 + 本包大小",若超过limit * 1024则丢弃该包(即超限即丢,非整形/排队),否则计入累计。limit = 0时理论上会丢弃所有包(源码注释:allow 0 limit which should drop all)。

五、命令行参数模式:脚本化与自动化

clumsy 支持纯命令行驱动,方便放进测试脚本。解析逻辑在 src/utils.c 的parseArgs(),格式为--key value成对出现,值会被写入 IUP 全局变量,随后在 GUI 初始化阶段通过setFromParameter()(src/utils.c)反向应用到各控件并手动触发其回调(因为 IUP 不会自动触发),从而同步到模块参数。

各模块的关键字(即模块shortName)如下:

模块短名可用参数
Laglag--lag on/off(总开关)、--lag-inbound on/off、--lag-outbound on/off、--lag-time 毫秒
Dropdrop--drop on/off、--drop-inbound on/off、--drop-outbound on/off、--drop-chance 百分比
Throttlethrottle--throttle on/off、--throttle-inbound on/off、--throttle-outbound on/off、--throttle-chance 百分比、--throttle-frame 毫秒
Duplicateduplicate--duplicate on/off、--duplicate-inbound on/off、--duplicate-outbound on/off、--duplicate-chance 百分比、--duplicate-count 副本数
Out of orderood--ood on/off、--ood-inbound on/off、--ood-outbound on/off、--ood-chance 百分比
Tampertamper--tamper on/off、--tamper-inbound on/off、--tamper-outbound on/off、--tamper-chance 百分比、--tamper-checksum on/off
Set TCP RSTreset--reset on/off、--reset-inbound on/off、--reset-outbound on/off、--reset-chance 百分比
Bandwidthbandwidth--bandwidth on/off、--bandwidth-inbound on/off、--bandwidth-outbound on/off、--bandwidth-bandwidth KB/s

此外还支持两个全局参数:

  • --filter 过滤器表达式:设置起始过滤器(见 src/main.c);
  • --timeout 秒:程序运行指定秒数后自动退出(src/main.c 与uiTimeoutCb),适合无人值守的自动化测试。

示例:以 20% 概率丢弃 UDP 回环出站包并延迟 200ms,运行 60 秒后退出:

clumsy.exe --drop on --drop-chance 20 --drop-inbound off --lag on --lag-time 200 --lag-inbound off --filter "udp and outbound and loopback" --timeout 60

说明:GUI 模式仍会启动(无界面隐藏机制),且参数化模式启动时会跳过 UAC 弹窗的交互等待(见 src/main.c 传入parameterized给tryElevate)。参数必须严格成对出现,格式错误会直接退出(fail fast)。

六、GUI 交互与状态反馈

主界面(由 IUP 构建,见 src/main.c)分为两部分:

  • Filtering 区:过滤器输入框 + Start/Stop 按钮 + Presets 下拉框,左侧有一个 8x8 的圆点状态图标;
  • Functions 区:8 个模块各占一行,左侧为模块开关(Toggle)与指示灯,右侧为参数控件;未启用时参数控件置灰(uiToggleControls,src/main.c)。

状态灯通过 200ms 定时器(uiTimerCb,src/main.c)刷新,三种颜色语义明确:

  • 灰色圆点:空闲,无数据包被处理;
  • 绿色圆点:该模块最近一次消费步骤中确实处理了数据包(processTriggered置位),或数据包发送成功(SEND_STATUS_SEND);
  • 红色圆点:数据包注入失败(SEND_STATUS_FAIL,例如WinDivertSend失败)。

底部状态栏会显示当前状态,例如 "Started filtering. Enable functionalities to take effect." / "Stopped. To begin again, edit criteria and click Start."。

七、运行前提与注意事项

  1. 必须以管理员权限运行:WinDivert 打开过滤设备需要管理员令牌。src/elevate.c 中的tryElevate()会检测权限(IsRunAsAdmin),不足时通过ShellExecuteEx以runas动词自我提权;用户拒绝 UAC 或提权失败则弹窗并退出。支持的最低系统为 Windows Vista 及以上(IsWindowsVistaOrGreater检查),README 声明支持 Windows 7/8/10,仓库 manifest(etc/clumsy.manifest)声明兼容 Vista/7/8/8.1/10。
  2. 不要混用 32 位与 64 位:check32RunningOn64(src/main.c)检测到"32 位程序运行在 64 位系统"时会弹窗要求改用 64 位版本并退出,因此请选择与操作系统位数匹配的构建。
  3. 单实例限制:checkIsRunning()(src/main.c)通过命名事件Global\CLUMSY_IS_RUNNING_EVENT_NAME防止同时运行多个实例。
  4. 回环流量的 inbound 限制:捕获 localhost 流量时过滤器不能包含 inbound 条件,应写成如udp and outbound and loopback。
  5. inbound 注入的已知局限:源码注释(src/divert.c)指出"入站方向注入成功率偏低",并针对入站 ICMP 包提供了"转为出站重发"的规避逻辑;涉及 inbound 注入异常时这是已知的底层限制而非配置问题。

八、从源码构建

项目使用 GENie)。

  • 依赖库已在仓库内置:WinDivert(external/WinDivert-2.2.0-A 等)与 IUP 3.30(external/iup-3.30_Win64_dll16_lib 等四个变体);
  • 链接目标包括WinDivert、iup、comctl32、Winmm、ws2_32(genie.lua);
  • 编译后自动拷贝 WinDivert 的 dll/sys、iup.dll 与 config.txt 到输出目录(genie.lua),产物即绿色可运行。

Release 版以 WindowedApp 构建(无控制台窗口,日志走OutputDebugString),Debug 版为 ConsoleApp(LOG宏直接 printf,见 src/common.h),便于开发期观察处理过程。

九、自带的本地验证脚本

仓库 scripts 目录提供了基于ncat(端口 9911)的本地验证方案,用于肉眼观察各类劣化效果:

  • scripts/send_udp_nums.py:持续向目标主机发送递增编号的数据包(每包一串-,默认每 100ms 一发;--nosleep为满速;--tcp切换为 TCP),可配合-s调整发送周期;
  • scripts/start_nums_cs.bat:启动 ncat UDP 服务器(ncat -u -l -C localhost 9911)并同时运行编号发送器;
  • scripts/start_nums_cs_tcp.bat:同上,但使用 TCP;
  • scripts/start_nums_cs_nosleep.bat:满速 UDP 版本。

使用方法:分别运行start_nums_cs.bat(发送端)与ncat -u -l -C localhost 9911(接收端),同时启动 clumsy 并应用localhost ipv4 udp: udp and outbound and loopback过滤器,再开启 Lag/Drop 等模块——观察接收端序列号的延迟、缺失、重复或乱序即可直观验证各模块效果。

十、License

clumsy 以 MIT 许可证发布(见 LICENSE),源码可自由查看、修改与分发。

参考文件索引

  • 项目总览:README.md
  • 过滤器预设:etc/config.txt
  • 主程序与 GUI、模块调度:src/main.c
  • WinDivert 封装与数据通路:src/divert.c、src/packet.c
  • 八个功能模块:src/lag.c、src/drop.c、src/throttle.c、src/duplicate.c、src/ood.c、src/tamper.c、src/reset.c、src/bandwidth.c
  • 参数解析与概率判定:src/utils.c
  • 权限提升:src/elevate.c
  • 构建工程描述:genie.lua
  • 本地验证脚本:scripts/send_udp_nums.py、scripts/start_nums_cs.bat、scripts/start_nums_cs_tcp.bat、scripts/start_nums_cs_nosleep.bat
  • 网络
  • 测试

【免费下载链接】clumsy

clumsy makes your network condition on Windows significantly worse, but in a controlled and interactive manner.

项目地址:https://gitcode.com/gh_mirrors/cl/clumsy
点击查看免费下载
上一篇:30分钟让iPhone跑起Minecraft Java版:PojavLauncher iOS上手全记录
下一篇:Wan2.2开源:MoE架构重构视频生成,消费级显卡实现电影级创作

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询