1. 程式人生 > 其它 >【網路排查】定位防火牆(一):傳輸層的對比分析

【網路排查】定位防火牆(一):傳輸層的對比分析

結合傳輸層和應用層的分析推理

無論防火牆有多麼神祕,它本質上是一種網路裝置。既然是網路裝置,那麼它必然同樣遵循我們知道的技術原理和網路規範。

這裡傳輸層當然就是指 TCP/UDP,應用層就是問題表象,比如超時、報錯之類。我們來看一個具體的例子。

內部的一個應用 A 訪問應用 B 的時候,經常耗時過長,甚至有時候事務無法在限定時間內完成,就導致報錯。而且我們發現,問題都是在訪問 B 的 HTTPS 時發生的,訪問 B 的 HTTP 就一切正常。因為應用 A 對時間比較敏感,開發團隊希望能既解決事務失敗的問題,也改善事務處理慢的問題,於是我們運維團隊開始調查。那麼,我們先來看一下這個案例中,應用請求路徑的大體示意圖:

 

 

 應用 A 和應用 B 各自都是一組獨立的叢集(多臺伺服器)。A 的眾多機器,都訪問 B 的位於負載均衡(LB)上的 VIP(虛擬 IP),然後 LB 再把請求轉發給 B 的機器。這裡的 HTTP 和 HTTPS 都位於同一個虛擬 IP,只是服務埠不同而已(一個是 80,一個是 443)。

既然問題是“A 覺得 B 很慢”,那麼,我們除了聽聽 A 的抱怨,是否也該問問 B 的解釋,才顯得比較公正呢?這就是我們選擇做“兩側抓包”的背後的考量了。

所以,我們就在 A 中選擇了一臺機器(即客戶端)做 tcpdump 抓包,同時在 LB 的 HTTPS VIP(即服務端)上也進行抓包。

  • 各端的抓包過濾條件一般以對端 IP 作為條件,比如 tcpdump dst host {對端 IP},這樣可以過濾掉無關的流量。
  • 兩端的抓包應該差不多在同時開始和結束,這樣兩端的報文就有儘量多的時間是重合的,便於對比分析。
  • 在同時抓包的時間段內,要把問題重現,也就是邊重現,邊抓包。至於如何重現,又分兩種情況:一種是我們知道觸發條件,那麼直接操作發起就好了;另一種是觸發條件未知,那麼只有在抓包的同時,耐心等待問題出現,然後再停止抓包。

我們先來看一下客戶端的報文情況。開啟抓包檔案後,我一般會按部就班地做以下幾件事:

  • 檢視 Expert Information;
  • 重點關注可疑報文(比如 Warining 級別),追蹤其整個 TCP 流;
  • 深入分析這個可疑 TCP 流的第二到四層的報文,再結合應用層表象,綜合分析其根因
  • 結合兩側的抓包檔案,找到屬於同一個 TCP 流的資料包,對比分析,得出結論。

 

 

 檢視 Expert Information

這一步主要是為了獲取整體的網路傳送情況,這對於我們大體判斷問題方向很有幫助。

 

 

 不過,我們要怎麼理解 Expert Information 裡面的各種資訊呢?我來給你挨個介紹一下:

  • Warning 條目的底色是黃色,意味著可能有問題,應重點關注。
  • Note 條目的底色是淺藍色,是在允許範圍內的小問題,也要關注。什麼叫“允許範圍內的小問題”呢?舉個例子,TCP 本身就是容許一定程度的重傳的,那麼這些重傳報文,就屬於“允許範圍內”。
  • Chat 條目的底色是正常藍色,屬於 TCP/UDP 的正常行為,可以作為參考。比如你可以瞭解到,這次通訊裡 TCP 握手和揮手分別有多少次,等等。

 

 

 上圖展示的就是客戶端抓包檔案的情況,我們逐個來解讀這三種不同級別的 Severity(嚴重級別)。

  • Warning:有 7 個亂序(Out-of-Order)的 TCP 報文,6 個未抓到的報文(如果是在抓包開始階段,這種未抓到報文的情況也屬正常)。
  • Note:有 1 個懷疑是快速重傳,5 個是重傳(一般是超時重傳),6 個重複確認。
  • Chat:有 TCP 揮手階段的 20 個 FIN 包,握手階段的 10 個 SYN 包和 10 個 SYN+ACK 包。

