一句话总结:MTU 是网络层的物理限制,MSS 是传输层的主动协商——理解这两者的区别,是搞懂网络性能问题的关键第一步。
问题的起源:数据包为什么需要分片?想象你要把一张超大的海报邮寄出去,但邮局规定每个包裹最长不能超过 1 米。你只有两个选择:
把海报裁成几段,分多个包裹寄出,收件人再拼起来。在寄出之前,就把海报折叠到合适的尺寸。IPv4 的世界里,这两种方案分别对应 IP 分片(Fragmentation) 和 MSS 协商。前者是不得已而为之,后者是更聪明的提前规划。
核心概念一:MTU(Maximum Transmission Unit)是什么?MTU 是链路层允许传输的最大数据帧大小,单位是字节。它是一个物理/链路层的硬性限制,由网络介质决定,不是软件可以随意更改的。
网络类型典型 MTU以太网(Ethernet)1500 字节(最常见)本地回环(loopback)65535 字节PPPoE(ADSL 拨号)1492 字节Wi-Fi(802.11)2304 字节VPN(如 WireGuard)1420 字节左右解决了什么问题?不同物理网络的帧大小不同。MTU 定义了"这条路上的卡车最大能装多少货",让网络层知道自己最多能发多大的包。
Linux 命令验证 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看所有网卡的 MTU
ip link show
# 只看 eth0 的 MTU
ip link show eth0
# 临时修改 MTU(需要 root)
sudo ip link set eth0 mtu 1400
# 用 ping 测试 MTU 边界(-s 指定数据大小,-M do 禁止分片)
# ICMP header 8字节 + IP header 20字节 = 28字节
# 所以测试 1500 MTU 时,-s 应该是 1472
ping -s 1472 -M do 8.8.8.8
# 如果包太大且禁止分片,会看到:
# ping: local error: message too long, mtu=1500
ping -s 1473 -M do 8.8.8.8
实验结果解读:
-s 1472 成功 → 说明链路 MTU 是 1500(1472 + 8 ICMP header + 20 IP header = 1500)-s 1473 失败 → 包超过 MTU,且我们禁止了分片,所以直接报错核心概念二:IP 分片(IPv4 Fragmentation)是什么?当一个 IP 数据包大于路径上某个节点的 MTU 时,路由器(或发送方)会把这个包切割成多个更小的分片,每个分片独立传输,最终由目标主机重组。
IPv4 分片头部字段IP Header(20字节)包含三个关键字段:
┌─────────────────┬──────────────────────────────────────┐
│ Identification │ 16位,同一个原始包的分片共享同一个 ID │
│ Flags │ 3位:DF(Don't Fragment)、MF(More Fragments)│
│ Fragment Offset │ 13位,当前分片在原始数据中的偏移量(单位8字节)│
└─────────────────┴──────────────────────────────────────┘
分片过程举例假设要发送一个 3000 字节的 UDP 包,链路 MTU = 1500:
原始包: [IP Header 20B][UDP Header 8B][Data 2972B] = 3000B
分片1: [IP Header][Data 0~1479B] offset=0, MF=1
分片2: [IP Header][Data 1480~2959B] offset=185, MF=1
分片3: [IP Header][Data 2960~2971B] offset=370, MF=0
offset 单位是 8 字节,所以 1480 / 8 = 185
为什么 IP 分片是个坏东西?性能差:分片和重组消耗 CPU,增加延迟。丢一片全重传:TCP 层不知道分片的存在,只要有一个分片丢失,整个 TCP 段都要重传。防火墙/NAT 问题:很多防火墙只检查第一个分片(含端口号),后续分片可能被错误处理或丢弃。IPv6 已废弃路由器分片:IPv6 中只有发送方才能分片,路由器不再做分片,遇到大包直接发 ICMPv6 “包太大” 报错。Linux 命令验证 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 用 tcpdump 抓包,观察 IP 分片
# 先在一个终端启动抓包
sudo tcpdump -i eth0 -n 'host 目标IP' -v
# 另一个终端发送大 UDP 包(故意触发分片)
# hping3 发送 3000 字节 UDP 包
sudo hping3 -2 -d 3000 目标IP
# 或者用 Python 发送大 UDP 包
python3 -c "
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b'x' * 3000, ('目标IP', 9999))
"
# 在 tcpdump 输出中,你会看到:
# flags [+] 表示 MF=1(还有更多分片)
# flags [none] 表示最后一个分片
# frag XXXX:0 第一个分片,offset=0
# frag XXXX:185 第二个分片,offset=185(×8字节=1480)
# 查看系统 IP 分片统计
cat /proc/net/snmp | grep -i frag
# 或者
nstat | grep -i frag
核心概念三:MSS(Maximum Segment Size)是什么?MSS 是 TCP 层的概念,表示 TCP 单次可以发送的最大数据载荷大小(不含 TCP/IP 头部)。它在 TCP 三次握手时协商,写在 SYN 包的 Options 字段中。
MSS = MTU - IP Header(20B) - TCP Header(20B)
MSS = 1500 - 20 - 20 = 1460 字节(以太网标准情况)
MSS 解决了什么问题?MSS 是"聪明的预防"——在发送之前就把数据切到合适的大小,让 TCP 段永远不超过路径 MTU,从根本上避免了 IP 层的分片。
对比一下:
IP 分片 = 货物已经装车了,过不了收费站,被迫在收费站拆包MSS = 发货前就按收费站限制打包好,一路顺畅TCP 握手中的 MSS 协商Client ──SYN (MSS=1460)──────────→ Server
Client ←──SYN-ACK (MSS=1460)───── Server
Client ──ACK──────────────────────→ Server
双方取较小的 MSS 作为实际使用值。
Linux 命令验证 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 用 ss 命令查看 TCP 连接的 MSS
ss -tin dst 8.8.8.8
# 输出示例:
# skmem:(r0,rb212992,t0,tb212992,f0,w0,o0,bl0,d0)
# ts sack cubic wscale:7,7 rto:204 rtt:4.123/1.234 mss:1460 ...
# 用 tcpdump 抓 TCP 握手,查看 MSS 协商
sudo tcpdump -i eth0 -n 'tcp[tcpflags] & (tcp-syn) != 0' -v
# 在另一个终端发起连接
curl -s https://example.com > /dev/null
# tcpdump 输出中可以看到:
# Options [mss 1460, ...]
# 手动设置连接的 MSS(用于测试)
sudo ip route add 目标IP/32 via 网关IP mtu 1400
# 这会让内核对该路由使用更小的 MTU,从而协商更小的 MSS
# 查看路由的 MTU 设置
ip route show cache
MTU vs MSS:一张图说清楚以太网帧(最大 1518 字节)
┌──────────────────────────────────────────────────────┐
│ Ethernet Header │ IP Packet(MTU=1500) │ FCS │
│ 14B │ │ 4B │
│ │ IP Header │ TCP Segment │ │
│ │ 20B │ │ │
│ │ │ TCP Header │ Data │ │
│ │ │ 20B │ (MSS=1460)│ │
└──────────────────────────────────────────────────────┘
MTU = 1500(IP 层看到的最大包大小)
MSS = 1460(TCP 层实际能装的数据大小)
对比项MTUMSS所在层网络层 / 链路层边界传输层(TCP)单位含义整个 IP 包最大大小TCP 数据载荷最大大小设置方式由网络接口决定TCP 握手时协商超出后果触发 IP 分片或丢包不会超出(提前协商好了)适用协议IP(含 UDP、TCP)仅 TCP进阶:PMTUD(路径 MTU 发现)如果路径上有中间节点 MTU 更小(比如过了一段 VPN),怎么办?
Path MTU Discovery (PMTUD) 的原理:
发送方发出 DF=1(禁止分片)的包。如果某个路由器的 MTU 不够,它会丢掉这个包,并回复 ICMP Type 3 Code 4(需要分片但 DF 位已置位),同时告知自己的 MTU。发送方收到 ICMP 后,缩小包大小重试。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看本机路由缓存中的 PMTU 信息
ip route show cache
# 手动触发 PMTUD 测试(路径上逐步减小包大小)
for size in 1472 1400 1300 1200; do
result=$(ping -s $size -M do -c 1 8.8.8.8 2>&1)
echo "size=$size: $(echo $result | grep -oE 'mtu=[0-9]+|1 received|100% packet loss')"
done
# 如果 ICMP 被防火墙屏蔽(常见的 VPN/云环境问题),PMTUD 会失效
# 这时候可以用 TCP MSS Clamping 作为补救
# 在 iptables 中限制 MSS
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
实战:排查 MTU/MSS 导致的网络问题症状小数据包(ping、DNS)正常,大文件传输卡死或超慢SSH 连接可以建立,但执行命令时没有输出HTTPS 页面加载一半就卡住这是经典的 “MTU Black Hole” 问题,通常发生在 VPN、PPPoE、或 GRE 隧道环境中。
排查步骤 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 步骤1:确认正常路径 MTU
ping -s 1472 -M do -c 3 目标IP # 如果成功,MTU 是 1500
ping -s 1452 -M do -c 3 目标IP # 如果成功,MTU 是 1480(PPPoE 典型)
# 步骤2:检查 ICMP 是否被防火墙屏蔽
sudo tcpdump -i eth0 icmp -n
# 步骤3:检查当前 TCP 连接的实际 MSS
ss -tin | grep mss
# 步骤4:用 tracepath 探测路径上每跳的 MTU
tracepath 目标IP
# 输出示例:
# 1: 192.168.1.1 (pmtu 1500)
# 2: 10.0.0.1 (pmtu 1492) ← 这里 MTU 变小了
# 步骤5:如果确认 MTU 问题,临时修复
sudo ip link set eth0 mtu 1400
# 或者在内核强制 MSS Clamping(见上方 iptables 命令)
总结场景发生了什么应该怎么做以太网直连MTU=1500,MSS=1460,无分片正常,无需操作PPPoE 宽带MTU=1492,MSS=1452路由器自动处理,或手动设置 MSSVPN 隧道隧道头占用额外字节,有效 MTU 下降设置 VPN 接口 MTU,或启用 MSS ClampingUDP 大包可能触发 IP 分片应用层自己控制数据大小,或改用 TCPMTU Black HoleICMP 被屏蔽,PMTUD 失效降低 MTU / 启用 iptables MSS Clamping核心记忆点:
MTU = 链路的物理上限,网络层被动遵守MSS = TCP 主动协商,目的是避免 IP 分片IP 分片 = 性能杀手,现代网络应尽量避免PMTUD = 自动发现路径 MTU 的机制,依赖 ICMP 畅通参考资料:RFC 791(IPv4)、RFC 879(MSS)、RFC 1191(PMTUD)