Qt6 实现 TCP/UDP/组播 透明中继抓包工具 | 流量录制回放 | 工业设备调试 一个透明 TCP/UDP/组播 中继调试工具不打断生产链路的抓包 录制回放面向工业自动化 / 嵌入式运维工程师。全文基于开源项目 DeviceForgeQt 6 C17MIT里 NetRelayTool 模块的真实实现。支持 TCP/UDP 透明代理 组播Multicast零影响抓收与回灌。一个现场常见的尴尬调设备的人多半遇到过这个场景上位机HMI、组态软件、自研客户端连着一台 PLC 或嵌入式终端跑得好好的突然某个指令不生效、或者偶发丢数据。你想看看两边到底在收发什么——上 Wireshark需要网卡混杂模式或交换机镜像口现场环境常常没有条件抓到的还是裸包要自己剥协议头。在上位机或设备上加日志改代码、重新烧录、重启——打断了正在复现的现场问题可能就不复现了。更麻烦的是等你好不容易抓到一次异常数据想反复重放给设备去定位却没有手段。我想要的其实很简单一个能串在中间、原样转发、顺便把流量录下来、之后还能按原始节奏重放的东西。市面上开源的、带图形界面、还支持 TCP/UDP 录制回放的中继调试工具意外地少于是自己写了一个。核心思路透明中继代理不改任何一端的代码只在数据链路中间插一个透明代理数据生产者(上位机/客户端) ──①原始数据──▶ 工具(监听) ──②原封不动──▶ 真实消费者(PLC/设备) 数据生产者 ◀──④转发───── 工具 ◀──③响应/回控制── 真实消费者 │ ├──▶ 实时 HexASCII 视图 └──▶ 录制成 .nrec 文件 ──▶ 之后按原始时序回放用户只需要把生产者原来连的PLC:502改成连工具:502工具再去连真实的PLC:502。中间的字节一个不改地双向透传生产逻辑照常跑工具只是顺路把流量抄一份。TCP每个连接配一对 socketTCP 是有连接的所以对每个进来的客户端连接工具都建立一条到上游的配对连接两个方向各自readyRead → write。核心就这么点代码真实实现QTcpServerQTcpSocket// 客户端有数据可读 → 原样转发到上游同时旁路抓取voidNetRelayBackend::onTcpClientReadyRead(QTcpSocket*client){TcpPair*pairm_pairByClient.value(client,nullptr);if(!pair)return;QByteArray dataclient-readAll();if(data.isEmpty())return;pair-session.bytesUpdata.size();// 旁路实时视图 录制不改动 data 本身if(m_dataCb)m_dataCb(RelayDirection::Upstream,pair-session.clientAddr,pair-sessionId,data);recordData(RelayDirection::Upstream,pair-sessionId,data);// 转发if(pair-upstreamConnectedpair-upstream){pair-upstream-write(data);}else{pair-pending.append(data);// 上游尚未连上先缓冲有上限保护}}有个容易忽略的坑上游连接是异步的。客户端可能在upstream还没connected时就发来数据所以要先缓冲到pending等connected信号来了再补发。而且缓冲必须有上限——否则上游若长时间连不上一个猛发的客户端能把内存吃光。这里设了 1MB 上限超了就丢最早的数据并告警。反方向上游→客户端对称处理方向标记为Downstream。全部 socket I/O 都在 Qt 主线程事件循环里信号驱动没有手动线程和锁——这也是我选 Qt 而非裸 socket 线程池的原因事件驱动模型让一进一出两个方向 N 个并发连接的状态机简单得多。UDP无连接靠源地址认会话UDP 没有连接概念转发回程是难点上游把响应发回来时工具怎么知道该转给哪个客户端做法是按客户端源地址(ip, port)建会话表每个会话持有一个面向上游的QUdpSocket回程数据从这个 socket 收到后查会话表路由回对应客户端。会话还带空闲超时5 分钟没流量就回收避免长时间运行会话表无限膨胀。组播额外订阅者,零影响抓雷达组播和单播 UDP 的中继逻辑完全不同。组播是订阅制——数据源往组播组地址如239.1.2.3:5000推数据,所有加入该组的接收者都能收到。关键洞察是:工具只是作为一个额外的订阅者加入组播组,对数据源和现有消费者完全无感知——你在调的雷达继续发,显控继续收,工具在旁边悄无声息地抄一份落盘。实现上有一个整个功能最要命的一行:bind(AnyIPv4, port, ShareAddress | ReuseAddressHint)。Windows 上ShareAddress被忽略,必须靠ReuseAddressHint才能让多个 socket 共享同一端口。如果只写ShareAddress,工具会独占端口——现有消费者立刻收不到数据,直接毁掉不影响现场这个承诺。这个坑是在 spec 设计阶段查 Qt 文档和 Windows Socket API 行为才确认的,要是实现时才发现,整个功能的定位就崩了。组播回放的设计也比较微妙:默认录制时文件头存了组播地址和端口,回灌时用QUdpSocket::setMulticastInterface指定网卡,按原始时序逐条writeDatagram回去。但回灌原组只能在真实源离线时用——否则录制流和真实数据混叠,消费者收到双份。这个不能硬阻止无法可靠判断源是否在线,靠 UI 提示。录制一个够用就好的自定义二进制格式抓包看是一回事能回放是另一回事。回放要求录制格式必须带方向 相对时间戳否则没法还原节奏。我没有直接用 pcap它要造假的以太网/IP/TCP 头为回放重建反而更重而是定义了一个极简的.nrec格式文件头32 字节: NREC magic 版本 协议(TCP/UDP/Multicast) 起始时间戳 保留 记录条目变长: 方向 会话号 相对时间戳(ms) 长度 原始字节写入端就是顺序 appendQDataStream小端// 文件头组播时 protocol2, reserved 区存 IPv4 组地址和端口m_stream-writeRawData(nrec::kMagic,4);// NREC*m_streamquint16(nrec::kVersion);// 版本quint8 protoByte(protoRelayProtocol::Tcp)?0:(protoRelayProtocol::Udp)?1:2;// 0TCP,1UDP,2Multicast*m_streamprotoByte;*m_streamqint64(startEpochMs);// 起始 epoch// 组播时 reserved 区(16 字节)存 group_port(u16)group_ipv4(u32)TCP/UDP 时为 0// ... 之后每个数据块一条记录方向 会话号 ts_offset_ms 长度 payload有两个细节值得说读取端要防御损坏文件。.nrec会被分享、从磁盘加载属于不可信输入。读的时候校验 magic、版本尤其是长度字段要和文件剩余字节比对再分配——否则一个构造出来的超大 length 会触发一次 GB 级内存分配直接把程序打挂。这个在写单元测试时特意覆盖了坏 magic / 坏版本 / 超长 length / 截断文件都要被拒绝而非崩溃。文件权限。录制内容可能含明文凭证落盘时设为仅所有者可读写导出前二次确认。回放按原始时间间隔重放回放的关键是还原节奏不是把数据一股脑灌出去而是按录制时相邻记录的时间戳差值用QTimer逐条调度尽量复现真实时序也提供尽快模式做压测。默认只重放上行记录到消费者——相当于没有真实生产者时用录制流去驱动设备做回归测试。这里踩过一个真实的 bugTCP 回放最后一条记录时write()后立刻close()导致最后一包还在发送缓冲里就被丢了。修复是完成前等bytesWritten把缓冲排空再收尾——异步 socket 的写完≠发完是个经典陷阱。中继与回放互斥一个 Tool 内中继和回放是互斥的都占用同一套监听/连接资源用一个RelayMode{Idle, Relaying, Replaying}状态机守着。这里有个隐蔽的并发窗口UDP 上游地址若是主机名要异步 DNS 解析解析期间状态还没切到Relaying此时若发起回放就会和中继撞车。修复是解析开始就置位 加isRunning()守卫。异步操作的中间态最容易漏掉互斥判断这类 bug 单测覆盖不到靠代码审查才逮出来。小结整个工具的价值不在于多复杂而在于把不打断现场 双向透传 抓包 录制 回放这条链路凑齐了。几个可复用的经验透明代理用事件驱动Qt signal/slot比线程池简单可靠得多UDP 中继的回程路由靠源地址会话表 空闲超时组播中继靠零影响定位——额外订阅者模式天然不破现有系统端口共享的ShareAddress|ReuseAddressHint双 flag 是 Windows 唯一正确写法要回放就必须录方向 相对时间戳自定义格式比 pcap 更省事加载不可信文件先校验长度上限别信文件里写的数字异步 socket 的收尾要等bytesWritten别 write 完就 close代码在 DeviceForgeMITsrc/tools/NetRelayTool/带单元测试和完整设计文档。这是一个 Qt 6 C17 的工业设备批量运维平台除了网络调试还有 FTP/FTPS 批量部署、Telnet 批量命令、Modbus 测试等模块。欢迎试用、提 Issue、拍砖。如果这篇对你有用项目点个 Star 是最大的鼓励。有工业现场的调试需求或 bug欢迎在 GitHub Issues 交流。