IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

大厂的内部工具居然开源了! 一窥百度物理网络秒级监控定位的秘密

鸟窝 2026-06-21 18:40:38 累计浏览 18 次
本机暂存
<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&#x2F;iperf3 压测工具做探测不行?"></a>为什么 iperf&#x2F;iperf3 压测工具做探测不行?</h2><p>如示意图,我们想覆盖Client到Server之间所有的链路,该怎么办?</p><p>理想情况下,我们计算出所有链路对应的五元组,每个五元组覆盖一条链路。但实际上做不到。每个网络设备都有自己的哈希算法,不同层级的设备哈希算法还不同,随着端口Up&#x2F;Down路径还会变化。你没办法预先算出一条五元组走哪条路,也就没办法确定用哪些五元组去覆盖所有路径。</p><p>所以经验上我们用10倍链路数量的五元组。即使有哈希不均的情况,10倍的数量也足以让每一条链路都被覆盖到。</p><p>如果Client到Server最多有256条链路,2560条五元组就够了。</p><p>单一几条五元组用iperf&#x2F;iperf3压测还行,但2560条就得启动大量实例,CPU和内存会爆掉,数据处理也很麻烦。</p><p>在物理网络监控下,我们一般不用普通的UDP&#x2F;TCP网络程序,而是用rawsocket构造自定义的IP&#x2F;UDP packet,实现更底层的通讯。好处是单进程就能构造不同的五元组进行探测,资源占用少。</p><p>当然有得必有失。rawsocket虽然解决了任意构造五元组的问题,但pps会急剧下降。正常的UDP程序能做到百万级pps,rawsocket只能到6万左右,再大就可能丢包造成误报,这是rawsocket机制本身决定的。</p><p>还有很多细节的设计和权衡,很多网络故障场景和踩过的坑,在后续工具逐步整理开源的过程中再介绍。</p><p>这一次先开源 bitflip&#x2F;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故障,你看到的现象是&quot;A到B丢包&quot;,但实际故障在回程路径上。定位系统会误判。</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>丢包你至少知道&quot;包没了&quot;。比特翻转不一样,包到了,内容却被悄悄改了。</p><p>这种问题在大规模网络中比想象的常见。我最早接触它是微博的一次故障,据说数据库一个表的表名有一个bit翻转,导致表名变了。到百度后我被告知这是几年一遇的故障,但实际不是,几乎每年都有,其它云厂商也是每年都能遇到。</p><p>交换机内存故障、光模块劣化、信号串扰,都可能导致传输中的某几个bit跳变。TCP&#x2F;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>&#x2F;<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&#x2F;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 &#123;</span><br><span class="line"> <span class="keyword">return</span> []bpf.Instruction&#123;</span><br><span class="line"> bpf.LoadIndirect&#123;Off: <span class="number">9</span>, Size: <span class="number">1</span>&#125;,</span><br><span class="line"> bpf.JumpIf&#123;Cond: bpf.JumpEqual, Val: <span class="type">uint32</span>(<span class="number">17</span>), SkipFalse: <span class="number">4</span>&#125;,</span><br><span class="line"> bpf.LoadIndirect&#123;Off: <span class="number">1</span>, Size: <span class="number">1</span>&#125;,</span><br><span class="line"> bpf.JumpIf&#123;Cond: bpf.JumpEqual, Val: <span class="type">uint32</span>(tos), SkipFalse: <span class="number">4</span>&#125;,</span><br><span class="line"> bpf.LoadAbsolute&#123;Off: <span class="number">22</span>, Size: <span class="number">2</span>&#125;,</span><br><span class="line"> bpf.JumpIf&#123;Cond: bpf.JumpGreaterOrEqual, Val: <span class="type">uint32</span>(minPort), SkipFalse: <span class="number">2</span>&#125;,</span><br><span class="line"> bpf.JumpIf&#123;Cond: bpf.JumpLessOrEqual, Val: <span class="type">uint32</span>(maxPort), SkipFalse: <span class="number">1</span>&#125;,</span><br><span class="line"> bpf.RetConstant&#123;Val: <span class="number">0xffff</span>&#125;,</span><br><span class="line"> bpf.RetConstant&#123;Val: <span class="number">0x0</span>&#125;,</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</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获取的链路拓扑,分析丢包五元组的公共端口&#x2F;设备,就能定位到具体哪台设备、哪个板卡、哪个端口。</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 &amp;&amp; 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 &lt;server_ip&gt;</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 &lt;server_ip&gt; --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 &lt;server_ip&gt; --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&#x2F;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>

同分类推荐文章

  1. Go 实验特性详解 (2026-06-21 10:05:27)
  2. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)
  3. Loop Engineering 实践:我把 RDMA 开发库移植到 Go 语言,花费 239 块钱 (2026-06-17 04:00:24)

查看更多 后端 文章 →

建议继续学习

  1. Linux下三种常用的流量监控软件对比 (累计阅读 10,157)
  2. curl检查访问网页返回的状态码 (累计阅读 7,819)
  3. nicstat 网络流量统计利器 (累计阅读 7,420)
  4. 实时监控Android设备网络封包 (累计阅读 6,530)
  5. 如何提升视频服务质量 (累计阅读 6,297)
  6. ssldump (累计阅读 5,269)
  7. 神探tcpdump第五招 (累计阅读 4,746)
  8. tcpdump匹配http头 (累计阅读 3,707)
  9. dropwatch 网络协议栈丢包检查利器 (累计阅读 3,483)
  10. LINUX网站流量监测工具iftop (累计阅读 2,792)