百度网络监控工具开源第四弹:evr — 构造 VXLAN 探测
本机暂存
<p>这是百度网络监控工具 nettools 开源系列的第四篇。前三篇分别介绍了 bitflip/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 往上连百度的骨干/城域网络,往下连客户自己的虚拟网络(VXLAN overlay)。</p><blockquote><p>EVR - 边缘虚拟路由器,通常用于在虚拟化环境中实现路由功能。EVR 位于网络的边缘,用于连接内部网络和外部网络(如客户机房)。</p></blockquote><p>现在问题来了:我们需要监控「百度网络 → EVR」这一段链路的健康度——有没有丢包、延迟多大、有没有改包。按照前几个工具的套路,我们的方案应该是:</p><ul><li>在 EVR 设备上装个 agent?—— <strong>不行</strong>。EVR 是网络设备/客户侧设备,我们没有权限往里塞监控程序。</li><li>在 EVR 对面(客户机房内)放一台探测机,做点到点探测?—— <strong>更不行</strong>。那是客户的机房,正常情况我们不可能在客户的物理环境里申请一台探测机常驻。</li></ul><p>lidar 那一套「发 SYN,靠对端内核 TCP 协议栈自动回 SYN-ACK/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 / dst IP</td><td>underlay 三层路由(VTEP 之间)</td></tr><tr><td>外层 UDP</td><td>8 B</td><td><strong>dport = 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 / dst</td><td>overlay 里的真实通信地址</td></tr><tr><td>内层 UDP/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/4789 收到一个 VXLAN 包,剥掉外层 Eth/IP/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=本机、src=各个 EVR 的回包,按 src IP 分流即可。</p><p>但 evr 偏偏没这么做——它把内层 src IP 也填成了本机(<strong>src = dst = 本机,自环</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 = 本机,自然就把内层帧转回本机。本机的 raw socket 直接收到回包,<strong>不需要在对端起任何 server,也不需要单独申请回包用的 IP/端口</strong>。</p><p><strong>第二招:把「真正的目标标识」嵌进 payload。</strong></p><p>既然内层 src/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 "EVRCHECK" (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">{</span></span><br><span class="line"> <span class="attr">"id"</span><span class="punctuation">:</span> <span class="string">"evr-probe-1"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"client_addr"</span><span class="punctuation">:</span> <span class="string">"203.0.113.10"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"targets"</span><span class="punctuation">:</span> <span class="string">"198.51.100.96#192.0.2.1#203.0.113.99,198.51.100.97#192.0.2.2#203.0.113.99"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"vni"</span><span class="punctuation">:</span> <span class="number">15990000</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"tos"</span><span class="punctuation">:</span> <span class="number">64</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"ttl"</span><span class="punctuation">:</span> <span class="number">64</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"client_port_range"</span><span class="punctuation">:</span> <span class="string">"63000,63999"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"rate_in_span"</span><span class="punctuation">:</span> <span class="number">2000</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"span"</span><span class="punctuation">:</span> <span class="string">"1s"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"delay"</span><span class="punctuation">:</span> <span class="string">"5s"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"msg_len"</span><span class="punctuation">:</span> <span class="number">1024</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"log_dir"</span><span class="punctuation">:</span> <span class="string">"./log"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"log_max_age_days"</span><span class="punctuation">:</span> <span class="number">3</span></span><br><span class="line"><span class="punctuation">}</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/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's complement 校验和漏检的「互补翻转」</strong>,这是普通 UDP/TCP checksum 唯一无法察觉的一类 bitflip</li></ul><p>回包时如果 payload 长度等于发送长度,就和对应 Salt 比对,命中差异即记一条 <code>[client bitflip]</code> 日志。这套 Salt 实现与 baize/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/DSCP 并挂载 BPF。Linux 上需要 <code>CAP_NET_RAW</code>,最简单就是 sudo 或 systemd 以 root 启动。</p><p><strong>Q:和 baize / kuiniu 怎么选?</strong><br>普通业务网络长期监控用 <strong>baize</strong>;AI 训练的 GPU NIC 互联(RoCE)用 <strong>kuiniu</strong>;机房 VXLAN/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>
同分类推荐文章
- Go 语言技能:AI 时代的 Go 开发工具链 (2026-06-28 18:00:00)
- 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
- Go 实验特性详解 (2026-06-21 10:05:27)
建议继续学习
- Go Reflect 性能 (累计阅读 14,172)
- 面向“接口”编程和面向“实现”编程 (累计阅读 13,919)
- 自建DNS以防止GFW干扰 (累计阅读 13,148)
- 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 10,856)
- Linux下三种常用的流量监控软件对比 (累计阅读 10,238)
- 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 8,861)
- 关于 SOCKS 代理的远端 DNS 解析 (累计阅读 8,023)
- curl检查访问网页返回的状态码 (累计阅读 7,857)
- 一种基于长连接的社交游戏服务器程序构架 (累计阅读 7,526)
- nicstat 网络流量统计利器 (累计阅读 7,453)