大厂的内部工具居然开源了! 一窥百度物理网络秒级监控定位的秘密
本机暂存
<p>目前顶尖的云服务商都包含百万台服务器、数十甚至上百个机房、上万台网络设备、百万级网络链路。单单一个GPU集群,就有上万卡的级别。对这些网络和服务器的监控,一个分钟级别的故障,可能就是上百万资产的损失。</p><p>这不是一个ping能解决的问题。</p><p>今天,我们将百度物理网络黑盒监控方向的工具集 nettools 开源了(<a href="https://github.com/baidu/nettools%EF%BC%89%EF%BC%8C%E7%AC%AC%E4%B8%80%E6%89%B9%E6%94%BE%E5%87%BA%E7%9A%84%E6%98%AF">https://github.com/baidu/nettools),第一批放出的是</a> bitflip 和 bitflip6,用于检测网络丢包和比特翻转,在百度内部跑了很长时间了。</p><span id="more"></span><p>后续还有更多工具和SDK正在整理中,包括骨干网fullmesh监控、网关设备监控、连接客户内部机房设备的监控、定位工具等等,还有对巨量监控数据的处理。百度物理网络黑盒监控团队积累了一大批经验、产品和工具,后续逐步整理开源出来,欢迎关注。</p><h2 id="为什么不是一个ping?"><a href="#为什么不是一个ping?" class="headerlink" title="为什么不是一个ping?"></a>为什么不是一个ping?</h2><p>在大规模物理网络中,ping几乎没有用。</p><p>下面是一个集群简化的示意图。实际上层级比这更多,同一层级的网络设备数量远远大于图中画的,一个交换机有几十个端口而不是图中的几根连线,一个ToR交换机可能连接几十台服务器……两台服务器之间可走的链路有很多种可能。</p><p>![image-20260602023053877.png<img src="/2026/06/11/baidu-internal-tool-opensourced-network-monitoring/image-20260602023053877.png" class=""></p><p>ICMP协议无法构造五元组。两个固定IP之间,ICMP包走的链路始终相同。你探测了一万次,本质上只在检查同一条路。而真正的物理网络中,一个交换机上有几十个端口,数据包通过ECMP哈希分散到不同链路上。</p><p>问题来了:你怎么知道那条你没探测到的链路,此刻正在丢包?</p><p>答案是构造足够多的UDP。</p><p>UDP可以自由构造五元组(源IP、目的IP、源端口、目的端口、协议),通过端口组合的变化,让探测流量覆盖网络中每一条链路、每一个端口。UDP丢了就是丢了,不像TCP有重传机制会掩盖网络质量问题。</p><p>这是网络探测的一个基本思想:构造足够多的五元组,去覆盖全部可能的链路。</p><h2 id="为什么-iperf-iperf3-压测工具做探测不行?"><a href="#为什么-iperf-iperf3-压测工具做探测不行?" class="headerlink" title="为什么 iperf/iperf3 压测工具做探测不行?"></a>为什么 iperf/iperf3 压测工具做探测不行?</h2><p>如示意图,我们想覆盖Client到Server之间所有的链路,该怎么办?</p><p>理想情况下,我们计算出所有链路对应的五元组,每个五元组覆盖一条链路。但实际上做不到。每个网络设备都有自己的哈希算法,不同层级的设备哈希算法还不同,随着端口Up/Down路径还会变化。你没办法预先算出一条五元组走哪条路,也就没办法确定用哪些五元组去覆盖所有路径。</p><p>所以经验上我们用10倍链路数量的五元组。即使有哈希不均的情况,10倍的数量也足以让每一条链路都被覆盖到。</p><p>如果Client到Server最多有256条链路,2560条五元组就够了。</p><p>单一几条五元组用iperf/iperf3压测还行,但2560条就得启动大量实例,CPU和内存会爆掉,数据处理也很麻烦。</p><p>在物理网络监控下,我们一般不用普通的UDP/TCP网络程序,而是用rawsocket构造自定义的IP/UDP packet,实现更底层的通讯。好处是单进程就能构造不同的五元组进行探测,资源占用少。</p><p>当然有得必有失。rawsocket虽然解决了任意构造五元组的问题,但pps会急剧下降。正常的UDP程序能做到百万级pps,rawsocket只能到6万左右,再大就可能丢包造成误报,这是rawsocket机制本身决定的。</p><p>还有很多细节的设计和权衡,很多网络故障场景和踩过的坑,在后续工具逐步整理开源的过程中再介绍。</p><p>这一次先开源 bitflip/bitflip6 这个丢包改包工具,我们经常用在丢包改包的疑难杂症和实验网测试中。</p><p>先前这个工具是我手搓的代码,现在在AI的加持下很快完成了重构,加上了单向检测功能和IPv6支持。</p><h2 id="丢包检测难在哪?定位。"><a href="#丢包检测难在哪?定位。" class="headerlink" title="丢包检测难在哪?定位。"></a>丢包检测难在哪?定位。</h2><p>检测丢包本身不难,难的是定位。</p><p>传统的往返探测有个问题:回包的路径和来包不同。</p><p>数据包从A到B走的是端口1,但回来时五元组的源目IP、端口互换了,哈希结果完全不同,可能走的是端口7,或者另外一台设备。如果端口7故障,你看到的现象是"A到B丢包",但实际故障在回程路径上。定位系统会误判。</p><p>所以我们需要单向探测。</p><h2 id="bitflip怎么做单向丢包定位"><a href="#bitflip怎么做单向丢包定位" class="headerlink" title="bitflip怎么做单向丢包定位"></a>bitflip怎么做单向丢包定位</h2><p>思路是:让服务端自己就能完成丢包检测和五元组定位,不依赖回包路径。</p><p>每个UDP探测包的头部携带三个字段:</p><ul><li><code>LastSent</code>:上一个时间窗口客户端实际发了多少包</li><li><code>LastSrcPort</code>:上一个窗口的起始源端口</li><li><code>LastDstPort</code>:上一个窗口的起始目的端口</li></ul><p>服务端收到当前窗口的第一个包,就知道上一个窗口客户端发了多少包、端口变化的起点是什么。结合确定性的端口轮转算法(<code>GetNextPorts</code>),服务端可以还原上一个窗口中每一个包的五元组,然后和实际收到的做对比。</p><p>丢了哪些五元组,一目了然。</p><p>不依赖两端时钟同步。服务端收到未知客户端的第一个包就自动注册,不需要预配置。所有信息都编码在协议里,服务端不需要维护客户端的发送状态。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">+----------+----------+-----------+---------------+------------------+------------------+----------+</span><br><span class="line">| Magic(8) | Seq(8) | Ts(8) | LastSent(4) | LastSrcPort(2) | LastDstPort(2) | Salt(N) |</span><br><span class="line">+----------+----------+-----------+---------------+------------------+------------------+----------+</span><br></pre></td></tr></table></figure><p>32字节头部,够了。</p><h2 id="比特翻转:包到了,但数据被改了"><a href="#比特翻转:包到了,但数据被改了" class="headerlink" title="比特翻转:包到了,但数据被改了"></a>比特翻转:包到了,但数据被改了</h2><p>丢包你至少知道"包没了"。比特翻转不一样,包到了,内容却被悄悄改了。</p><p>这种问题在大规模网络中比想象的常见。我最早接触它是微博的一次故障,据说数据库一个表的表名有一个bit翻转,导致表名变了。到百度后我被告知这是几年一遇的故障,但实际不是,几乎每年都有,其它云厂商也是每年都能遇到。</p><p>交换机内存故障、光模块劣化、信号串扰,都可能导致传输中的某几个bit跳变。TCP/IP的checksum理论上能检测到,但有一类互补跳变,比如 <code>0xAAAA</code> 变成 <code>0x5555</code>,checksum的值恰好不变,完全绕过校验。</p><p>![image-20260602030620414.png<img src="/2026/06/11/baidu-internal-tool-opensourced-network-monitoring/image-20260602030620414.png" class=""></p><p>bitflip用4种salt填充模式来覆盖各种跳变场景:</p><table><thead><tr><th>模式</th><th>值</th><th>作用</th></tr></thead><tbody><tr><td>全1</td><td><code>0xFF</code></td><td>检测 1→0 跳变</td></tr><tr><td>全0</td><td><code>0x00</code></td><td>检测 0→1 跳变</td></tr><tr><td>固定</td><td><code>0x5A</code></td><td>检测混合模式跳变</td></tr><tr><td>互补交替</td><td><code>0xAAAA</code>/<code>0x5555</code></td><td>专门检测checksum盲区的互补跳变</td></tr></tbody></table><p>每个包按序列号选择一种模式,服务端用相同模式验证,精确到哪个字节、哪个bit翻转了。</p><p>这个场景非常讨厌。可能一年就遇到一次,遇到了就特别麻烦,影响还大,错误数据被当成正常数据保存了。客户发现还算容易,因为除了能绕过checksum的那部分,大部分坏包会被服务器的checksum校验drop掉,通过采集监控服务器的checksum指标,理论上容易发现(实际还有一些坏包干扰)。</p><p>但定位起来就头疼了。客户报过来说好几个机房有改包现象,到底是哪个机房哪一层设备的哪个板卡?</p><p>这时候用bitflip工具,如果能复现,就可以通过找改包五元组的共同路径,定位到故障的设备和端口。这要靠单向监控,双向的话就像前面说的,有可能误导。</p><h2 id="工程细节:raw-socket-BPF"><a href="#工程细节:raw-socket-BPF" class="headerlink" title="工程细节:raw socket + BPF"></a>工程细节:raw socket + BPF</h2><p>在百度的规模下,每秒要发送数千甚至上万个探测包。普通的UDP socket不够用:</p><ul><li>需要自由构造IP头(设置TOS/DSCP等网络参数)</li><li>需要用极少的socket覆盖上百个端口对</li><li>需要精确控制发包速率</li></ul><p>bitflip客户端用raw socket直接构造IP+UDP包,通过BPF过滤收包,只接收目标端口范围内、特定TOS值的回包。读侧按端口范围切分为最多8个goroutine,每个绑定独立的BPF过滤器。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">portRangeBPF</span><span class="params">(minPort, maxPort, tos <span class="type">int</span>)</span></span> []bpf.Instruction {</span><br><span class="line"> <span class="keyword">return</span> []bpf.Instruction{</span><br><span class="line"> bpf.LoadIndirect{Off: <span class="number">9</span>, Size: <span class="number">1</span>},</span><br><span class="line"> bpf.JumpIf{Cond: bpf.JumpEqual, Val: <span class="type">uint32</span>(<span class="number">17</span>), SkipFalse: <span class="number">4</span>},</span><br><span class="line"> bpf.LoadIndirect{Off: <span class="number">1</span>, Size: <span class="number">1</span>},</span><br><span class="line"> bpf.JumpIf{Cond: bpf.JumpEqual, Val: <span class="type">uint32</span>(tos), SkipFalse: <span class="number">4</span>},</span><br><span class="line"> bpf.LoadAbsolute{Off: <span class="number">22</span>, Size: <span class="number">2</span>},</span><br><span class="line"> bpf.JumpIf{Cond: bpf.JumpGreaterOrEqual, Val: <span class="type">uint32</span>(minPort), SkipFalse: <span class="number">2</span>},</span><br><span class="line"> bpf.JumpIf{Cond: bpf.JumpLessOrEqual, Val: <span class="type">uint32</span>(maxPort), SkipFalse: <span class="number">1</span>},</span><br><span class="line"> bpf.RetConstant{Val: <span class="number">0xffff</span>},</span><br><span class="line"> bpf.RetConstant{Val: <span class="number">0x0</span>},</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="双向对比判断故障方向"><a href="#双向对比判断故障方向" class="headerlink" title="双向对比判断故障方向"></a>双向对比判断故障方向</h2><p>bitflip同时支持客户端和服务端两侧统计。对比两侧丢包:</p><ul><li>仅服务端有丢包:故障在正向路径(Client → Server)</li><li>仅客户端有丢包,服务端正常:故障在回程路径(Server → Client)</li><li>两端都有丢包:需要进一步分析</li></ul><p>结合traceroute获取的链路拓扑,分析丢包五元组的公共端口/设备,就能定位到具体哪台设备、哪个板卡、哪个端口。</p><h2 id="快速体验"><a href="#快速体验" class="headerlink" title="快速体验"></a>快速体验</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 克隆并编译</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/baidu-sys/nettools.git</span><br><span class="line"><span class="built_in">cd</span> nettools && make build</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在远端启动服务端(自动检测IP,自动注册客户端)</span></span><br><span class="line">./bitflip</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在本地启动客户端</span></span><br><span class="line"><span class="built_in">sudo</span> ./bitflip -r client -s <server_ip></span><br><span class="line"></span><br><span class="line"><span class="comment"># 以每秒10000包的速率探测60秒</span></span><br><span class="line"><span class="built_in">sudo</span> ./bitflip -r client -s <server_ip> --rate 10000 --duration 60s</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开启详细模式,丢包时输出具体五元组</span></span><br><span class="line"><span class="built_in">sudo</span> ./bitflip -r client -s <server_ip> --verbose</span><br></pre></td></tr></table></figure><p>IPv6环境使用 <code>bitflip6</code>,用法一致。</p><p>使用手册查看:<a href="https://nettools.rpcx.io/bitflip.html">bitflip使用指南</a></p><h2 id="后续规划"><a href="#后续规划" class="headerlink" title="后续规划"></a>后续规划</h2><p>nettools 目前开源的是 bitflip/bitflip6。后续还有更多工具和SDK在整理中:</p><ul><li>更多探测场景的工具</li><li>通用的网络探测SDK</li><li>定位分析相关的工具</li></ul><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>在百度这种体量的物理网络中做监控和定位,不是写几个socket程序就能搞定的。为什么用UDP不用ICMP和TCP?为什么有时候又采用TCP syn探测?为什么需要单向探测?为什么协议头要带上一个窗口的发送信息?为什么salt要用4种模式?每个选择背后都有对应的故障场景和教训。</p><p>这些东西以前都在内部,现在开源出来了。</p><p>项目地址:<a href="https://github.com/baidu/nettools">https://github.com/baidu/nettools</a></p><p>欢迎Star、试用、提Issue和PR。</p>
同分类推荐文章
- Go 实验特性详解 (2026-06-21 10:05:27)
- amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)
- Loop Engineering 实践:我把 RDMA 开发库移植到 Go 语言,花费 239 块钱 (2026-06-17 04:00:24)
建议继续学习
- Linux下三种常用的流量监控软件对比 (累计阅读 10,157)
- curl检查访问网页返回的状态码 (累计阅读 7,819)
- nicstat 网络流量统计利器 (累计阅读 7,420)
- 实时监控Android设备网络封包 (累计阅读 6,530)
- 如何提升视频服务质量 (累计阅读 6,297)
- ssldump (累计阅读 5,269)
- 神探tcpdump第五招 (累计阅读 4,746)
- tcpdump匹配http头 (累计阅读 3,707)
- dropwatch 网络协议栈丢包检查利器 (累计阅读 3,483)
- LINUX网站流量监测工具iftop (累计阅读 2,792)