一. 背景
近期有部分使用者反饋我司某款 APP 內容一直載入失敗,我們進行了排查與記錄,並分享給大家
在開始正文之前,我們先來回顧一些基礎知識:
MTU 是二層協議裡面的最大傳輸單元,指幀內容的最大值,不包括幀頭和 FCS。乙太網 MTU 標準是 1500 bytes
MSS 是指 TCP 最大有效載荷,通常會在 SYN 包中宣告。中間裝置可以修改 MSS 大小,專業術語稱為 MSS Clamping
在實踐中,傳輸大量資料時,TCP 傾向於傳送滿載的資料包
Wireshark 中展示的幀長度不包括 4 bytes 的 FCS
二. 問題排查
先上一張抓包截圖,看看你能不能發現其中的異常
從這張圖中一眼可以看出最後幾個資料包 Client 一直在重傳,但是 Server 沒有 Ack
順著重傳包往上看,會發現被重傳的是 Client 發起的序號為 16 的包。同時期間有 Server 傳送的資料包被 Client Ack 了,這說明 Server 和 Client 還能互動,但是 Server 卻沒有收到 16 號包
那麼問題來了,為什麼 Server 沒有收到 16 號包
來看看 16 號包有什麼特徵,Length 1514,是一個滿載的資料幀
我們再往上倒一眼,看到 Server 連續傳送的 6、7、8、9 四個包中,前三個 Length 都是 1506。這裏疑點就出現了,為什麼 Length 不是 1514。說明很可能中間裝置修改了 Client 發給 Server 的 SYN 包中的 MSS,改爲了 1452。這裏我們有理由懷疑中間裝置的 MTU 傳送了變化
再往上從 2 號包還可以看到 Server 發給 Client 的 SYN 包 MSS 是 1460,常規大小,沒有變化。這裏我們有理由懷疑中間裝置對 MSS 的處理出現了異常,只處理了單向的 MSS。導致 Client 沒有感知到路徑上 MTU 的變化,導致滿載 1514 大小的資料幀被丟棄
上面只有部分證據,怎麼進一步證實呢:
用 ping 傳送一個滿載的禁止分片的包來探測
ping -M do -s 1472 -c 3 -i 0.2 ip
運氣好的話,你會收到類似下面這種明顯的提示。我們確實收到了:-),這就證明了我們上面的猜測
ping: local error: Message too long, mtu=1492
如果沒有明確的提示也可以透過調整 -s 引數逐步降低載荷,探測路徑最小 MTU
如果想探測到具體是哪一跳的 MTU 異常,可以透過指定 TTL 來探測:
ping -M do -s 1472 -c 3 -i 0.2 ip -t 3
到這裏分析還沒有結束,讓我們更深入業務來看一看。已知上面的請求是普通的 HTTP API Get 請求, 不涉及資料上傳。暫停一會兒,你有看出其中反常的地方嗎
如果沒有,再來看一張 TLS 解密後的 HTTP API Get 請求,不知你是否看出其中端倪
下面揭曉答案
通常來說,Client 端傳送的 API 類請求原始資料遠小於 1460,再加上 HTTP/2 的頭部壓縮,資料量會更小,很少出現滿載的資料包
所以我們和業務同學做了確認,原來是因為業務需求在新版本請求中加了一些非常大的引數。使用者的報障也是從新版本上線後多起來的,回退到老版本可以避免觸發相關問題。業務同學根據我們的反饋也在進行相應的最佳化
老版本請求大小:
新版本請求大小:
Tips: 如果你想要觀察 TLS 解密後的資料包,可以配置 SSLKEYLOGFILE 環境變數記錄 TLS 會話金鑰,再用 Wireshark 解密。Chrome、Curl、Firefox 等都支援這種方法
三. 結語
到此我們的分析就結束啦。抓包並分析是一種非常高效的 debug 方法,已經幫助筆者解決了不少問題。
不過筆者在處理此 case 時遠沒有文中那麼順暢,有很多細碎知識點運用並不熟練,初期沒有相互關聯起來。好在一個問題有多個觀察麵,念念不忘,逐漸蒐集證據和知識,終於破案。以此記錄,希望對你能有所助益。