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

百度网络监控工具开源第四弹:evr — 构造 VXLAN 探测

鸟窝 2026-07-01 08:40:21 累计浏览 9 次
本机暂存
<p>这是百度网络监控工具 nettools 开源系列的第四篇。前三篇分别介绍了 bitflip&#x2F;baize(UDP 丢包与改包检测工具和Agent)、lidar(TCP SYN 端口可达性探测),它们解决的都是「服务器之间」「点到点」的探测问题——前提是:探测机和被探测对象,至少有一端在我们手里。</p><p>但有一类设备,我们既无法在它上面装 agent,也没法在它对面的机房里放一台探测机。这就是今天的主角 evr 要解决的困境。</p><p>这也是我更深入的了解网络包的构造,把网络探测玩出花了来,对我的网络编程的功力大增的一个很好的场景。</p><blockquote><p>项目地址:<a href="https://github.com/baidu/nettools">https://github.com/baidu/nettools</a><br>文档:<a href="https://nettools.rpcx.io/">https://nettools.rpcx.io</a></p></blockquote><h2 id="一、evr-探测的困境:探测机进不去客户机房"><a href="#一、evr-探测的困境:探测机进不去客户机房" class="headerlink" title="一、evr 探测的困境:探测机进不去客户机房"></a>一、evr 探测的困境:探测机进不去客户机房</h2><p>先说一个真实的场景。</p><p>百度有大量的云客户,我们提供的 <strong>EVR(Edge Virtual Router,边缘虚拟路由器)</strong> 设备作为客户侧网络接入百度云网络的边界节点。EVR 往上连百度的骨干&#x2F;城域网络,往下连客户自己的虚拟网络(VXLAN overlay)。</p><blockquote><p>EVR - 边缘虚拟路由器,通常用于在虚拟化环境中实现路由功能。EVR 位于网络的边缘,用于连接内部网络和外部网络(如客户机房)。</p></blockquote><p>现在问题来了:我们需要监控「百度网络 → EVR」这一段链路的健康度——有没有丢包、延迟多大、有没有改包。按照前几个工具的套路,我们的方案应该是:</p><ul><li>在 EVR 设备上装个 agent?—— <strong>不行</strong>。EVR 是网络设备&#x2F;客户侧设备,我们没有权限往里塞监控程序。</li><li>在 EVR 对面(客户机房内)放一台探测机,做点到点探测?—— <strong>更不行</strong>。那是客户的机房,正常情况我们不可能在客户的物理环境里申请一台探测机常驻。</li></ul><p>lidar 那一套「发 SYN,靠对端内核 TCP 协议栈自动回 SYN-ACK&#x2F;RST」的思路,在这里也不灵——EVR 不是一台服务器,它不会帮你跑 TCP 协议栈三次握手,或者说不允许我们高频的探测。</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><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> 百度侧 边缘设备</span><br><span class="line">┌──────────┐ ┌──────────────────────┐</span><br><span class="line">│ 探测机 │ ───── ??? ─────► │ EVR 设备 │</span><br><span class="line">│ (我们的) │ │ (装不了 agent) │</span><br><span class="line">└──────────┘ │ 对面也放不了探测机 │</span><br><span class="line"> └──────────────────────┘</span><br></pre></td></tr></table></figure><img src="/2026/06/30/baidu-network-monitor-evr-vxlan-probe/image-20260630042946520.png" class=""><p>困境的本质是:<strong>被探测对象不可控,且它对面也无法部署探测机。</strong> 我们需要一个 <strong>「单边」</strong> 就能完成的探测方案——只在百度侧放一台机器,让 EVR 设备自己「帮我们把包送回来」。</p><p>答案藏在 EVR 设备的工作原理里:它是一个 <strong>VXLAN VTEP(VXLAN Tunnel End Point)</strong>。而 VTEP 有一个非常好用的特性——它会忠实地按照内层 IP 头转发解封后的内层帧。这就给了我们「构造一个会被反射回来的 VXLAN 包」的可能。</p><p>要理解这个技巧,得先看懂 VXLAN 的包结构。</p><span id="more"></span><h2 id="二、VXLAN:把二层帧塞进-UDP-里"><a href="#二、VXLAN:把二层帧塞进-UDP-里" class="headerlink" title="二、VXLAN:把二层帧塞进 UDP 里"></a>二、VXLAN:把二层帧塞进 UDP 里</h2><p>VXLAN(Virtual Extensible LAN,RFC 7348)是数据中心 overlay 网络的事实标准。它要解决的核心问题是:传统 VLAN 只有 12 位 VLAN ID,最多 4096 个二层网络,在大规模多租户云环境里完全不够用。</p><p>VXLAN 的做法简单粗暴又有效:<strong>把一个完整的二层以太网帧,整个塞进一个 UDP 数据报里</strong>,通过三层网络传输。这样原本受限于物理二层域的网络,可以跨越任意三层网络延展,VNI(VXLAN Network Identifier)有 24 位,支持约 1600 万个 overlay 网络。</p><img src="/2026/06/30/baidu-network-monitor-evr-vxlan-probe/image-20260630053211131.png" class=""><p>它的包结构从外到内是这样的:</p><img src="/2026/06/30/baidu-network-monitor-evr-vxlan-probe/vxlan-packet.png" class=""><p>一层层拆开看:</p><table><thead><tr><th>层</th><th>大小</th><th>关键字段</th><th>作用</th></tr></thead><tbody><tr><td>外层 Ethernet</td><td>14 B</td><td>物理 MAC</td><td>underlay 二层转发</td></tr><tr><td>外层 IP</td><td>20 B</td><td>src &#x2F; dst IP</td><td>underlay 三层路由(VTEP 之间)</td></tr><tr><td>外层 UDP</td><td>8 B</td><td><strong>dport &#x3D; 4789</strong></td><td>VXLAN 标准端口,VTEP 据此识别</td></tr><tr><td><strong>VXLAN header</strong></td><td>8 B</td><td>flags + <strong>VNI(24 位)</strong></td><td>标识 overlay 网络</td></tr><tr><td>内层 Ethernet</td><td>14 B</td><td>租户 MAC</td><td>被封装的原始二层帧开始</td></tr><tr><td>内层 IP</td><td>20 B</td><td>内层 src &#x2F; dst</td><td>overlay 里的真实通信地址</td></tr><tr><td>内层 UDP&#x2F;TCP</td><td>8 B</td><td>内层端口</td><td>租户的真实流量</td></tr><tr><td>Payload</td><td>N B</td><td>业务数据</td><td>原始负载</td></tr><tr><td>其中 8 字节的 VXLAN header 结构是:</td><td></td><td></td><td></td></tr></tbody></table><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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"> 0 1 2 3</span><br><span class="line"> 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">|R|R|R|R|I|R|R|R| Reserved |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br><span class="line">| VXLAN Network Identifier (VNI) | Reserved |</span><br><span class="line">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span><br></pre></td></tr></table></figure><p><code>I</code> 位(第 5 位)置 1 表示 VNI 有效,剩下的 24 位就是 VNI。</p><p><strong>VTEP 的工作模型</strong>:它从外层 UDP&#x2F;4789 收到一个 VXLAN 包,剥掉外层 Eth&#x2F;IP&#x2F;UDP 和 VXLAN header,拿到内层的原始以太网帧,然后<strong>按内层 IP 头继续转发</strong>。</p><p>注意最后这句话——「按内层 IP 头继续转发」。这就是 evr 整个设计的命门所在:<strong>如果我把内层 dst IP 填成我自己,VTEP 解封后不就把包转回给我了吗?</strong></p><h2 id="三、evr-工具:让-EVR-自己把包反射回来"><a href="#三、evr-工具:让-EVR-自己把包反射回来" class="headerlink" title="三、evr 工具:让 EVR 自己把包反射回来"></a>三、evr 工具:让 EVR 自己把包反射回来</h2><h3 id="实现原理:自环内层帧-payload-内嵌-EVR-源-IP"><a href="#实现原理:自环内层帧-payload-内嵌-EVR-源-IP" class="headerlink" title="实现原理:自环内层帧 + payload 内嵌 EVR 源 IP"></a>实现原理:自环内层帧 + payload 内嵌 EVR 源 IP</h3><p>evr 的核心是一个反直觉但极其精简的设计——「<strong>自环内层帧</strong>」。</p><img src="/2026/06/30/baidu-network-monitor-evr-vxlan-probe/image-20260630055337562.png" class=""><p>先看一个绕不开的硬约束:VTEP 是<strong>按内层 IP 头转发</strong>解封后的帧的。所以要让反射的包能回到探测机,<strong>内层 dst IP 就必须填探测机本机</strong>——填别的地址,包就被转到别处去了,根本回不来。这一点没得选。</p><p>真正有选择空间的是<strong>内层 src IP</strong>。最「自然」的想法是:把每个目标 EVR 的源 IP 填进内层 src IP,回包时靠这个 src IP 区分「是哪个目标反射回来的」。多目标时,本机收到一堆 dst&#x3D;本机、src&#x3D;各个 EVR 的回包,按 src IP 分流即可。</p><p>但 evr 偏偏没这么做——它把内层 src IP 也填成了本机(<strong>src &#x3D; dst &#x3D; 本机,自环</strong>)。这样一来,那个本可用来标识目标的内层 src IP 就被「浪费」掉了,回包里全是 <code>src=本机, dst=本机</code>,彼此长得一模一样,没法区分目标了。</p><p>那为什么要主动放弃这个天然的标识位?<strong>这里其实有一个技巧</strong>:<br>当出现故障的时候,我们要使用traceroute的功能,通过设置TTL,回获取内层的回包经过的路径。通过设置src IP为探测机的IP,我们就能够让ICMP的回包发送给探测机,这样就可以把回程的路径都探测出来了。</p><p>evr 的三招合起来是这样:</p><p><strong>第一招:内层 src IP 和 dst IP 都填探测机的本机地址。</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// agent.go:内层 src == 内层 dst == 本机 IP</span></span><br><span class="line">inner, err := codec.EncodeVxlanInner(a.conf.VNI, a.conf.SrcMAC, a.conf.DstMAC,</span><br><span class="line"> p.innerDstIP, p.innerDstIP, <span class="comment">// inner src == inner dst == local IP</span></span><br><span class="line"> newPort, a.conf.InnerDstPort, <span class="type">uint8</span>(a.conf.TOS), a.conf.TTL, payload)</span><br></pre></td></tr></table></figure><p>EVR 解封 VXLAN 后看到内层 dst &#x3D; 本机,自然就把内层帧转回本机。本机的 raw socket 直接收到回包,<strong>不需要在对端起任何 server,也不需要单独申请回包用的 IP&#x2F;端口</strong>。</p><p><strong>第二招:把「真正的目标标识」嵌进 payload。</strong></p><p>既然内层 src&#x2F;dst 都被占用成本机了,那「这个包是探测哪个 EVR 的」靠什么区分?答案是把目标 EVR 的IP 写进 payload 的第 24~28 字节:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// codec/packet.go:EVRCHECK 协议头</span></span><br><span class="line"><span class="comment">// 偏移 0 : magic &quot;EVRCHECK&quot; (8B),校验合法报文</span></span><br><span class="line"><span class="comment">// 偏移 8 : seq (8B) 业务序列号,原子自增</span></span><br><span class="line"><span class="comment">// 偏移 16 : ts (8B) 发送时间戳(纳秒)</span></span><br><span class="line"><span class="comment">// 偏移 24 : evrIP (4B) EVR 源 IP —— 回包据此映射回 target</span></span><br><span class="line"><span class="comment">// 偏移 28+ : salt Salt 填充,用于 bitflip 检测</span></span><br></pre></td></tr></table></figure><img src="/2026/06/30/baidu-network-monitor-evr-vxlan-probe/image-20260630060840628.png" class=""><p>回包时解析 payload 里的这 4 字节,一步定位回目标:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// agent.go:handlePacket 收到回包</span></span><br><span class="line">seq, ts, evrIP := codec.DecodeWithSrcIP(payload)</span><br><span class="line">p := a.peerByEVRSrc[evrIP.String()] <span class="comment">// 直接 O(1) 定位 target</span></span><br></pre></td></tr></table></figure><p>本质上,evr 把「目标标识」从 IP 头搬到了 payload 里,让 EVR 的 VXLAN 反射动作天然变成一次「单源对多目标」的回包匹配。代价仅仅是 4 字节 payload + 解析时多读 4 字节。</p><p><strong>第三招:外层源 IP 可以 spoof。</strong></p><p>evr 用 <code>ipv4.NewRawConn</code> 包了一层(等价于开启 <code>IP_HDRINCL</code>),让内核不再自己生成外层 IP 头,而是原样发出我们手工拼好的外层 IPv4 头。这样外层 src IP 可以填成 <code>mock_src</code>——一个虚假的IP地址。当然这个功能是可选的。</p><p>这有什么好处呢?<br>为了在Evr上识别出来探测包,我们其实是在Evr中做了一些特殊的配置的,使用和客户不同的VNI,识别出『探测机』的源IP,有不同的路由策略。但这也带来的一个问题: 如果探测机有故障,需要切换探测机的时候,需要在Evr上改配置。<br>为线上的网络设备修改配置流程上很复杂,需要评审改配方案、需要等待变更窗口,需要在凌晨的时候操作等。所以不能及时的更改探测机。但是如果我们使用mockIP,那么就可以分分钟的把探测切到新的探测机上,不需要修改Evr的配置。这也是我们的一个技巧。</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><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></pre></td><td class="code"><pre><span class="line"> 发送侧 回程侧</span><br><span class="line">┌────────────────────────────┐ ┌────────────────────────────┐</span><br><span class="line">│ 外层 IP : mock_src → vtepIP │ │ 内层 dst = 本机 │</span><br><span class="line">│ 外层 UDP: srcPort → 4789 │ 反射 │ ↓ 本机 raw socket 收到 │</span><br><span class="line">│ ┌ VXLAN(VNI) ───────────┐ │ ────► │ 解析 payload[24:28] │</span><br><span class="line">│ │ 内层 ETH/IPv4/UDP │ │ │ 得到 EVR 源 IP │</span><br><span class="line">│ │ src=本机, dst=本机 │ │ │ peerByEVRSrc[ip] → target │</span><br><span class="line">│ │ payload: ...+EVR srcIP│ │ │ 累加 stat(丢包/延迟/翻转) │</span><br><span class="line">│ └───────────────────────┘ │ └────────────────────────────┘</span><br><span class="line">└────────────────────────────┘</span><br></pre></td></tr></table></figure><p>整个过程<strong>只在百度侧部署一台 evr</strong>,EVR 设备本身充当了反射器——困境破解。</p><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><p>evr 由 JSON 配置驱动,每个 target 用 <code>vtep#evrSrc[#mockSrc]</code> 三段式表达:</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;evr-probe-1&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;client_addr&quot;</span><span class="punctuation">:</span> <span class="string">&quot;203.0.113.10&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;targets&quot;</span><span class="punctuation">:</span> <span class="string">&quot;198.51.100.96#192.0.2.1#203.0.113.99,198.51.100.97#192.0.2.2#203.0.113.99&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;vni&quot;</span><span class="punctuation">:</span> <span class="number">15990000</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;tos&quot;</span><span class="punctuation">:</span> <span class="number">64</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;ttl&quot;</span><span class="punctuation">:</span> <span class="number">64</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;client_port_range&quot;</span><span class="punctuation">:</span> <span class="string">&quot;63000,63999&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;rate_in_span&quot;</span><span class="punctuation">:</span> <span class="number">2000</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;span&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1s&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;delay&quot;</span><span class="punctuation">:</span> <span class="string">&quot;5s&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;msg_len&quot;</span><span class="punctuation">:</span> <span class="number">1024</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;log_dir&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./log&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;log_max_age_days&quot;</span><span class="punctuation">:</span> <span class="number">3</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>三段式的含义:</p><table><thead><tr><th>段</th><th>必填</th><th>含义</th></tr></thead><tbody><tr><td><code>vtepIP</code></td><td>是</td><td>EVR VTEP 的 IP(外层目的地址)</td></tr><tr><td><code>evrSrcIP</code></td><td>是</td><td>「目标」标识,嵌入 payload 用于回包匹配</td></tr><tr><td><code>mockSrcIP</code></td><td>否</td><td>外层源 IP;为空则用 <code>client_addr</code></td></tr><tr><td>启动:</td><td></td><td></td></tr></tbody></table><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置文件启动(推荐,线上用 systemd 以 root 拉起)</span></span><br><span class="line"><span class="built_in">sudo</span> ./evr -c /etc/evr/evr.json</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> ./evr \</span><br><span class="line"> --client-addr 203.0.113.10 \</span><br><span class="line"> --targets 198.51.100.96#192.0.2.1#203.0.113.99 \</span><br><span class="line"> --rate-in-span 2000 --span 1s --msg-len 1024</span><br><span class="line"></span><br><span class="line"><span class="comment"># CLI 参数会覆盖配置文件里的同名字段(pflag.Visit 模式)</span></span><br><span class="line"><span class="built_in">sudo</span> ./evr -c /etc/evr/evr.json --rate-in-span 5000 --verbose</span><br></pre></td></tr></table></figure><p>evr 需要 <code>sudo</code>(或 <code>CAP_NET_RAW</code>)来创建 raw socket、启用 <code>IP_HDRINCL</code>、设置 IP TOS 并挂载 BPF。它<strong>仅在 Linux 上有意义</strong>,macOS 只能用于编译开发。</p><h2 id="四、evr-中的技巧与高频-FAQ"><a href="#四、evr-中的技巧与高频-FAQ" class="headerlink" title="四、evr 中的技巧与高频 FAQ"></a>四、evr 中的技巧与高频 FAQ</h2><p>evr 看似简单,但藏了几个值得拿出来单独说的工程技巧。</p><h3 id="技巧-1:BPF-内核层过滤,别让无关-UDP-流量打扰"><a href="#技巧-1:BPF-内核层过滤,别让无关-UDP-流量打扰" class="headerlink" title="技巧 1:BPF 内核层过滤,别让无关 UDP 流量打扰"></a>技巧 1:BPF 内核层过滤,别让无关 UDP 流量打扰</h3><p>读 socket 用的是 <code>ip4:udp</code> raw socket,它默认会收到本机<strong>所有</strong> UDP 报文。在万兆网卡、几十万 pps 的探测机上,内核把海量无关包拷到用户态,性能直接崩。</p><p>解法是在内核层用 cBPF 过滤,只放行三个条件同时命中的包:</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></pre></td><td class="code"><pre><span class="line">(1) IPv4 协议号 = UDP (17)</span><br><span class="line">(2) IPv4 TOS = cfg.tos</span><br><span class="line">(3) UDP 目的端口 = inner_dst_port (默认 8972)</span><br></pre></td></tr></table></figure><p>只有这三条都满足才投递到用户态,几乎零开销。而<strong>写 socket</strong> 上则反过来装一个「全丢」的 BPF——阻止内核给这个发送 socket 排队任何回包。</p><h3 id="技巧-2:源端口轮转覆盖-ECMP-多路径"><a href="#技巧-2:源端口轮转覆盖-ECMP-多路径" class="headerlink" title="技巧 2:源端口轮转覆盖 ECMP 多路径"></a>技巧 2:源端口轮转覆盖 ECMP 多路径</h3><p>和 bitflip&#x2F;lidar 一样,evr 也面临多路径覆盖问题。两个固定地址之间,五元组固定则 ECMP 哈希结果不变,永远只走一条链路。evr 通过 <code>client_port_range</code> 配置一段源端口,每发一轮就让 <code>srcPort + 1</code>,在统计意义上覆盖 ECMP 哈希全空间。</p><h3 id="技巧-3:4-种-Salt-模式抓-bitflip"><a href="#技巧-3:4-种-Salt-模式抓-bitflip" class="headerlink" title="技巧 3:4 种 Salt 模式抓 bitflip"></a>技巧 3:4 种 Salt 模式抓 bitflip</h3><p>payload 第 28 字节往后是 Salt 填充,按 <code>seq % 4</code> 在四种模式间轮换,专门用来检测链路上的位翻转:</p><ul><li><code>0xFF</code> 全 1 —— 暴露任何「1 变 0」的翻转</li><li><code>0x00</code> 全 0 —— 暴露任何「0 变 1」的翻转</li><li><code>0x5A</code> 交替位 (01011010) —— 适配 NIC 串行链路的奇偶错误</li><li>互补 <code>0xAAAA / 0x5555</code> —— 专治 <strong>1&#39;s complement 校验和漏检的「互补翻转」</strong>,这是普通 UDP&#x2F;TCP checksum 唯一无法察觉的一类 bitflip</li></ul><p>回包时如果 payload 长度等于发送长度,就和对应 Salt 比对,命中差异即记一条 <code>[client bitflip]</code> 日志。这套 Salt 实现与 baize&#x2F;kuiniu 完全共用。</p><h3 id="高频-FAQ"><a href="#高频-FAQ" class="headerlink" title="高频 FAQ"></a>高频 FAQ</h3><p><strong>Q:mock_src 是怎么生效的?内核为什么不会把它改回去?</strong><br>evr 在写端用 <code>ipv4.NewRawConn(conn)</code> 包了一层,等价于开启 <code>IP_HDRINCL</code>。开启后内核不再生成自己的 IP 头,而是原样发出我们手工拼的外层 IPv4 头,其中 src 就是 mock_src。如果不包这一层,内核会前置一份自己的 IP 头,导致双层 IP 封装且 mock_src 失效。</p><p><strong>Q:<code>rate_in_span</code> 是单 target 速率还是总速率?</strong><br>是<strong>所有 target 的总速率</strong>。比如 12 个 target、<code>rate_in_span=2000/s</code>,每个 target 平均只有约 166 pps。要提高单 target 速率,就减少 target 数量或调大 <code>rate_in_span</code>。</p><p><strong>Q:为什么内层 src 和 dst 都填本机 IP?</strong><br>这样 EVR 反射回来的内层帧 dst 就是本机,本机 raw socket 直接收得到,无需在对端起 server、无需单独申请回包 IP。这是 evr 破解「对面进不去」困境的核心招式。src IP也填写本机IP是为了定位的时候traceroute的需要。</p><p><strong>Q:为什么 evr 必须用 sudo?</strong><br>需要创建 <code>ip4:udp</code> raw socket、启用 <code>IP_HDRINCL</code>、设置 IP TOS&#x2F;DSCP 并挂载 BPF。Linux 上需要 <code>CAP_NET_RAW</code>,最简单就是 sudo 或 systemd 以 root 启动。</p><p><strong>Q:和 baize &#x2F; kuiniu 怎么选?</strong><br>普通业务网络长期监控用 <strong>baize</strong>;AI 训练的 GPU NIC 互联(RoCE)用 <strong>kuiniu</strong>;机房 VXLAN&#x2F;EVR 路径与 EVR 设备本身的探测用 <strong>evr</strong>——关键区别是 evr <strong>不需要在对端起 server</strong>,EVR 设备本身就是反射器。</p><hr><p>evr 把一个看似无解的困境——「探测机进不去客户机房,被探测设备又装不了 agent」——通过对 VXLAN VTEP 反射特性的巧妙利用,变成了一个单边即可完成的探测。这背后是同一套技术栈在不同网络场景下的复用:raw socket 构造报文、BPF 内核过滤、源端口轮转覆盖 ECMP、Salt 检测 bitflip、时间桶统计。</p><p>这只是 nettools 的冰山一角。后续还有网关设备监控、定位工具,以及巨量监控数据的处理方案。</p><p>项目地址:<a href="https://github.com/baidu/nettools">https://github.com/baidu/nettools</a></p><p>欢迎 Star、试用、提 Issue 和 PR。</p>

同分类推荐文章

  1. Go 语言技能:AI 时代的 Go 开发工具链 (2026-06-28 18:00:00)
  2. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  3. Go 实验特性详解 (2026-06-21 10:05:27)

查看更多 后端 文章 →

建议继续学习

  1. Go Reflect 性能 (累计阅读 14,172)
  2. 面向“接口”编程和面向“实现”编程 (累计阅读 13,919)
  3. 自建DNS以防止GFW干扰 (累计阅读 13,148)
  4. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 10,856)
  5. Linux下三种常用的流量监控软件对比 (累计阅读 10,238)
  6. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 8,861)
  7. 关于 SOCKS 代理的远端 DNS 解析 (累计阅读 8,023)
  8. curl检查访问网页返回的状态码 (累计阅读 7,857)
  9. 一种基于长连接的社交游戏服务器程序构架 (累计阅读 7,526)
  10. nicstat 网络流量统计利器 (累计阅读 7,453)