一般來說,亂序是應該被重點關注的。因為正常情況下,傳送端的報文是按照先後順序傳送的,如果到了接收端發生了亂序,那麼很可能是中間裝置出現了問題,比如中間的交換機路由器(以及這節課的主角防火牆)做了一些處理,引發了報文亂序。所以自然,這個亂序也容易引發應用層異常。

重點關注問題報文

理解了這幾個嚴重級別所代表的含義之後,我們還是回到 Expert Information 的進一步解讀上來。點選 Warning 左邊的小箭頭,展開亂序的報文集合,我們就能看到這些報文的概覽資訊,如下所示:

 

我習慣上會選擇靠後一點的報文(因為相對靠後的報文所屬的 TCP 流相對更完整),然後跟蹤這些報文(Follow -> TCP Stream),找到所屬的 TCP 流來進一步分析。比如選中 191 號報文,這時主視窗自動定位到了這條報文,我們在主視窗中選中該報文後右單擊,選擇 Follow,在次級選單中點選 TCP Stream:

 

 

 

然後,我們就能看到過濾出來的這個 TCP 流的全部報文了!

 

細心的你可能會發現,介面裡很多欄位不是預設有的,比如 Seq、NextSeq、TCP Seglen 等等,這些其實都是我自定義新增的,目的就是便於分析(新增的辦法我會在下一講裡介紹)。

這時,Wireshark 的顯示過濾器欄出現了tcp.stream eq 8這個過濾器,這是我們剛剛點選 Follow -> TCP Stream 後自動生成的。

一眼看去,整串資料流確實有點問題,因為有好幾個被 Wireshark 標註紅色的報文。我們重點關注下 189、190、191、193、195 這幾個報文。

  • 189:服務端(HTTPS)回覆給客戶端的報文,TCP previous segment not captured 意思是,它之前的報文沒有在應該出現的位置上被抓到(並不排除這些報文在之後被抓到)。
  • 190:客戶端回覆給服務端的重複確認報文(DupAck),可能(DupAck 報文數量多的話)會引起重傳。
  • 191:服務端(HTTPS)給客戶端的報文,是 TCP Out-of-Order,即亂序報文。193:服務端(HTTPS)給客戶端的 TCP Retransmission,即重傳報文。195:也是服務端(HTTPS)給客戶端的重傳報文191:服務端(HTTPS)給客戶端的報文,是 TCP Out-of-Order,即亂序報文。193:服務端(HTTPS)給客戶端的 TCP Retransmission,即重傳報文。195:也是服務端(HTTPS)給客戶端的重傳報文。。 191:服務端(HTTPS)給客戶端的報文,是 TCP Out-of-Order,即亂序報文。
  • 193:服務端(HTTPS)給客戶端的 TCP Retransmission,即重傳報文。
  • 195:也是服務端(HTTPS)給客戶端的重傳報文

以上都是根據 Wireshark 給我們提示的資訊所做的一些解讀,主要是針對 TCP行為方面的,這也是從 Wireshark 中讀取出來的重要資訊之一。另外一個重要的資訊源是耗時(也就是時間列展示的時間間隔)。顯然,在 192 和 193 號報文之間,有 1.020215 秒的時間間隔。

要知道,對於內網通訊來說,時間是以毫秒計算的。一般內網的微服務的處理時間,等於網路往返時間 + 應用處理時間。比如,同機房環境內,往返時間(Round Trip Time)一般在 1ms 以內。比如一個應用本身的處理時間是 10ms,內網往返時間是 1ms,那麼整體耗時就是 11ms。

