最初发布于 https://blog.pathvector.dev/protocol-lab-mss-37/ —— 免费 Protocol Lab 系列的一部分。
本文属于 Protocol Lab,这是一个免费的动手系列,通过在容器实验室中构建和破坏协议来学习网络协议。所有实验材料——拓扑、配置和脚本——都存放在仓库中:github.com/pathvector-studio/protocol-lab。
在 Lab #25 中,我们观察了 Path MTU Discovery 通过 ICMP 发现更小的链路——以及当该 ICMP 被过滤时它如何黑洞。本实验是操作性修复:MSS 钳制。路径上的路由器重写经过的 TCP SYN 中的 MSS,让两端事先协商发送能适配最窄链路的报文段——无需依赖 PMTUD 正常工作。
预计用时:35–50 分钟。
目标
客户端链路 MTU 为 1500;r–server 链路 MTU 为 1400:
- 不钳制时,客户端的 SYN 通告 MSS 1460(其本地 1500 − 40);完整报文段对 1400 链路而言过大,依赖 PMTUD。
-
钳制时,
r将 SYN 中的 MSS 下调至 1360(1400 − 40),让两端始终发送适配的报文段。
完成后,你应能解释下表:
| 服务器看到的 SYN MSS | 原因 | |
|---|---|---|
| 不钳制 | 1460 | 客户端本地 MTU 1500 − 40 |
| 钳制 | 1360 |
r 钳制到 1400-MTU 链路(− 40) |
你将学到
- TCP MSS 选项是什么,以及端点如何从本地 MTU 推导它。
- 为什么 SYN 中的 MSS 对路径中更小的链路是盲的。
- 当 ICMP 被过滤时,PMTUD 为什么会黑洞(Lab 25 回顾)。
- 路由器上的MSS 钳制如何重写 SYN 中的 MSS 以适配路径。
- 为什么这对会缩小有效 MTU 的隧道(WireGuard/VXLAN/GRE)很重要。
本实验不涵盖:
- 完整的 PMTUD 内部机制(Lab 25 涵盖)。
- IPv6 MSS 细节(使用更大最小值时 MSS = MTU − 60)。
- 每路由 MTU 固定或 PLPMTUD(RFC 8899)。
RFC 中可阅读的位置
| 参考 | 重点 |
|---|---|
| RFC 9293 §3.7.1 | MSS 选项及报文段大小的决定方式 |
| RFC 1191 | PMTUD(钳制回退的机制) |
| RFC 2923 | PMTUD 黑洞问题 |
| RFC 5737 / RFC 1918 | 确认此处使用的地址仅限本地 |
全景
r 位于客户端与服务器之间,且只有 r–server 链路 MTU 为 1400。
client ---- eth1/eth1 ---- r ---- eth2/eth1 ---- server
MTU 1500 MTU 1400 (narrow) MTU 1400
SYN: mss 1460 ---> r clamps ---> mss 1360 at server
Enter fullscreen mode Exit fullscreen mode
在 r 的 FORWARD 链(mangle 表)中,我们将 SYN 的 MSS 钳制到 PMTU。
flowchart LR
C["client (MTU 1500)<br/>SYN mss 1460"] --> R["r<br/>TCPMSS --clamp-mss-to-pmtu"]
R -->|"SYN rewritten<br/>mss 1360"| S["server (MTU 1400)"]
Enter fullscreen mode Exit fullscreen mode
10.0.0.0/8 是本地封闭范围。
注意: 本实验全部使用本地/文档地址空间(RFC 1918 / RFC 5737),因此不会触及真实互联网。
你需要的环境
推荐环境:
- Linux / WSL2 / Linux 虚拟机
- Docker
- containerlab
使用的镜像:
-
nicolaka/netshoot:latest—— 包含iptables、tcpdump、curl和python3。
无需额外镜像。
运行实验
快速路径,脚本会为你完成部署、验证和清理:
./scripts/labctl.sh run mss-37
Enter fullscreen mode Exit fullscreen mode
或手动逐步执行:
1. 进入工作目录
cd protocol-lab/examples/mss-37
Enter fullscreen mode Exit fullscreen mode
2. 部署并启动 HTTP 服务器
sudo containerlab deploy -t mss-37.clab.yml
docker exec -d clab-mss-37-server python3 -m http.server 80
docker exec clab-mss-37-r ip -br link show eth2 # mtu 1400
Enter fullscreen mode Exit fullscreen mode
3. 查看不钳制时的 SYN MSS
docker exec -d clab-mss-37-server sh -c 'tcpdump -i eth1 -n -c1 "tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0" > /tmp/syn.txt 2>&1'
docker exec clab-mss-37-client curl -s --max-time 4 http://10.0.8.2/ >/dev/null
docker exec clab-mss-37-server grep -oE 'mss [0-9]+' /tmp/syn.txt
Enter fullscreen mode Exit fullscreen mode
你应看到 mss 1460 —— 客户端本地 1500 − 40。
4. 在 r 上启用 MSS 钳制
docker exec clab-mss-37-r iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
Enter fullscreen mode Exit fullscreen mode
5. 再次查看 SYN MSS
docker exec -d clab-mss-37-server sh -c 'tcpdump -i eth1 -n -c1 "tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0" > /tmp/syn2.txt 2>&1'
docker exec clab-mss-37-client curl -s --max-time 4 http://10.0.8.2/ >/dev/null
docker exec clab-mss-37-server grep -oE 'mss [0-9]+' /tmp/syn2.txt
Enter fullscreen mode Exit fullscreen mode
现在是 mss 1360 —— r 已将其钳制到 1400 − 40。
预期输出
-
r的 eth2:mtu 1400。 - 不钳制时:服务器看到 SYN 携带
mss 1460。 - 钳制后:服务器看到
mss 1360。 - mangle FORWARD 链显示
TCPMSS clamp to PMTU规则。
工作原理
MSS 钳制即“在开始时告知两端一个安全的报文段大小,而不是依赖 PMTUD”。
- MSS。 TCP 在一个报文段中能接受的最大净荷。仅在 SYN 中通告,值为 本地 MTU − 40(IPv4):MTU 1500 → 1460。发送方将报文段大小限制为对端通告的 MSS。
- 盲区。 每个端点只知道自己接口的 MTU。即使路径中存在更窄的链路(此处为 1400-MTU 的 r–server 链路),SYN 中的 MSS 也不会反映它。
- PMTUD 与黑洞(Lab 25)。当设置 DF 的大报文段遇到窄链路时,路由器返回 ICMP “需要分片”,发送方缩小尺寸。但若该 ICMP 被过滤,发送方永远不知道;大报文段持续被丢弃,连接挂起——这就是黑洞。
-
钳制。 路径上的路由器将经过 SYN 中的 MSS 选项 重写为出接口 MTU − 40(若通告值更大)。本实验中,
r将 1460 降为 1360。两端随后以 1360 或更小发送,因此报文段始终适配 1400 链路——即使 PMTUD 失效也不会黑洞。仅重写 SYN 就足够,因为 MSS 仅在 SYN 中协商。
核心洞见:路径上的路由器将端点不可见的路径最小 MTU 注入 SYN 的 MSS,使两端从第一个字节起就发送适配的报文段。 这是隧道场景下的常用手段。
常见陷阱
- 混淆 MSS 与 MTU。 MTU 是整个 IP 包;MSS 是 TCP 净荷。IPv4 中,MSS = MTU − 40。
- 认为 MSS 可重新协商。 它仅在 SYN 中协商,不会随数据报文段改变——这正是重写 SYN 的 MSS 选项就足够的原因。
- 认为 PMTUD 就够了。 ICMP 过滤会导致黑洞。钳制是保险。
- 认为只需降低端点的 MTU。 路径中深处的窄链路对端点不可见。必须由路径上的路由器执行重写。
- 期望它对 UDP 有帮助。 MSS 是 TCP 概念;UDP 是另一回事。
-
钳制值。
--clamp-mss-to-pmtu基于出接口 MTU 推导;固定值请用--set-mss。
清理
sudo containerlab destroy -t mss-37.clab.yml --cleanup
Enter fullscreen mode Exit fullscreen mode
若你使用 labctl.sh run mss-37,脚本会在结束时自动执行 destroy。
理解自测
- 什么是 TCP MSS?它与 MTU 有什么区别(IPv4 关系)?
- MSS 何时协商?能否在 SYN 之外改变?
- 为什么 SYN 中的 MSS 无法反映路径中更窄的链路?
- PMTUD 何时会黑洞?
- MSS 钳制重写什么、在何处重写?本实验中 1460 为什么变成 1360?
- 为什么隧道(WireGuard/VXLAN/GRE)特别需要钳制?
参考
- RFC 9293: Transmission Control Protocol
- RFC 1191: Path MTU Discovery
- RFC 2923: TCP Problems with Path MTU Discovery
- RFC 5737: IPv4 Address Blocks Reserved for Documentation
已验证运行日志(2026-07-07)
本实验已在真实硬件上确认可复现。
环境:
- Ubuntu 26.04 LTS(内核 7.0.0-27-generic,x86_64)
- Docker 29.1.3
- containerlab 0.77.0
- client / r / server:
nicolaka/netshoot:latest(iptables、tcpdump、curl、python3)
运行 PATH="/tmp/pl-shim:$PATH" ./scripts/labctl.sh run mss-37 执行 deploy → verify → destroy,verification.json 返回 "status": "verified"。
不钳制 → SYN 携带 MSS 1460
在服务器的 eth1 上捕获客户端的 SYN:
Flags [S], seq ..., options [mss 1460, ...]
Enter fullscreen mode Exit fullscreen mode
这就是 MSS 1460,来自客户端链路 MTU 1500 —— 对 r–server 链路(MTU 1400)而言过大,连接依赖 PMTUD。
在 r 上钳制后 → SYN 携带 MSS 1360
mangle FORWARD: TCPMSS tcp flags:0x06/0x02 TCPMSS clamp to PMTU
Enter fullscreen mode Exit fullscreen mode
添加 iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 后,服务器捕获到的同一客户端 SYN 显示:
mss 1360
Enter fullscreen mode Exit fullscreen mode
r 将 SYN 的 MSS 选项从 1460 → 1360(1400 − 40)重写,以匹配其出接口(eth2,MTU 1400)。此后两端发送 1360 字节或更小的报文段,因此一切都适配窄链路——即使 ICMP 被过滤也不会黑洞。
| 服务器看到的 SYN MSS | |
|---|---|
| 不钳制 | 1460 |
| 钳制 | 1360 |
清理
containerlab destroy -t mss-37.clab.yml --cleanup
Enter fullscreen mode Exit fullscreen mode
这就是 MSS 钳制:路径路由器上的一条 iptables 规则,让每个 TCP 连接在发送第一个字节前就协商好安全的报文段大小——无需 PMTUD 正常工作。
探索完整的 Protocol Lab 系列:github.com/pathvector-studio/protocol-lab。如果这些实验对你有帮助,请在 GitHub 上 ⭐ 为仓库点星——这确实能帮助他人发现该项目。
接下来,我们将继续探讨当教科书机制失效时,真实网络如何保持可靠。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.