你的服务在测试环境跑得好好的,部署到生产环境后,小请求正常,但一旦返回大 JSON(比如 10KB 以上),客户端就随机超时。应用日志没有报错,数据库查询正常,CPU 和内存都不高。你在服务端抓包,发现 TCP 在反复重传某些段——而且总是同一个大小的段。
更诡异的是:你用 curl
从服务器本机请求,一切正常;从同一个子网的机器请求,也正常;只有跨网段的请求才会超时。
这种”大包丢、小包好、跨网段才出问题”的模式,几乎可以确定是 MTU 问题。而 MTU 是以太网帧结构的核心参数。
这篇文章深入以太网的工程细节:帧结构到底长什么样、MTU 和 MSS 的精确关系、PMTUD 为什么会形成黑洞、VLAN 标签怎么影响帧大小、以及 Jumbo Frame 在数据中心的收益和代价。
一、以太网帧结构
以太网(Ethernet)由 Bob Metcalfe 在 1973 年发明,经过 IEEE 802.3 标准化,成为局域网的绝对霸主。今天几乎所有有线局域网都是以太网,无论你用的是 1GbE、10GbE 还是 100GbE。
帧格式详解
一个标准以太网 II(Ethernet II / DIX)帧的完整结构:
线缆上的完整帧(包含物理层开销):
┌──────────┬─────┬──────────────────────────────────────────────┬─────┬─────┐
│ Preamble │ SFD │ MAC Frame │ FCS │ IFG │
│ 7 bytes │ 1B │ 14 + 46~1500 bytes │ 4B │ 12B │
└──────────┴─────┴──────────────────────────────────────────────┴─────┴─────┘
│ │
├─ Dst MAC (6B) ─┤ │
├─ Src MAC (6B) ─┤ │
├─ EtherType (2B)┤ │
├─ Payload (46~1500B) ────────────────────────┤
│ │
└──────── 这部分是 L2 交换机处理的范围 ────────┘
各字段的工程含义:
| 字段 | 大小 | 说明 |
|---|---|---|
| Preamble(前导码) | 7 字节 | 交替的 10101010 模式,用于时钟同步。接收端靠它来锁定比特采样时机 |
| SFD(帧起始定界符) | 1 字节 | 10101011,标记帧的真正开始 |
| Dst MAC(目标 MAC) | 6 字节 | 目标网卡的 MAC 地址。ff:ff:ff:ff:ff:ff 是广播地址 |
| Src MAC(源 MAC) | 6 字节 | 发送端网卡的 MAC 地址 |
| EtherType(类型) | 2 字节 | 标识上层协议:0x0800=IPv4,0x86DD=IPv6,0x0806=ARP,0x8100=VLAN |
| Payload(载荷) | 46-1500 字节 | 上层协议数据(IP 包)。最小 46 字节,不足时填充(padding) |
| FCS(帧校验序列) | 4 字节 | CRC-32 校验,检测传输中的比特翻转 |
| IFG(帧间间隔) | 12 字节 | 帧之间的最小静默期,给接收端处理时间 |
几个工程细节:
为什么载荷最小 46 字节? 以太网的冲突检测(CSMA/CD)要求帧的总长度不能太短——如果帧太短,发送端可能在检测到冲突前就发完了。在 10Mbps 以太网的最大网段长度(2500 米)下,一个最小帧(64 字节,不含 Preamble/SFD/IFG)需要 51.2 微秒来传输,正好覆盖最大往返传播延迟。虽然现代全双工以太网已经不需要 CSDA/CD,但最小帧大小的限制保留了下来。
FCS 由网卡硬件计算和校验。
发送端网卡在发送前计算 CRC-32
并追加到帧尾;接收端网卡收到帧后重新计算 CRC-32,如果与 FCS
不匹配,直接丢弃——不会把错误帧传给上层。这就是为什么
ethtool -S 看到的 rx_crc_errors
如果持续增长,说明物理链路有问题(线缆、光模块、交换机端口)。
Preamble、SFD 和 IFG 是物理层开销,不计入”帧大小”。 当我们说”以太网帧最大 1518 字节”,指的是从 Dst MAC 到 FCS 的部分(14 + 1500 + 4 = 1518)。但线缆上实际传输的是 1518 + 8(Preamble+SFD)+ 12(IFG)= 1538 字节。这个区别在计算线速(wire speed)时很重要。
用 tcpdump 观察帧结构
# -e 选项显示链路层头部(MAC 地址和 EtherType)
tcpdump -i eth0 -nn -e -c 5
# 输出示例:
# 10:23:45.123456 aa:bb:cc:dd:ee:ff > 11:22:33:44:55:66, ethertype IPv4 (0x0800), length 74:
# 10.0.0.100.55432 > 93.184.216.34.80: Flags [S], seq 1234567890, ...
#
# 解读:
# aa:bb:cc:dd:ee:ff > 11:22:33:44:55:66 → 源 MAC > 目标 MAC
# ethertype IPv4 (0x0800) → 载荷是 IPv4 包
# length 74 → 帧大小 74 字节(不含 FCS)
# 74 = 14(Eth Header) + 40(IP Header+TCP Header) + 20(TCP options)
# 查看网卡的帧统计
ethtool -S eth0 | grep -iE "frame|crc|error|drop"
# 关注:
# rx_crc_errors → CRC 校验失败的帧数(物理层问题)
# rx_frame_errors → 帧格式错误
# rx_length_errors → 帧长度不合法二、MTU 与 MSS:精确的数字关系
MTU(Maximum Transmission Unit)是链路层允许的最大载荷大小。标准以太网的 MTU 是 1500 字节——这个值从 1980 年代沿用至今。
MTU、MSS 与各层头部的精确计算
MTU 决定了 IP 层能发送的最大包大小。而 IP 头部本身也要占空间,所以传输层(TCP/UDP)能用的空间更小。TCP 的 MSS(Maximum Segment Size)就是 TCP 层能发送的最大数据段大小。
标准以太网帧(MTU 1500)的空间分配:
以太网帧载荷(1500 字节 = MTU)
├── IP Header: 20 字节(无选项)
├── TCP Header: 20 字节(无选项)
└── TCP Payload: 最大 1460 字节 = MSS(无 TCP 选项时)
实际情况(有 TCP 时间戳选项):
├── IP Header: 20 字节
├── TCP Header: 20 字节
├── TCP Options: 12 字节(时间戳选项 10 字节 + 2 字节 NOP 对齐)
└── TCP Payload: 最大 1448 字节 = 实际 MSS
几个关键的数字关系:
| 参数 | 值 | 计算 |
|---|---|---|
| 以太网 MTU | 1500 字节 | 标准固定值 |
| IPv4 头部 | 20 字节 | 无选项时 |
| IPv6 头部 | 40 字节 | 固定值(比 IPv4 大 20 字节) |
| TCP 头部 | 20 字节 | 无选项时 |
| TCP MSS(IPv4,无选项) | 1460 字节 | 1500 - 20 - 20 |
| TCP MSS(IPv4,有时间戳) | 1448 字节 | 1500 - 20 - 32 |
| TCP MSS(IPv6,无选项) | 1440 字节 | 1500 - 40 - 20 |
| UDP 头部 | 8 字节 | 固定值 |
| UDP 最大载荷 | 1472 字节 | 1500 - 20 - 8 |
一个工程上容易忽略的细节:TCP
头部的选项字段会进一步压缩 MSS。 最常见的 TCP
选项是时间戳(Timestamps,RFC 7323),占 12 字节(10
字节选项本身 + 2 字节 NOP 对齐)。几乎所有现代 Linux
系统都默认开启时间戳选项(net.ipv4.tcp_timestamps=1),所以实际
MSS 通常是 1448 而不是 1460。
TCP SYN 包中的选项更多——除了 MSS 声明外,通常还有 Window Scale(3 字节 + 1 字节 NOP)和 SACK Permitted(2 字节),加上时间戳,TCP 头部总长度达到 40 字节。但 MSS 是根据 数据传输阶段 的头部大小计算的(通常只有时间戳选项,32 字节 TCP 头部),不是根据 SYN 包的头部大小。
MSS 在 TCP 三次握手时协商。 双方在 SYN
包的 TCP 选项中各自声明自己的 MSS
值,之后使用两者中较小的那个。Linux 默认根据本机网卡的 MTU
计算 MSS(MTU - 40),但你也可以用 ip route
为特定路由设置 MSS 上限:
# 查看 TCP 连接协商的 MSS
ss -ti | grep mss
# 输出示例:mss:1460 pmtu:1500 rcvmss:1460 advmss:1460
# 为特定路由设置 MSS 上限(限制到 1400,避免隧道中的 MTU 问题)
ip route change 10.0.0.0/24 via 10.0.0.1 advmss 1400
# 用 iptables 钠制 MSS(更灵活,常用于 VPN 隧道场景)
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -o eth0 -j TCPMSS --clamp-mss-to-pmtu隧道封装对 MTU 的影响
每一层隧道封装都会在原始包外面再包一层头部,压缩了可用的 MTU 空间:
无隧道:
Eth(14) + IP(20) + TCP(20) + Data(最多 1460) + FCS(4)
MTU = 1500
VXLAN 隧道:
外层 Eth(14) + 外层 IP(20) + 外层 UDP(8) + VXLAN Header(8)
+ 内层 Eth(14) + 内层 IP(20) + TCP(20) + Data(最多 1410) + FCS(4)
有效 MTU = 1450
VXLAN 开销 = 50 字节
WireGuard 隧道:
外层 Eth(14) + 外层 IP(20) + 外层 UDP(8) + WG Header(32)
+ 内层 IP(20) + TCP(20) + Data(最多 1400) + FCS(4)
有效 MTU = 1440
WireGuard 开销 = 60 字节
容器网络的常见 MTU 值:
| CNI 插件 | 默认封装 | 推荐 Pod MTU |
|---|---|---|
| Calico(IPIP 模式) | IP-in-IP (20B) | 1480 |
| Calico(VXLAN 模式) | VXLAN (50B) | 1450 |
| Calico(BGP 模式) | 无封装 | 1500 |
| Flannel(VXLAN) | VXLAN (50B) | 1450 |
| Cilium(VXLAN) | VXLAN (50B) | 1450 |
| Cilium(native routing) | 无封装 | 1500 |
| WireGuard overlay | WireGuard (60B) | 1440 |
如果 CNI 插件没有正确配置 Pod 网卡的 MTU,Pod 内的应用会以为 MTU 是 1500,发出的大包在隧道封装后超过物理链路的 MTU,被丢弃。
IP 分片 vs TCP 分段
MTU 限制的是 IP 层发出的单个包的大小。当上层要发送的数据大于 MTU 时,有两种处理方式,它们的工程含义完全不同:
TCP 分段(Segmentation):TCP 在发送端把数据切成不超过 MSS 的段,每个段独立封装成 IP 包。这是正常的工作方式——TCP 知道 MSS,会主动切分数据,每个 IP 包都不会超过 MTU。
IP 分片(Fragmentation):如果 IP 层收到一个超过 MTU 的包(比如来自 UDP 的大数据报),且 DF 位没有设置,IP 层会把它切成多个片段(fragment),每个片段独立传输,在接收端重组。
IP 分片在现代网络中几乎被视为 bug,原因有三:
任何一个分片丢失,整个数据报都要重传。 接收端必须收齐所有分片才能重组。如果丢包率是 0.1%,一个被分成 5 个分片的数据报有 0.5% 的概率需要重传。
分片增加了接收端的资源消耗。 接收端要在内存中缓存不完整的分片,等待剩余分片到达。如果大量分片涌入(分片攻击),可能耗尽接收端的重组缓冲区。
某些防火墙和 NAT 设备无法处理分片。 只有第一个分片包含传输层头部(端口号),后续分片没有——这让基于端口的防火墙规则失效。有些防火墙会直接丢弃所有分片。
正因为 IP 分片的问题,现代 TCP 实现默认设置 DF 位(Linux 从 2.6.x 开始默认 DF=1),完全避免 IP 分片,转而依赖 PMTUD 来发现路径 MTU。UDP 应用则需要自己注意数据报大小不超过路径 MTU,或者接受分片带来的性能损失。
# 查看 IP 分片统计
cat /proc/net/snmp | grep Ip | head -2
# 关注:
# ReasmReqds - 需要重组的 IP 分片数
# ReasmOKs - 重组成功的数据报数
# ReasmFails - 重组失败的数据报数
# FragOKs - 分片成功的数据报数
# FragFails - 分片失败的数据报数(DF 位设置导致)
# FragCreates - 创建的分片数
# 如果 FragFails 持续增长,说明有大包被 DF 位拦截但 PMTUD 没有生效
# 如果 ReasmFails 持续增长,说明有分片在传输中丢失三、PMTUD:路径 MTU 发现与黑洞问题
路径 MTU 发现(Path MTU Discovery,PMTUD,RFC 1191)是 TCP/IP 协议栈用来自动发现整条路径上最小 MTU 的机制。它的工作原理很简单:
- 发送端设置 IP 头部的 DF(Don’t Fragment)位,禁止中间路由器分片
- 发送端按照本机 MTU 发送数据包
- 如果路径上某一跳的 MTU 小于包的大小,该路由器丢弃数据包并返回 ICMP “Fragmentation Needed”(Type 3, Code 4)消息,消息中包含该跳的 MTU 值
- 发送端收到 ICMP 消息后,降低 MTU 重新发送
PMTUD 正常工作流程:
发送端(MTU=1500) 路由器(MTU=1400) 接收端
│ │ │
│──── IP包(1500B, DF=1) ──────→│ │
│ │ 包太大,丢弃 │
│←── ICMP Frag Needed(MTU=1400)│ │
│ │ │
│ 降低 PMTU 到 1400 │ │
│──── IP包(1400B, DF=1) ──────→│──── IP包(1400B) ──────────→│
│ │ │
PMTUD 黑洞
PMTUD 黑洞(PMTUD Black Hole)是指路径上的某个设备丢弃了大包,但没有返回 ICMP Fragmentation Needed 消息。发送端永远不知道该降低 MTU,只能一遍又一遍地重传相同大小的包,全部被丢弃。
下面的流程图展示了 PMTUD 黑洞的诊断和解决路径:
flowchart TD
A["TCP 连接正常,小请求正常<br>大响应超时"] --> B{"tcpdump 中看到<br>大包反复重传?"}
B -- "是" --> C{"ping -M do -s 1472<br>是否收到 ICMP<br>Frag Needed?"}
B -- "否" --> D["非 MTU 问题,检查<br>应用层/服务端"]
C -- "是" --> E["PMTUD 正常<br>检查操作系统是否<br>正确处理 ICMP"]
C -- "否" --> F["确认 PMTUD 黑洞"]
F --> G{"tracepath 能否<br>定位瓶颈节点?"}
G -- "是" --> H["联系该节点管理员<br>放行 ICMP type 3"]
G -- "否" --> I["防御性解决方案"]
I --> J["MSS Clamping<br>iptables -j TCPMSS<br>--clamp-mss-to-pmtu"]
I --> K["降低本端 MTU<br>ip link set mtu 1400"]
I --> L["启用 PLPMTUD<br>tcp_mtu_probing=1"]
PMTUD 黑洞的常见原因:
| 原因 | 说明 | 常见程度 |
|---|---|---|
| 防火墙屏蔽 ICMP | 很多防火墙把所有 ICMP 一刀切地屏蔽了,包括 Fragmentation Needed | 最常见 |
| ICMP 限速 | 路由器对 ICMP 做了严格的速率限制,高流量时 ICMP 被丢弃 | 常见 |
| NAT 设备丢弃 | 某些 NAT 设备不会为 ICMP 错误消息做地址转换,导致 ICMP 无法到达源主机 | 偶尔 |
| 运营商网络策略 | 某些运营商在核心网络中屏蔽 ICMP | 较少 |
PMTUD 黑洞的症状:TCP
连接建立正常(SYN/SYN-ACK
是小包),小请求正常,但大响应超时或极慢。在
tcpdump 中会看到同一个大包被反复重传。
检测与排查 PMTUD 黑洞
# 方法 1:用 ping 手动探测路径 MTU
# -M do 设置 DF 位,-s 指定 ICMP payload 大小
# ICMP payload + IP Header(20) + ICMP Header(8) = 总包大小
# 所以 -s 1472 对应 IP 包大小 1500
# 测试 1500 MTU 是否可通
ping -M do -s 1472 -c 3 目标IP
# 如果不通(Frag needed and DF set 或者超时),缩小
ping -M do -s 1372 -c 3 目标IP
# 二分法快速定位精确 PMTU
for s in 1472 1400 1300 1200; do
result=$(ping -M do -s $s -c 1 -W 2 目标IP 2>&1)
if echo "$result" | grep -q "bytes from"; then
echo "Size $s: OK"
else
echo "Size $s: FAIL"
fi
done
# 方法 2:用 tracepath(自动发现路径 MTU,不需要 root)
tracepath 目标IP
# 输出最后一行会显示 "Resume: pmtu 1500" 或更小的值
# 方法 3:查看内核缓存的 PMTU(最近发现过 PMTUD 的路径)
ip route get 目标IP
# 如果内核缓存了 PMTU,会显示 "mtu 1400" 等值
# 方法 4:用 tcpdump 抓包确认重传模式
tcpdump -i eth0 -nn 'host 目标IP and tcp' -c 100 | grep -E "retransmit|length [0-9]{4}"
# 如果看到相同 length 的包被反复重传,高度怀疑 PMTUD 黑洞解决 PMTUD 黑洞
有几种解决方案,按推荐程度排序:
方案 1:MSS Clamping(最推荐)。 用 iptables/nftables 在 TCP SYN 包中修改 MSS 选项,让 TCP 从一开始就使用较小的 MSS,避免发出超过路径 MTU 的包:
# 把所有出站 TCP SYN 的 MSS 限制为路径 MTU 允许的最大值
# --clamp-mss-to-pmtu 会根据路由表中的 PMTU 自动计算
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
# 或者手动指定一个保守的 MSS 值
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1400这是最干净的解决方案:它只影响 TCP SYN 握手阶段,不需要分片,没有额外开销。几乎所有的 VPN 网关和容器网络 CNI 插件都默认配置了 MSS Clamping。
方案 2:降低本机 MTU。 如果你知道路径上最小的 MTU 是多少,可以直接把本机网卡的 MTU 调低:
# 降低网卡 MTU(立即生效,但重启后失效)
ip link set eth0 mtu 1400
# 永久设置(取决于你的网络配置系统)
# Netplan(Ubuntu):
# network:
# ethernets:
# eth0:
# mtu: 1400缺点是影响了所有流量,即使有些目标的路径 MTU 是完整的 1500。
方案 3:启用 PMTUD 黑洞检测。 Linux 内核从 3.2 开始支持 RFC 4821 的 Packetization Layer PMTUD(PLPMTUD),通过主动发送不同大小的探测包来发现路径 MTU,不依赖 ICMP:
# 启用 PLPMTUD(基于 TCP 的 PMTU 黑洞检测)
sysctl -w net.ipv4.tcp_mtu_probing=1
# 0 = 禁用(默认)
# 1 = 当检测到 PMTUD 黑洞时才启用探测
# 2 = 始终启用探测
# 设置探测的最小 MSS
sysctl -w net.ipv4.tcp_base_mss=1024MTU 问题排查实战案例
一个真实场景的排查过程,帮你建立 MTU 问题的排查直觉。
症状:Kubernetes 集群中,Pod A 调用 Pod B 的 API,小请求(< 1KB)正常,但当 Pod B 返回大于 1400 字节的响应时,请求超时。两个 Pod 在不同的 Node 上。
排查步骤:
# 1. 确认症状:用 curl 测试不同大小的响应
# 从 Pod A 中执行
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" http://pod-b:8080/small
# 输出: 200 0.005s ← 正常
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" http://pod-b:8080/large
# 输出: 000 30.001s ← 超时
# 2. 在 Pod A 中抓包,看 TCP 行为
tcpdump -i eth0 -nn host Pod-B-IP -c 50
# 观察到:大响应的 TCP 段被重传多次,从未收到 ACK
# 3. 检查 Pod 内的 MTU
ip link show eth0
# 输出:mtu 1500 ← Pod 认为 MTU 是 1500
# 4. 检查 Node 上的 CNI 网络
# Node 使用 Calico VXLAN 模式
ip link show vxlan.calico
# 输出:mtu 1450 ← VXLAN 设备 MTU 1450
# 5. 问题定位!
# Pod MTU 1500 > VXLAN 隧道 MTU 1450
# Pod 发出 1500 字节的 IP 包,经过 VXLAN 封装后变成 1550 字节
# 超过物理网卡 MTU 1500,被丢弃
# ICMP Frag Needed 被 Calico 的 iptables 规则拦截 → PMTUD 黑洞
# 6. 验证:用 ping 测试路径 MTU
ping -M do -s 1422 -c 1 Pod-B-IP # 不通(1422+28=1450, 加 VXLAN 头后 1500,刚好超)
ping -M do -s 1372 -c 1 Pod-B-IP # 通过
# 7. 解决方案:修正 Calico 配置,让 Pod MTU = 1450
# 在 Calico 的 IPPool 配置中设置 MTU
# kubectl patch ippool default-ipv4-ippool --type=merge \
# -p '{"spec":{"vxlanMTU":1450}}'这个案例覆盖了 MTU 问题的典型模式:大包丢、小包好、隧道封装是根因、PMTUD 黑洞放大了问题。在容器网络中,第一件事就是检查 Pod MTU 和 CNI 隧道 MTU 是否匹配。
各种场景下的 MTU 推荐值
| 网络场景 | 推荐 MTU | 对应 MSS(IPv4) | 原因 |
|---|---|---|---|
| 标准以太网(无隧道) | 1500 | 1460 | 默认值 |
| VXLAN Overlay | 1450 | 1410 | VXLAN 头部 50 字节 |
| GRE 隧道 | 1476 | 1436 | GRE 头部 24 字节 |
| IPsec(ESP 隧道模式) | 1400 | 1360 | ESP 开销约 50-60 字节,取保守值 |
| WireGuard | 1420 | 1380 | WG 头部 60 字节 + 一些余量 |
| PPPoE(家庭宽带) | 1492 | 1452 | PPPoE 头部 8 字节 |
| Jumbo Frame(数据中心) | 9000 | 8960 | 需要全路径支持 |
| 双层隧道(VXLAN over IPsec) | 1340 | 1300 | 两层开销叠加 |
四、VLAN 标签与帧大小
VLAN(Virtual LAN,IEEE 802.1Q)通过在以太网帧中插入 4 字节的 VLAN 标签来实现逻辑隔离。这个标签插在源 MAC 和 EtherType 之间:
标准帧(无 VLAN):
┌─────────┬─────────┬───────────┬─────────────┬─────┐
│ Dst MAC │ Src MAC │ EtherType │ Payload │ FCS │
│ 6B │ 6B │ 2B │ 46-1500B │ 4B │
└─────────┴─────────┴───────────┴─────────────┴─────┘
总计:最大 1518 字节
802.1Q VLAN 帧:
┌─────────┬─────────┬──────────────┬───────────┬─────────────┬─────┐
│ Dst MAC │ Src MAC │ 802.1Q Tag │ EtherType │ Payload │ FCS │
│ 6B │ 6B │ 4B │ 2B │ 46-1500B │ 4B │
└─────────┴─────────┴──────────────┴───────────┴─────────────┴─────┘
总计:最大 1522 字节
802.1Q 标签的 4 字节结构:
| 字段 | 位数 | 说明 |
|---|---|---|
| TPID(Tag Protocol Identifier) | 16 位 | 固定为 0x8100,标识这是 VLAN 帧 |
| PCP(Priority Code Point) | 3 位 | 优先级(0-7),用于 QoS |
| DEI(Drop Eligible Indicator) | 1 位 | 拥塞时可丢弃标志 |
| VID(VLAN ID) | 12 位 | VLAN 编号(0-4095,其中 0 和 4095 保留) |
VLAN 对 MTU 的影响:802.1Q 标签增加了 4 字节。虽然 IEEE 802.3ac 标准将最大帧大小从 1518 扩展到 1522 字节以容纳 VLAN 标签,但MTU(1500 字节)不变——VLAN 标签不占用 MTU 空间。这是因为 MTU 定义的是网络层(IP)的最大载荷,而 VLAN 标签是链路层的开销。
但有一个例外:QinQ(IEEE 802.1ad,双层 VLAN 标签) 会插入两个 4 字节标签(共 8 字节额外开销),总帧大小可达 1526 字节。有些老设备不支持大于 1518 字节的帧(即使是合法的 VLAN 帧),这在运营商网络中偶尔会导致问题。
# 查看 VLAN 配置
ip -d link show | grep vlan
# 创建 VLAN 子接口
ip link add link eth0 name eth0.100 type vlan id 100
ip addr add 192.168.100.1/24 dev eth0.100
ip link set eth0.100 up
# 用 tcpdump 查看 VLAN 标签(需要在 trunk 口抓包)
tcpdump -i eth0 -nn -e -c 5 vlan
# 输出中会显示 vlan 100 等标签信息Linux VLAN 配置的常见陷阱
在 Linux 上配置 VLAN 子接口时,有几个容易踩的坑:
# 陷阱 1:父接口 MTU 限制子接口
# 如果父接口 MTU 是 1500,VLAN 子接口的 MTU 也不能超过 1500
ip link show eth0 | grep mtu # 先检查父接口 MTU
ip link show eth0.100 | grep mtu # 子接口 MTU ≤ 父接口 MTU
# 陷阱 2:忘记开启混杂模式或正确配置交换机 trunk 口
# 如果交换机端口是 access 模式,VLAN 标签在到达 Linux 前就被剥掉了
# 确保连接的交换机端口配置为 trunk,并允许对应的 VLAN ID
# 陷阱 3:VLAN 子接口的 ARP 行为
# 默认情况下,Linux 会在所有接口上响应 ARP 请求(arp_filter=0)
# 这可能导致不同 VLAN 的 ARP 响应出现在错误的接口上
sysctl -w net.ipv4.conf.all.arp_filter=1 # 推荐在多 VLAN 环境启用
sysctl -w net.ipv4.conf.all.arp_announce=2 # 使用最匹配的源 IP 发送 ARPARP 与以太网的关系
ARP(Address Resolution Protocol)是以太网正常工作的基础——它负责把 IP 地址解析为 MAC 地址。ARP 问题经常伪装成”网络不通”,实际是链路层的地址解析出了问题。
# 查看 ARP 缓存
ip neigh show
# 输出示例:
# 10.0.0.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE
# 10.0.0.2 dev eth0 FAILED ← ARP 解析失败,可能对端不在线或被防火墙拦截
# 10.0.0.3 dev eth0 lladdr 00:11:22:33:44:55 STALE ← 缓存过期,下次通信会重新解析
# 手动添加静态 ARP 条目(避免 ARP 欺骗)
ip neigh add 10.0.0.1 lladdr aa:bb:cc:dd:ee:ff dev eth0
# 清除 ARP 缓存中的单条记录
ip neigh del 10.0.0.1 dev eth0
# 监控 ARP 请求和响应
tcpdump -i eth0 -nn arp -c 20
# 如果看到大量 ARP 请求但没有响应,说明目标主机不可达或配置错误
# 如果看到同一 IP 有多个不同 MAC 的 ARP Reply,可能存在 ARP 欺骗或 IP 冲突ARP 问题的常见场景:
- ARP 表溢出:Linux 默认 ARP 表大小由
gc_thresh参数控制。在大规模扁平网络(如 /16 子网)中,ARP 表可能被填满,导致新地址无法解析 - GARP(Gratuitous ARP)冲突:VIP 漂移时,新 master 发送 GARP 通知交换机更新 MAC 表。如果交换机没有正确处理 GARP,会导致流量仍然发往旧 master
- ARP 代理:开启
proxy_arp的设备会代替其他主机响应 ARP,这在容器网络(如 Calico BGP 模式)中被广泛使用
# 检查 ARP 表大小限制
sysctl net.ipv4.neigh.default.gc_thresh1 # 软下限,默认 128
sysctl net.ipv4.neigh.default.gc_thresh2 # 软上限,默认 512
sysctl net.ipv4.neigh.default.gc_thresh3 # 硬上限,默认 1024
# 在大规模网络中适当增大
sysctl -w net.ipv4.neigh.default.gc_thresh1=4096
sysctl -w net.ipv4.neigh.default.gc_thresh2=8192
sysctl -w net.ipv4.neigh.default.gc_thresh3=16384五、Jumbo Frame:数据中心的性能优化
Jumbo Frame(巨帧)是指 MTU 大于标准 1500 字节的以太网帧,通常为 9000 字节(有些设备支持 9216 字节)。它不是 IEEE 标准(从未被正式标准化),但在数据中心内部网络中被广泛使用。
性能收益
Jumbo Frame 的收益主要来自两方面:
1. 减少帧头开销占比。 每个以太网帧有固定的头部开销(Preamble 8B + Eth Header 14B + FCS 4B + IFG 12B = 38 字节)。传输同样大小的数据,帧越大,头部开销的占比越低:
| MTU | 帧数(传输 1MB 数据) | 头部开销占比 | 理论最大效率 |
|---|---|---|---|
| 1500 | 700 | 2.5% | 97.5% |
| 9000 | 117 | 0.4% | 99.6% |
2. 减少中断次数和 CPU 开销。 每收到一个帧,网卡会触发一次中断(或者在 NAPI 模式下被轮询处理一次)。帧越大,传输同样数据量需要的帧数越少,中断次数越少,CPU 花在中断处理和协议栈处理上的时间越少。
在 10GbE 线速传输下,标准帧(1500B)需要每秒处理约 812,000 帧,而 Jumbo Frame(9000B)只需约 138,000 帧——减少了 83% 的帧处理开销。
实际性能提升取决于工作负载。对于大块顺序传输(比如存储网络、数据库复制、大文件传输),Jumbo Frame 的收益显著(吞吐量提升 5-20%,CPU 使用率下降)。对于小包为主的工作负载(比如 Web 服务的 HTTP 请求),收益很小。
Jumbo Frame vs TSO/GRO:哪个更有效?
在讨论 Jumbo Frame 之前,需要了解另一组做类似事情的技术——TSO/GSO 和 GRO/LRO。
TSO(TCP Segmentation Offload)/ GSO(Generic Segmentation Offload):发送端把大块数据交给网卡(TSO)或内核(GSO),由网卡/内核负责切分成 MSS 大小的段。这样 TCP 协议栈只需处理一个大段,减少了协议栈的处理开销。
GRO(Generic Receive Offload)/ LRO(Large Receive Offload):接收端把多个小帧合并成一个大块再交给上层协议栈处理,减少协议栈的调用次数。
# 查看网卡的 offload 特性
ethtool -k eth0 | grep -E "segmentation|offload|gro|tso"
# 输出示例:
# tcp-segmentation-offload: on
# generic-segmentation-offload: on
# generic-receive-offload: on
# large-receive-offload: off [fixed]
# TSO/GRO 是否开启直接影响性能
# 如果关闭了 TSO,大块 TCP 传输的 CPU 开销会显著增加TSO/GRO 和 Jumbo Frame 的区别:TSO/GRO 是纯软件/网卡驱动优化,线缆上传输的帧仍然是标准大小(1500 MTU)。Jumbo Frame 则是线缆上传输的帧本身就很大(9000 MTU)。两者可以叠加使用:在 Jumbo Frame 网络中开启 TSO/GRO 效果最好。
一个实际的选择:如果你不能保证路径上所有设备都支持 Jumbo Frame(比如跨数据中心或面向互联网),TSO/GRO 是更安全的选择——它几乎没有部署风险,默认就是开启的,且在大多数场景下提供了 Jumbo Frame 80% 的收益。
部署陷阱
Jumbo Frame 的最大问题是:路径上的所有设备都必须支持相同的 MTU。 这个要求看起来简单,实际部署中非常容易出错。
陷阱 1:路径上某一跳 MTU 不匹配。 如果端到端路径上有一个设备的 MTU 是 1500(比如一个没配 Jumbo Frame 的交换机),所有超过 1500 字节的帧都会被丢弃。PMTUD 可能检测到这个问题,但前面说过的 PMTUD 黑洞问题同样存在。
陷阱 2:忘了改 VLAN 接口的 MTU。 你把物理口的 MTU 改成了 9000,但 VLAN 子接口的 MTU 还是默认的 1500。或者反过来——VLAN 子接口设了 9000,但 trunk 口还是 1500。
陷阱 3:虚拟化/容器环境的 MTU 链条。 在虚拟化环境中,MTU 链条可能是:虚拟机 → virtio 网卡 → 宿主机 bridge → 物理网卡。每一层都需要正确配置 MTU,而且虚拟层的 MTU 必须 ≤ 物理层的 MTU。容器环境(veth pair → bridge → 物理口)同理。
陷阱 4:云环境的限制。 大多数公有云的 VPC 网络不支持 Jumbo Frame,或者只在特定实例类型之间支持(比如 AWS 在同一个 Placement Group 内的实例之间支持 9001 MTU)。跨 VPC 或跨可用区的流量通常限制在 1500 MTU。
配置与验证
# 设置 Jumbo Frame MTU(需要网卡和交换机都支持)
ip link set eth0 mtu 9000
# 检查网卡支持的最大 MTU
ip -d link show eth0 | grep maxmtu
# 或者
cat /sys/class/net/eth0/mtu
# 验证端到端 Jumbo Frame 是否可用
# ICMP payload 8972 + IP Header 20 + ICMP Header 8 = 9000
ping -M do -s 8972 -c 5 对端IP
# 如果 ping 通,说明整条路径支持 9000 MTU
# 如果显示 "Frag needed",说明路径上有设备 MTU 不足
# 查看当前网卡的 MTU
ip link show eth0 | grep mtu何时使用 Jumbo Frame
| 场景 | 推荐 | 原因 |
|---|---|---|
| 数据中心内部存储网络(iSCSI、NFS) | 强烈推荐 | 大块顺序 I/O,收益显著 |
| 数据中心内部后端网络 | 推荐 | 通常可以控制所有设备的 MTU |
| 数据库复制、大数据集群 | 推荐 | 大数据量传输,减少 CPU 开销 |
| 面向互联网的服务 | 不推荐 | 互联网路径无法保证 9000 MTU |
| 跨数据中心 / 跨云 | 不推荐 | VPN/隧道通常限制 MTU |
| 混合网络(部分设备不可控) | 不推荐 | MTU 不匹配风险高 |
一个务实的建议:只在你能完全控制所有网络设备的环境中使用 Jumbo Frame,并且在部署后用 ping -M do -s 8972 逐一验证所有路径。
六、网卡性能监控与异常检测
在日常运维中,链路层的健康状况直接影响上层性能。定期检查网卡的错误计数器可以在问题影响用户之前发现物理层和链路层的异常。
关键指标
# 查看网卡速度和双工模式
ethtool eth0
# 关注:
# Speed: 10000Mb/s ← 如果比预期低,可能自协商出了问题
# Duplex: Full ← Half-duplex 在现代网络中是异常
# Link detected: yes ← no 表示物理链路断开
# 查看网卡详细错误统计
ethtool -S eth0 | grep -iE "error|drop|crc|collision|fifo|miss|over|pause"关键计数器的含义:
| 计数器 | 含义 | 非零时说明 |
|---|---|---|
| rx_crc_errors | 接收的 CRC 校验失败帧数 | 物理层问题:线缆损坏、光模块故障、电磁干扰 |
| rx_frame_errors | 接收的帧格式错误 | 通常伴随 CRC 错误,同样是物理层问题 |
| rx_length_errors | 帧长度不合法(太短或太长) | MTU 不匹配或网卡 bug |
| rx_missed_errors | 网卡硬件缓冲区满导致的丢帧 | 网卡处理不过来,可能需要增加 ring buffer |
| rx_fifo_errors | 网卡 FIFO 溢出 | 中断处理太慢,CPU 跟不上网卡速率 |
| tx_dropped | 发送丢弃的帧 | 发送队列满或限速策略 |
| collisions | 冲突次数 | 在全双工网络中不应该出现 |
| rx_pause / tx_pause | 流控暂停帧 | 网卡或交换机在做链路层流控 |
rx_missed_errors 和 rx_fifo_errors 持续增长是性能警报。 它们说明网卡收到了帧但内核来不及处理——可能是中断亲和(IRQ affinity)配置不好,也可能是 CPU 被其他任务抢占。
# 增大网卡 ring buffer(减少 rx_missed_errors)
# 先查看当前和最大 ring buffer 大小
ethtool -g eth0
# 输出示例:
# Ring parameters for eth0:
# Pre-set maximums:
# RX: 4096
# TX: 4096
# Current hardware settings:
# RX: 256
# TX: 256
# 增大到最大值
ethtool -G eth0 rx 4096 tx 4096
# 查看中断亲和(哪个 CPU 处理哪个网卡队列)
cat /proc/interrupts | grep eth0
# 如果所有中断都集中在 CPU 0,需要配置 IRQ 亲和
# 查看网卡是否启用了多队列
ethtool -l eth0自动化监控
在生产环境中,应该把网卡错误计数器纳入监控系统。一个简单的采集脚本:
# 采集网卡错误指标(适合接入 Prometheus node_exporter 或自定义采集)
for iface in $(ls /sys/class/net/ | grep -v lo); do
echo "=== $iface ==="
ethtool -S $iface 2>/dev/null | grep -iE "error|drop|crc|miss|fifo" | grep -v ": 0$"
done
# 只输出非零的错误计数器——如果有输出,就需要关注Prometheus 的 node_exporter 默认采集
/sys/class/net/*/statistics/
下的网卡统计数据(node_network_receive_errs_total
等),可以设置告警规则在错误计数器增长时通知。
速度和双工协商问题
一个不太常见但很坑的问题是自协商(Auto-Negotiation)失败。当网卡和交换机端口的速度/双工模式配置不一致时,可能出现以下情况:
- 一端设置了强制 1000Mbps 全双工,另一端设置了自协商:自协商端无法检测到对端的速度,可能降级到 100Mbps 或半双工
- 半双工模式下会出现大量冲突(collision)和 late collision,导致性能急剧下降
# 查看当前速度和双工模式
ethtool eth0 | grep -E "Speed|Duplex|Auto-negotiation"
# 输出示例:
# Speed: 1000Mb/s
# Duplex: Full
# Auto-negotiation: on
# 如果速度低于预期,检查交换机端口配置
# 强制设置速度和双工(通常不推荐,除非排查问题)
# ethtool -s eth0 speed 1000 duplex full autoneg off
# 查看自协商能力
ethtool eth0 | grep -A 20 "Advertised auto-negotiation"最佳实践:两端都设置为自协商(这是默认配置)。只有在自协商确实有问题时,才在两端同时设置固定速度和双工。只改一端而另一端仍然自协商,几乎一定会导致问题。
七、以太网排查速查表
把全文的排查方法整理成一个速查表,供日常使用:
# ===== 链路层基本信息 =====
# 查看网卡状态、MAC、MTU
ip link show eth0
# 查看网卡详细信息(速度、双工、自协商)
ethtool eth0
# 查看网卡错误统计
ethtool -S eth0 | grep -iE "error|drop|crc|collision|fifo"
# ===== MTU 诊断 =====
# 查看本机 MTU
ip link show | grep mtu
# 测试路径 MTU(标准以太网)
ping -M do -s 1472 -c 3 目标IP
# 测试路径 MTU(Jumbo Frame)
ping -M do -s 8972 -c 3 目标IP
# 自动发现路径 MTU
tracepath 目标IP
# 查看内核缓存的 PMTU
ip route get 目标IP
# ===== ARP 诊断 =====
# 查看 ARP 缓存
ip neigh show
# 查看 ARP 统计
cat /proc/net/stat/arp_cache
# ===== VLAN 诊断 =====
# 查看 VLAN 配置
cat /proc/net/vlan/config 2>/dev/null || ip -d link show | grep vlan
# ===== 帧级抓包 =====
# 显示链路层头部
tcpdump -i eth0 -nn -e -c 10
# 只抓 ARP 包
tcpdump -i eth0 -nn -e arp -c 10
# 只抓 VLAN 包
tcpdump -i eth0 -nn -e vlan -c 10八、结论
以太网帧结构看似简单——源 MAC、目标 MAC、类型、载荷、校验——但它决定了很多上层行为的边界条件。MTU 1500 这个数字从 1980 年代沿用至今,影响着 TCP MSS 的大小、PMTUD 的行为、隧道封装的可用空间。
四个核心要点:
MTU 不匹配是最常见的链路层问题。 尤其在容器网络和 VPN 隧道场景中,隧道封装偷走了有效空间,如果不正确配置 Pod/VM 的 MTU,就会出现”大包丢、小包好”的经典症状。排查三步走:
ping -M do -s测路径 MTU →tracepath自动发现 →tcpdump确认重传模式。PMTUD 黑洞的解决方案是 MSS Clamping。 通过
iptables -j TCPMSS --clamp-mss-to-pmtu在 TCP 握手阶段就限制 MSS,从根本上避免发出超过路径 MTU 的包。这比降低本机 MTU 或依赖 PLPMTUD 更可靠。作为防御性措施,建议在所有 VPN 网关和隧道端点上默认配置。Jumbo Frame 有明确的收益,但部署条件严格。 只在完全可控的数据中心内部网络中使用,路径上的所有设备(包括交换机、路由器、虚拟化层)都必须配置一致的 MTU。部署后必须用
ping -M do -s 8972逐路径验证。TSO/GRO 在大多数场景下是更安全的替代方案。网卡错误计数器是链路层健康的晴雨表。
rx_crc_errors持续增长说明物理链路有问题,rx_missed_errors增长说明 CPU 处理跟不上。把这些计数器纳入 Prometheus 监控并设置告警,可以在问题影响用户之前发现硬件故障。
一个总结性的经验:链路层的问题往往表现为上层的奇怪症状。 TCP 重传、HTTP 超时、应用间歇性报错——如果你在传输层和应用层找不到原因,往下看一层,检查 MTU、网卡错误计数器、ARP 缓存。
下一篇将深入 IP 协议的工程细节:首部各字段的工程含义、DF 位与分片的行为、以及 TTL 在排查中的作用。
上一篇:网络模型的工程视角:为什么你需要理解分层 下一篇:IP 协议深度解剖:首部、分片与路由
参考资料
规范与标准
- IEEE 802.3-2022, IEEE Standard for Ethernet — 以太网帧格式、最小帧大小、FCS 计算的权威标准
- IEEE 802.1Q-2022, Bridges and Bridged Networks — VLAN 标签格式、PCP 优先级
- RFC 894, A Standard for the Transmission of IP Datagrams over Ethernet Networks, C. Hornig, 1984 — Ethernet II 帧格式
- RFC 1191, Path MTU Discovery, J. Mogul & S. Deering, 1990 — PMTUD 的工作原理
- RFC 4821, Packetization Layer Path MTU Discovery, M. Mathis & J. Heffner, 2007 — 不依赖 ICMP 的 PLPMTUD
- RFC 6298, Computing TCP’s Retransmission Timer, V. Paxson et al., 2011
书籍
- Charles E. Spurgeon, Joann Zimmerman, Ethernet: The Definitive Guide, 2nd Edition, O’Reilly — 以太网工程的权威参考
- W. Richard Stevens, TCP/IP Illustrated, Volume 1, Chapter 2: Link Layer — 帧结构与 ARP 的实证分析
技术文章
- Linux Kernel Documentation,
Documentation/networking/ip-sysctl.rst— tcp_mtu_probing 等参数的官方说明 - Cloudflare Blog, “Path MTU Discovery in Practice” — PMTUD 黑洞的真实案例分析
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】网络隔离与微分段:VLAN、SDN 策略与零信任
网络隔离是安全架构的基石。本文从传统 VLAN 的 4096 限制、VXLAN 的 Overlay 隔离机制、SDN 下的 Calico/Cilium Network Policy 工程实践、微分段的设计方法论,到零信任网络架构的分段策略,系统讲解从物理隔离到软件定义隔离的演进和工程落地。
【网络工程】IP 协议深度解剖:首部、分片与路由
IPv4 首部的每个字段都有工程含义——TTL 不只是'生存时间',DF 位决定了 PMTUD 能否工作,IP 分片在现代网络中几乎等于 bug。这篇文章逐字段解读 IPv4 首部,分析分片的工程代价,剖析路由表查找的最长前缀匹配原理,并用 tcpdump 和 ip 命令实际观察每一个行为。
【Kubernetes 网络深度系列】MTU 调优:1 字节的差距如何搞垮整个集群
MTU 层层封装计算、不匹配症状诊断、PMTUD 黑洞排查,以及各 CNI 的最佳 MTU 配置
【网络工程】QUIC 生态与工程部署:从实验到生产
QUIC 已经不是实验性协议——HTTP/3 标准化后,CDN、浏览器和主流服务端框架都在推进 QUIC 支持。本文从工程视角对比主流 QUIC 库的成熟度和性能特征,讲解 CDN/负载均衡器的 QUIC 适配方案、从 TCP 迁移到 QUIC 的渐进路径、QUIC 调试工具链,以及生产环境的部署陷阱和性能调优实践。