然而,這裡單單一個 193 號報文就引發了 1 秒的耗時,確實出乎意料。因此我們可以基本判定:這個超長的耗時,很可能就是導致問題的直接原因。

結合應用層做深入分析

那麼,為什麼會有這個 1 秒的耗時呢?

TCP 裡面有重傳超時的設計,也就是如果傳送端傳送了一個數據包之後,對方遲遲沒有迴應的話,可以在一定時間內重傳。這個“一定時間”就是 TCP 重傳超時(Retransmission Timeout)。顯然,這裡的 1 秒,很可能是這個重傳超時的設計導致的。

為了確認這件事,我們就需要做這次分析裡最為關鍵的部分了:兩端報文的對比分析。我們最好有一個大一點的顯示屏,開啟這兩個抓包檔案,並且把兩個 Wireshark 視窗靠近一些,更方便我們肉眼對兩邊報文進行比較。

不過,當我們開啟服務端(LB)的抓包檔案,看到的卻也是一大片報文。而要比較,必然要找到同樣的報文才能做比較。這也是一個不小的難點:如何才能在服務端抓包檔案裡,定位到客戶端的 TCP 流呢?我們接著往下看。

對比兩側檔案

其實,找到另一端的對應 TCP 流的技巧是:用 TCP 序列號。

我們知道,TCP 序列號的長度是 4 個位元組,其本質含義就是網路 IO 的位元組位置(等價於檔案 IO 的位元組位置)。因為是 4 個位元組,最大值可代表 4GB(即 2 的 32 次方)的資料,也就是如果一個流的資料超過 4GB,其序列號就要回繞複用了。不過一般來說,因為這個值的範圍足夠大,在短時間(比如幾分鐘)碰巧相同的概率幾乎為零,因此我們可以以它作為線索,來精確定位這個 TCP 流在兩端抓包檔案中的位置。

首先,我們可以記錄下客戶端側抓包檔案中,那條 TCP 流的某個報文的 TCP 序列號。比如選擇 SYN 包的序列號,是 4022234701:

 

 

 注意,這裡必須選裸序列號(Raw Sequence Number)。Wireshark 主窗口裡顯示的序列號是處理過的“相對序列號”,也就是為了方便我們閱讀,把握手階段的初始序列號當 1 處理,後續序列號相應地也都減去初始序列號,從而變成了相對序列號。

但是顯然,這樣處理後,無論在哪個 TCP 流裡面,Wireshark 展示的握手階段序列號都是 1,後續序列號也都是 1+ 載荷位元組數。相對序列號肯定是到處“撞車”的,所以不能作為選取的條件。

那麼,檢視裸序列號的方法是怎麼樣的呢?

開啟 Wireshark 的 Preference(配置)選單:

 

在彈出選單的左側選擇 Protocols,選中其中的 TCP,然後在右側的選項中,把“Relative sequence numbers”前面的勾去掉,就可以顯示裸序列號了:

 

 

 然後,我們再到服務端抓包檔案裡輸入過濾器:tcp.seq_raw eq 4022234701,得到同樣的這個 SYN 包:

 

 

 

 正是我們的搜尋條件是裸序列號,所以開啟服務端抓包檔案的那個 Wireshark 窗口裡才可以搜到這個報文。這也是利用了裸序列號在網路上傳輸是不會發生變化的這一特性。

接下來還是 Follow -> TCP Stream,翻出來這個 SYN 包所屬的服務端抓包裡的 TCP 流。

好了,現在我們睜大眼睛,來仔細比對這些報文的對應情況:

 

 

 左側是服務端抓包,右側是客戶端抓包。前 4 個報文的順序沒有任何變化,但服務端隨後一口氣傳送的 4 個包(這裡叫它們 1、2、3、4 吧),到了客戶端卻變成了 4、1、2、3!這也就是 Wireshark 提示我們的:

  • Out-of-Order:包 1、2、3。
  • TCP previous segment not captured:包 4。

