pathvector-dev

Originally published at https://blog.pathvector.dev/protocol-lab-mss-37/ — part of the free Protocol Lab series.

This post is part of Protocol Lab,一個免費、動手實作的系列課程,透過在容器實驗室中建置與破壞通訊協定來學習網路協定。所有實驗室教材 — 拓撲、設定檔與腳本 — 都放在儲存庫中:github.com/pathvector-studio/protocol-lab

In Lab #25,我們觀察到 Path MTU Discovery 透過 ICMP 找到較小的鏈路 — 也看到了當 ICMP 被過濾時,它如何變成黑洞。本實驗室是操作上的修正:MSS clamping。路徑上的路由器會改寫通過的 TCP SYN 中的 MSS,讓兩端主機一開始就同意傳送能適配最窄鏈路的區段 — 不需要依賴 PMTUD 正常運作。

Reading guide: rfc-notes/mss-clamping.md

Prerequisite: Lab 25: MTU and Path MTU Discovery

預估時間:35–50 分鐘。

目標

用戶端鏈路 MTU 為 1500;r–server 鏈路 MTU 為 1400

  • 沒有 clamping 時,用戶端的 SYN 會宣告 MSS 1460(其本地 1500 − 40);完整區段對 1400 鏈路來說太大,必須依賴 PMTUD。
  • 有 clamping 時,r 會將 SYN 的 MSS 改寫為 1360(1400 − 40),讓兩端傳送的區段永遠能適配。

結束時,您應該能解釋這張表格:

伺服器看到的 SYN MSS 原因
without clamping 1460 用戶端本地 MTU 1500 − 40
with clamping 1360 r 將其 clamp 至 1400-MTU 鏈路(− 40)

您將學到

  • TCP MSS 選項是什麼,以及端點如何從本地 MTU 推導它。
  • 為什麼 SYN MSS 對路徑中更深的較小鏈路是盲目的
  • PMTUD 在 ICMP 被過濾時如何黑洞(Lab 25 的複習)。
  • MSS clamping 如何在路由器上改寫 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(clamping 所支援的機制)
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 chain(mangle table)中,我們將 SYN 的 MSS clamp 至 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 是本地、封閉的範圍。

Note: 這裡全部使用本地/文件位址空間(RFC 1918 / RFC 5737),因此本實驗室不會觸及真正的網際網路。

所需環境

建議環境:

  • Linux / WSL2 / Linux 虛擬機
  • Docker
  • containerlab

使用的映像:

  • nicolaka/netshoot:latest — 內建 iptablestcpdumpcurlpython3

不需要其他映像。

執行實驗室

快速路徑,會自動部署、驗證並拆除:

./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. 觀察未 clamping 時的 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 clamping

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 1360r 已將其 clamp 至 1400 − 40。

預期輸出

  • r 的 eth2:mtu 1400
  • 沒有 clamping:伺服器看到 SYN 帶有 mss 1460
  • 有 clamping:伺服器看到 mss 1360
  • mangle FORWARD chain 顯示 TCPMSS clamp to PMTU 規則。

運作原理

MSS clamping 的概念是「在一開始就告訴兩端安全的區段大小,而不是依賴 PMTUD」。

  • MSS。 TCP 在單一區段中能接受的最大 payload。它只在 SYN 中宣告,數值為 本地 MTU − 40(IPv4):MTU 1500 → 1460。發送端會將其區段上限設為對方宣告的 MSS。
  • 盲點。 每個端點只知道自己的介面 MTU。即使路徑中存在更窄的鏈路(這裡是 1400-MTU 的 r–server 鏈路),SYN 的 MSS 也不會反映它。
  • PMTUD 與黑洞(Lab 25)。當帶有 DF 旗標的過大區段撞上窄鏈路時,路由器會回傳 ICMP「fragmentation needed」,發送端隨之縮小。但如果 ICMP 被過濾,發送端永遠不會知道;大型區段持續被丟棄,連線掛起 — 形成黑洞
  • Clamping。 路徑上的路由器會將通過 SYN 中的 MSS 選項 改寫為輸出鏈路的 MTU − 40(若宣告值更大)。在本實驗室中,r 將 1460 降為 1360。兩端之後會以 1360 或更小傳送,因此區段永遠能適配 1400 鏈路 — 即使 PMTUD 失效也不會黑洞。因為 MSS 只在 SYN 中協商,所以只改寫 SYN 就足夠了。

關鍵洞見:路徑上的路由器將端點看不到的路徑最小 MTU 注入 SYN 的 MSS,讓兩端從第一個位元組開始就傳送適配的區段。 這是真實部署中處理隧道縮小有效 MTU 的常見做法。

常見陷阱

  • 混淆 MSS 與 MTU。 MTU 是整個 IP 封包;MSS 是 TCP payload。在 IPv4 中,MSS = MTU − 40。
  • 認為 MSS 會重新協商。 它只在 SYN 中協商,不會在資料區段中改變 — 這正是改寫 SYN 的 MSS 選項就足夠的原因。
  • 假設 PMTUD 就夠了。 ICMP 過濾可能造成黑洞。Clamping 是保險。
  • 假設只要降低端點的 MTU 即可。 路徑深處的窄鏈路從端點是看不到的。必須由路徑上的路由器進行改寫。
  • 期待它能幫助 UDP。 MSS 是 TCP 的概念;UDP 是另一個問題。
  • Clamp 值。 --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

確認理解

  1. 什麼是 TCP MSS?它與 MTU 有何不同(IPv4 關係為何)?
  2. MSS 何時協商?能否在 SYN 之外改變?
  3. 為什麼 SYN 的 MSS 無法反映路徑中更窄的鏈路?
  4. PMTUD 何時會黑洞?
  5. MSS clamping 改寫什麼、在哪裡改寫?為什麼本實驗室中 1460 會變成 1360?
  6. 為什麼 clamping 在隧道(WireGuard/VXLAN/GRE)中如此有價值?

參考文件

已驗證執行記錄(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:latestiptablestcpdumpcurlpython3

執行 PATH="/tmp/pl-shim:$PATH" ./scripts/labctl.sh run mss-37 執行 deploy → verify → destroy,且 verification.json 回傳 "status": "verified"

沒有 clamping → 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 上啟用 clamping → 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
without clamping 1460
with clamping 1360

清理

containerlab destroy -t mss-37.clab.yml --cleanup

Enter fullscreen mode Exit fullscreen mode


這就是 MSS clamping:路徑路由器上的一條 iptables 規則,讓每個 TCP 連線在傳送第一個資料位元組前就同意安全的區段大小 — 不需要 PMTUD 正常運作。

探索完整的 Protocol Lab 系列:github.com/pathvector-studio/protocol-lab。如果這些實驗室對您有幫助,請在 GitHub 上 ⭐ 為儲存庫按讚 — 這真的能幫助其他人找到這個專案。

接下來,我們將繼續探討當教科書機制失效時,真實網路如何保持可靠。