小包可以通过,稍大的包却卡住;veth 有流量,物理口没有。虚拟设备、二层域、路由、封装与排队需要沿同一条实际路径逐层解释。
| 查阅目标 | 阅读位置 |
| bridge、VLAN 与 bond | 第 1~2 节 |
| 封装尺寸与策略路由 | 第 3~4 节 |
| 排队、namespace 与 TUN/TAP | 第 5~7 节 |
1. Bridge:FDB 不等于路由表
bridge 根据源 MAC 学习端口,查询目的 MAC 对应的 FDB 表项。已知单播转发到相应端口;未知单播、广播和组播按配置采取泛洪、组播侦听等处理。它不根据 IP 最长前缀选择下一跳。
设 VM A、B 接在 br0 两个 TAP 端口,位于同一二层域。A 先通过 ARP/NDP 获得 B 的链路层地址,再发送目的 MAC 为 B 的帧。bridge 的学习与转发不要求 br0 自己有 IP;给 br0 配 IP 是让宿主也参与该三层网络,而不是打开桥功能的必要条件。
二层环路可能引起广播风暴,需要正确拓扑及 STP 等机制。VLAN filtering 打开后,端口成员、PVID 与 tagged/untagged 出口规则一起决定转发域;仅创建 VLAN ID 不代表所有端口都允许该 VLAN。见 bridge 文档。
2. VLAN 与 bonding 不增加同一种能力
VLAN 在共享物理链路上区分逻辑二层域。VLAN 子接口通常使上层看到独立 net_device,硬件 offload 可能把 tag 保存在元数据或由硬件插入/剥除,所以抓包是否看到 tag 取决于位置。
bonding 将多个下层链路组织成一个逻辑接口:
| 模式 | 典型用途 | 限制 |
| active-backup | 链路切换 | 不把单流吞吐叠加成多条链路总和 |
| balance-xor | 按 hash 分流 | 需要正确对端配置,hash 字段影响均衡 |
| 802.3ad | LACP 协商聚合 | 交换机支持、成员条件与 hashing 必须匹配 |
| balance-rr | 按轮转方式分配发送 | 可能导致报文乱序,并非通用优选 |
两条 10 Gbit/s 链路组成 LACP,不代表单个 TCP 连接一定达到 20 Gbit/s;为保序,同一 flow 通常仍映射到一条成员链路。链路检测也不等于端到端服务健康检测。见 bonding 文档。
3. 隧道:先算封装,再配置 MTU
IPIP 将 IP 放入外层 IP;GRE 增加 GRE 头并可承载相应协议;VXLAN 常把二层帧封装到 UDP/IP,VNI 标识逻辑网络。VNI 比 VLAN ID 更大,不代表它提供加密或访问认证。
假设 underlay 的 IPv4 MTU 为 1500,普通 VXLAN 无额外选项:
| 封装内容 | 字节数 |
| 外层 IPv4 | 20 |
| UDP | 8 |
| VXLAN | 8 |
| 内层 Ethernet | 14 |
| 合计 | 50 |
| 留给内层 IP 包 | 1500 − 50 = 1450 |
外层改为 IPv6 基本头时,多用 20 字节,结果约 1430。内层 VLAN 或额外头部还需继续计入。
这是根据明确封装计算的上限,不是所有 VXLAN 的固定 MTU。underlay MTU 按其 IP 载荷边界定义,上述计算没有再次扣除外层以太网头;混用层次会多减或少减。
路径中 MTU 不一致、ICMP Packet Too Big 等被过滤,可能形成小包正常而大包卡住的现象。隧道 key/VNI、远端地址、FDB/控制平面和返回路径也都需正确。WireGuard 等提供加密隧道,但它们与明文 VXLAN 的安全属性不同。见 VXLAN 文档。
把各层开销放回同一长度轴:外层 IP、UDP、VXLAN、内层以太网头与应用数据都占用预算。改变外层 IPv4/IPv6 或 VLAN 后,能容纳的数据上限随之移动。
Dmax=MTUouter−Htunnel−Hinner IP+TCP
MTU 从外层 IP 包起算,H_tunnel 包含外层 IP/UDP/VXLAN 与内层以太网头;外层以太网头不计入 IP MTU。
正在呈现知识画面
每加一层头部,内层还能放多少?VXLAN 的外层 IP、UDP、VXLAN 和内层 Ethernet 都占用 underlay IP MTU;内层 IP/TCP 头还会继续占用内层数据预算。
{"id":"tunnel-mtu-budget","title":"每加一层头部,内层还能放多少?","summary":"VXLAN 的外层 IP、UDP、VXLAN 和内层 Ethernet 都占用 underlay IP MTU;内层 IP/TCP 头还会继续占用内层数据预算。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"mtu\" data-i18n=\"mtu\"></label><output for=\"mtu\" id=\"mtu-value\"></output><input id=\"mtu\" type=\"range\" min=\"1280\" max=\"2000\" value=\"1500\" step=\"10\"></div><div class=\"presets\" role=\"group\" data-i18n-label=\"outer\"><button data-mode=\"4\" data-i18n=\"v4\"></button><button data-mode=\"6\" data-i18n=\"v6\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"vlan\"><span data-i18n=\"vlan\"></span></label><div class=\"control\"><label for=\"payload\" data-i18n=\"payload\"></label><output for=\"payload\" id=\"payload-value\"></output><input id=\"payload\" type=\"range\" min=\"0\" max=\"1950\" value=\"1400\" step=\"10\"></div><p class=\"caption\" data-i18n=\"packet\"></p><div class=\"packet\"><div id=\"packet\"></div><i id=\"mtu-mark\"></i></div><div class=\"axis\"><span>0</span><span>1050</span><span>2100 B</span></div><div class=\"formula\" id=\"parts\"></div><div class=\"metrics\"><div><span data-i18n=\"maxInner\"></span><strong id=\"maxInner\"></strong></div><div><span data-i18n=\"maxData\"></span><strong id=\"maxData\"></strong></div><div><span data-i18n=\"headers\"></span><strong id=\"headers\"></strong></div><div><span data-i18n=\"excess\"></span><strong id=\"excess\"></strong></div></div><div class=\"formula\" id=\"arithmetic\"></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"fit\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"over\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.packet{position:relative;height:40px;background:var(--surface);margin:18px 0}.packet>div{display:flex;height:100%}.packet b{display:block;background:var(--second);border-right:1px solid var(--paper);height:100%;box-sizing:border-box;transition:width .2s}.packet b:last-child{background:var(--accent);border:0}.packet>i{position:absolute;width:2px;top:-9px;bottom:-9px;background:var(--ink);transition:left .2s}.formula{min-height:6em;white-space:pre-line}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let outer=4;function draw(){const mtu=+$('#mtu').value,payload=+$('#payload').value,vlan=$('#vlan').checked?4:0,ip=outer===4?20:40,overhead=ip+8+8+14+vlan,total=overhead+40+payload,parts=[ip,8,8,14+vlan,20,20,payload];set('mtu-value',mtu+' B');set('payload-value',payload+' B');$('#packet').replaceChildren(...parts.map(n=>{const e=document.createElement('b');e.style.width=n/21+'%';return e;}));$('#mtu-mark').style.left=mtu/21+'%';set('parts','outer IP '+ip+' + UDP 8 + VXLAN 8 + Ethernet '+(14+vlan)+'\\ninner IPv4 20 + TCP 20 + data '+payload);set('maxInner',mtu-overhead);set('maxData',mtu-overhead-40);set('headers',overhead);set('excess',Math.max(0,total-mtu));set('arithmetic',mtu+' − '+overhead+' − 20 − 20 = '+(mtu-overhead-40)+' B\\npacket = '+total+' B');set('explanation',t(total>mtu?'over':'fit'));pressed(String(outer));}document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{outer=+e.dataset.mode;draw();});$('#mtu').oninput=$('#payload').oninput=$('#vlan').onchange=draw;draw();","audio":false,"strings":{"excess":"超出 MTU / B","fit":"包长未超出当前 MTU。头部长度只扣一次,并按各自的层次计算。","headers":"外层封装头部 / B","heading":"每加一层头部,内层还能放多少?","intro":"调整 underlay IP MTU、外层 IP 版本和 TCP 数据长度。竖线是 MTU,条带按字节比例组成整个外层 IP 包。","maxData":"TCP 数据上限 / B","maxInner":"内层 IP 上限 / B","mtu":"Underlay IP MTU / B","outer":"外层 IP 版本","over":"包长越过 MTU 标线。若大包持续失败,应检查 PMTU 反馈和实际封装,而不只盯着应用数据长度。","packet":"整包比例尺:头部用金色,TCP 数据用蓝色","payload":"TCP 数据长度 / B","reset":"重置","scope":"普通 VXLAN,无额外选项;内层固定 IPv4 20 B + TCP 20 B,无选项。外层 Ethernet 不计入 IP MTU。超出表示需要 PMTU/分片等处理,不模拟具体策略;IPv6 路由器不会替源端分片。图尺固定 2100 B,条宽不把短头部夸大。","scopeLabel":"模型范围与约定","v4":"IPv4","v6":"IPv6","vlan":"内层 Ethernet 含 VLAN tag(4 B)"}}
4. 策略路由先选规则,再查对应表
路由策略数据库按 rule 的优先级检查源地址、mark、接口等条件,并执行查表等动作。一条 lookup 规则选中的表若未得到终止结果,可能继续后续规则;不是第一次匹配条件就无条件停止。
同一张表内,路由通常优先最长前缀,再按候选项属性选择。10.2.3.9 同时匹配 10.0.0.0/8 与 10.2.0.0/16,若其他条件正常,后者更具体。不能据 IPv4 FIB 使用 trie 就承诺任何负载下严格 O(log n)。
from ipaddress import ip_address, ip_network
address = ip_address('10.2.3.9')
routes = [ip_network(s) for s in ('0.0.0.0/0', '10.0.0.0/8', '10.2.0.0/16')]
best = max((r for r in routes if address in r), key=lambda r: r.prefixlen)
assert str(best) == '10.2.0.0/16'
print(best)
此例只演示一张表的前缀匹配,未模拟 policy rule、ECMP、路由类型及邻居解析。ip route get 也要带上问题相关的源地址、mark 或接口信息,才能接近实际查找条件。参见 ip-rule。
5. qdisc:排队、调度、整形和 AQM
| 功能 | 核心动作 |
| 排队 | 保存暂时不能发送的 skb |
| 调度 | 从队列中选择下一包 |
| 整形 | 延迟发送以控制速率 |
| policing | 对超出条件的包丢弃或采取动作 |
| AQM | 按队列状态提前丢弃或 ECN 标记,反馈拥塞 |
它们可以组合,但不意味着一个接口自动提供全部功能。
fq_codel 结合流队列与 CoDel,CAKE 组合整形、公平和队列管理,HTB 支持分层带宽安排。系统默认 qdisc 受配置和设备影响,不能统一声称 fq_codel 一定是默认。
控制排队位置:本地 AQM 管不到所有远端缓冲
如果真实瓶颈在调制解调器/运营商缓冲区,主机本地队列可能始终很短,本地 AQM 无法直接控制远端队列。把可控整形速率设在适当瓶颈之前,才有机会让排队发生在自己管理的位置,但具体速率需测量链路开销和变化。
ingress/clsact 提供入口过滤位置,并不是普通可长期排队的接收整形队列;需要排队整形时可结合 IFB 等方式。BQL 调节驱动层在途字节,作用又不同于流级拥塞控制与 qdisc AQM。
6. Namespace、veth、TUN/TAP
network namespace 隔离设备列表、路由、端口绑定等网络状态。它共享同一个内核,不自动创建出口、DNS 服务或带宽保障。一个普通 net_device 在某时刻属于一个 namespace,veth pair 的两端可以放入不同 namespace。
veth 提供成对的内核网络端点;TUN/TAP 则让用户态经文件描述符读写网络数据。TUN 提供三层包,TAP 提供二层帧。用户态读 fd 得到内核发往该接口的数据,写 fd 则向内核注入接收数据,方向要按这个视角理解。
数据是否带额外 packet information 或 virtio-net header 取决于 flags,不是永远从第一个字节就是 IP/以太网头。VM 常用 TAP,部分用户态 VPN 使用 TUN;主线内核 WireGuard 自身实现 net_device,不能笼统说所有 WireGuard 都依赖 /dev/net/tun。见 TUN/TAP。
7. 只读查阅完整路径
ip -d link show
ip rule show
ip route show table all
bridge link show
bridge vlan show
tc -s qdisc show
先确认设备和 namespace,再检查二层域、地址与路由,随后确认 MTU、过滤规则与队列。流量出现在 veth 上而物理口没有,不足以直接判定丢包:它可能被本机交付、桥内转发、封装到另一出口,或提前被策略拒绝。
关联阅读:协议栈概述、Netfilter 与 BPF/XDP。