下面,我們再從服務端的角度,來看一下報文順序、重傳、1 秒耗時這三者間的關係:

 

 

 

 前面剛說到“服務端傳送 4 個報文後,客戶端收到的是 4、1、2、3”。因為後面 3 個報文的順序還是正確的,真正亂序的其實只是 4,所以就導致了這樣一個狀況:亂序是亂序的,但是“不夠亂”,也就是不能滿足快速重傳的條件“3 個重複確認”。
這樣的話,服務端就不得不用另外一種方式做重傳,即超時重傳。當然,這裡的 1 秒超時是硬體 LB 的設定值,而 Linux 的預設設定是 200 毫秒。

不過,撇開這些細節不談,我們現在知道了一個重要的事實:客戶端和服務端之間,有報文亂序的情況。

我們查看了其他 TCP 流,也有很多類似的亂序報文,而這種程度的亂序發生在內網是不應該的,因為內網比公網要穩定很多。以我個人的經驗,內網環境常見的丟包率在萬分之一上下,亂序的機率我沒有嚴格考證過,因為跟各個環境的具體拓撲和配置的關係太大了。但從經驗上看,亂序機率大概在百分之一以下到千分之一左右都屬正常。

我們把這兩個抓包檔案以及分析過程和推論,發給了網路安全部門。他們對於實際的抓包資訊也很重視,經過排查,發回了一個我們“期待已久”但一直無法證實的推測:問題出現在防火牆上!

具體來說,是這樣兩個事實:

  1. 在客戶端和服務端之間,各有一道防火牆,兩者之間設立有隧道;
  2. 因為軟體 Bug 的問題,這個隧道在大包的封包拆包的過程中,很容易發生亂序

就像下圖這樣:

 

 兩側抓包,對比分析。就是這樣一個方法,最終讓我們發現了防火牆方面的問題。我們做了裝置升級,效果是立竿見影,事務慢的問題完全消失了。開發團隊也非常感謝我們的排查工作。

為什麼隧道會引發亂序?

首先,隧道本身並不直接引起亂序。隧道是在原有的網路封裝上再加上一層額外的封裝,比如 IPIP 隧道,就是在 IP 頭部外面再包上一層 IP 頭部,於是形成了在原有 IP 層面裡的又一個 IP 層,即“隧道”(各種隧道技術也是 SDN 技術的核心基礎)。由於這個封裝和拆封都會消耗系統資源,加上程式碼方面處理不好,那麼出 Bug 的概率就大大增加了。這就是在這個案例裡,隧道會引發亂序的原因。

為什麼 HTTP 事務沒有被影響,只有 HTTPS 被影響?

在這個案例裡,HTTP 確實一直沒有被影響到。因為從抓包來看,這個場景的 HTTP 的 TCP 載荷,其實遠沒有達到一個 MSS 的大小。我們來看一下當時的 HTTP 抓包:

 

 TCP 載荷只有兩三百位元組,遠小於 MSS 的 1460 位元組。這個跟隧道的關係是很大的,因為隧道會增加報文的大小。

比如通常 MTU 為 1500 位元組的 IP 報文,做了 IPIP 隧道封裝後,就會達到 1520 位元組,所以一般有隧道的場景下,主機的 MTU 都需要改小以適配隧道需求。如果網路沒有啟用 Jumbo Frame,那這個 1520 位元組的報文,就會被路由器 / 防火牆拆分為 2 個報文。而到了接收端,又得把這兩個報文合併起來。這一拆一合,出問題的概率就大大增加了。

事實上,在大包情況下,這個隧道引發的是兩種不同的開銷:

  • IPIP 本身的隧道頭的封包和拆包;
  • IP 層因為超過 MTU 而引發的報文分片和合片。

因為 HTTPS 是基於 TLS 加密的,TLS 握手階段的多個 TCP 段(segment)就都撐滿了 MSS(也就是前面分析的 1、2、3 的資料包),於是就觸發了防火牆隧道的 Bug。