從Linux 2.6.8核心的一個TSO/NAT bug引出的網路問題排查觀點

qnr123456發表於2015-07-09

夢中沒有錯與對,夢中沒有恨和悔...最好閉上你的嘴,這樣才算可愛...我不會說:這不公道,我不能接受。我會用樸素的文字記錄點點滴滴,早上4點多起來,一氣呵成近期的收穫與評價,憤怒與懺悔。


四年多前的一個往事

大約在2010年的時候,我排查了一個問題。問題描述如下:


服務端:Linux Kernel 2.6.8/192.168.188.100
客戶端:Windows XP/192.168.40.34
業務流程(簡化版):
1.客戶端向服務端發起SSL連線
2.傳輸資料

現象:SSL握手的時候,服務端傳送Certificate特別慢。


分析:
具體思路,也就是當時怎麼想到的,我已經忘了,但是記住一個結論,那就是糾出了Linux 2.6.8的NAT模組的一個bug。
在抓取了好多資料包後,我發現本機總是發給自己一個ICMP need frag的報錯資訊,發現服務端的Certificate太大,超過了本機出網路卡的MTU,以下的一步步的思路,最終糾出了bug:

1.證實服務端程式設定了DF標誌。這是顯然的,因為只有DF標誌的資料包才會觸發ICMP need frag資訊。


2.疑問:在TCP往IP傳送資料的時候,會檢測MTU,進而確定MSS,明知道MSS的值,怎麼還會傳送超限的包呢?計算錯誤可能性不大,畢竟Linux也是準工業級的了。


3.疑問解答:幸虧我當時還真知道一些名詞,於是想到了TCP Segment Offload這個技術。

                     TCP Segment Offload簡稱TSO,它是針對TCP的硬體分段技術,並不是針對IP分片的,這二者區別應該明白,所以這與IP頭的DF標誌無關。對於IP分片,只有第一個分片才會有完整的高層資訊(如   果頭長可以包括在一個IP分片中的話),而對於TSO導致的IP資料包,每一個IP資料包都會有標準的TCP頭,網路卡硬體自行計算每一個分段頭部的校驗值,序列號等頭部欄位且自動封裝IP頭。它旨在提高TCP的效能。


4.印證:果然伺服器啟用了TSO


5.疑問:一個大於MTU的IP報文傳送到了IP層,且它是的資料一個TCP段,這說明TCP已經知道自己所在的機器有TSO的功能,否則對於本機始發的資料包,TCP會嚴格按照MSS封裝,它不會封裝一個大包,然後讓IP去分片的,這是由於對於本機始發而言,TCP MSS對MTU是可以感知到的。對於轉發而言,就不是這樣了,然而,對於這裡的情況,明顯是本機始發,TCP是知道TSO的存在的。


6.猜測:既然TCP擁有對TSO的存在感知,然而在IP傳送的時候,卻又丟失了這種記憶,從TCP發往IP的入口,到IP分片決定的終點,中間一定發生了什麼嚴重的事,迫使TCP丟失了TSO的記憶。


7.質疑:這種故障情況是我在公司模擬的,透過報告人員的資訊,我瞭解到並不是所有的情況都會這樣。事實上,我一直不太承認是Linux協議棧本身的問題,不然早就被Fix了,我一直懷疑是外部模組或者一些外部行為比如抓包導致的。


8.可用的資訊:到此為止,我還有一個資訊,那就是隻要載入NAT模組(事實上這是分析出來的,報告人員是不知道所謂的NAT模組的,只知道NAT規則)就會有這個現象,於是目標很明確,死盯NAT模組。


9.開始debug:由於Linux Netfilter NAT模組比較簡單,根本不需要高階的可以touch到記憶體級的工具,只需要printk即可,但是在哪裡print是個問題。


10.出錯點:在呼叫ip_fragment(就是該函式里面傳送了ICMP need frag)之前,有一個判斷(省略了不相關的):
  1. if (skb->len > dst_pmtu(skb->dst) && !skb_shinfo(skb)->tso_size) {  
  2.     return ip_fragment(skb, ip_finish_output);  
  3. }  

前一個判斷顯然為真,如果要想呼叫ip_fragment的話,後一個判斷一定要是假,實際上,如果開啟了TSO,就不該呼叫ip_fragment的。


11.查詢tso_size欄位:事情很明顯了,一定是哪個地方將tso_size設定成了0!而且一定在NAT模組中(98%以上的可能性吧...),於是在NAT模組中查詢設定tso_size的地方。


12.跟蹤ip_nat_fn:這是NAT的入口,進入這個入口的時候,tso_size不是0,可是呼叫了skb_checksum_help之後tso_size就是0了,問題一定在這個函式中,注意,呼叫這個help有一個前提,那就是硬體已經計算了校驗和。在這個help函式中,有一個skb_copy的操作,正是在這個copy之後,tso_size變成了0,於是進一步看skb_copy,最終定位到,copy_skb_header的最後,並沒有將原始skb的tso_size複製到新的skb中,這就是問題所在!


13.觸發條件:什麼時候會呼叫skb_copy呢?很簡單,如果skb不完全屬於當前的執行流的情況下,按照寫時複製的原則,需要複製一份。故障現象就是慢,而資料為本機始發,且為TCP。我們知道,TCP在沒有ACK之前,skb是不能被刪除的,因此當前的skb肯定只是一個副本,因此就需要複製一份了。


14.影響:如此底層的一個函式。搜尋程式碼,影響巨大,各種慢!對於那次的慢,其慢的流程為:socket傳送DF資料--感知TSO--丟失TSO--ICMP need frag--TCP裁成小段繼續傳送...如果禁止了lo的ICMP,那麼更慢,因為TCP會觸發超時重傳,而不是ICMP的建議裁減,並且重傳是不會成功的,直到使用者程式感知,自行減小傳送長度。

為什麼舊事重提

提起那件事有兩個原因,其一是當時沒有記錄下來整個過程,可是後續的patch卻一直在用,最終我自己都快不知其所以然了,其二,是透過那次的分析,按照現在的理解,就可以發現Linux協議棧的一個最佳化點,即TCP情況下,由於保留了資料skb佇列直到ack,那麼後續向下的所有skb處理流程都至少要經過一次skb_copy,這種複製操作難道就不能避開嗎?如果載入了某些Netfilter鉤子,需要對skb進行寫操作,這種序列化行為會嚴重影響Linux網路協議棧的處理效率,這是Netfilter的通病之一。

附:skb操作的最佳化點

1.如果把資料和後設資料徹底分開是不是更好呢?
2.進一步將寫操作的粒度細分

   有些寫操作是針對每一個資料包的,這些不得不復制,但是能否區域性複製,然後採取分散聚集IO進行拼接呢?儘量採用指標操作而不是複製資料本身,這正是借鑑了UNIX fork模型以及虛擬地址空間的COW。如果把skb的空間進行細粒度劃分,那麼就可以做到,需要COW哪部分就只有那部分,不會導致全域性複製。

前幾天的一個TCP問題排查過程

現象與過程

早就習慣了那種驚心動魄的三規制度(規定的時間,規定的地點,和規定的人一起解決問題),反而不習慣了按部就班了。事情是這樣的。

       週末的時候,中午,正在跟朋友一起聊天吃飯,收到了公司的簡訊,說是有一個可能與TCP/IP有關的故障,需要定位,我沒有隨即回覆,因為這種事情往往需要大量的資訊,而這些資訊一般簡訊傳來的時候早就經過了N手,所以為了不做無用功,等有關人員打電話給我再說吧。

       ...


 (以下描述有所簡化)
我方服務端:Linux/IP不確定(處在內網,不知道NAT策略以及是否有代理以及其它七層處理情況)
測試客戶端:Windows/192.168.2.100/GW 192.168.2.1
中間鏈路:公共Internet
可用接入方式:3G/有線撥號
服務端裝置:第三方負載均衡裝置。防火器等
業務流程:客戶端與服務端建立SSL連線
故障:
客戶端連線3G網路卡使用無線鏈路,業務正常;客戶端使用有線鏈路,SSL握手不成功,SSL握手過程的Client Certificate傳輸失敗。


分析:

1.透過抓包分析,在有線鏈路上,傳送客戶端證書(長度超過1500)後,會收到一條ICMP need frag訊息,說是長度超限,鏈路MTU為1480,而實際傳送的是1500。透過無線鏈路,同樣收到了這個ICMP need frag,只是報告的MTU不同,無線鏈路對應的是1400。


2.有線鏈路,客戶端接受ICMP need frag,重新傳送,只是截掉了20位元組的長度,然而抓包發現客戶端會不斷重傳這個包,始終收不到服務端的ACK,其間,由於客戶端久久不能傳送成功資料到服務端,服務端會回覆Dup ACK,以示催促。


3.猜想:起初,我以為是時間戳的原因,由於兩端沒有開啟TCP時間戳,所以在RTT以及重傳間隔估算方面會有誤差,但是這不能解釋100%失敗的情形,如果是由於時間戳計算的原因,那不會100%失敗,因為計算結果受波動權值影響會比較大。


4.對比無線鏈路,和有線鏈路的唯一區別就是ICMP報告的MTU不同。


5.中途總結:
5.1.此時,我並沒有把思路往運營商鏈路上引導,因為我始終認為那不會有問題,同樣,我也不認為是SSL的問題,因為錯誤總是在傳送大包後呈現,事實上,接受了ICMP need frag後,之前發的那個超限包已經被丟棄,重新傳送的是一個小一點的包,對於TCP另一端來講,這是完全正常的。
5.2.根本無需檢視服務日誌,因為還沒有到達那個層次。抓包結果很明確,就是大包傳不過去,其實已經按照MTU發現的值傳輸了,還是過不去,而無線鏈路能過去。因此應該不是MTU的問題。

5.3.除了運營商鏈路,MTU,服務端處理之外,還會是哪的問題呢?事實上,程式的bug也不是不可能的,或者說是一些不為人知的動作,不管怎樣,需要隔離問題。


6.猜測是中間某臺裝置沒法處理大包,這個和MTU沒有關係,可能就是它處理不了或者根本上不想處理大包,多大呢?反正1480的包處理不了,減去IP頭,TCP頭,剩餘的是1440的純資料。於是寫一個簡單的TCP client程式,在TCP握手完成後馬上傳送(為了防止由於不是Client Hello而主動斷開,因此必須馬上發,只是為了觀察針對大包的TCP ACK情況,此時與服務無關)長度1440的資料,驗證!


7.果然沒有ACK迅速返回,客戶端不斷重試傳送1440的包(之後10秒到20秒,會有ACK到來,但不是每次都會到來,這明顯是不正常的)。為了證明這種方式的合理性,傳送無線鏈路上MTU限制的資料大小,即1400-20-20=1360的資料,ACK秒回。因此猜測中間裝置的資料包處理的長度臨界點在1360和1440之間。


8.經過不斷的測試,二分法查詢臨界點,找到了1380是可處理長度臨界點。傳送1380的純資料是正常的,傳送1381的純資料就不正常了。抓包的目標地址是12.23.45.67,簡稱MA,現在不確定的是MA是什麼,是我方的裝置,還是它方的裝置,如果是我方的裝置,排錯繼續,如果不是,排錯終止。總之,1380這個臨界點是一個疑點,常規來講是不正常的,但也不能排除有這麼限制的正常理由。無線鏈路沒有問題是因為無線鏈路的MTU比較小,最大純資料長度1360小與臨界值1380。


9.補充測試,模擬問題機器,將其本機的MTU改為1380+20+20=1420,傳輸也是正常的,然而改為1421,就不行了。(注意,只有本機的MTU修改才有效,因為只有TCP資料始發裝置,MSS才與MTU關聯)


.....


1x.第9步後面的排查我沒有參與,但是最終,我方裝置確實沒有收到客戶端SSL握手過程傳出的證書,說明確實是中間裝置阻止了這個”大包“的傳輸,至於它到底是誰,到底怎麼回事,與我們無關了,但對於我個人而言,對其還是比較感興趣的。

對於該次排錯的總結

這是一個典型的網路問題,涉及到IP和TCP,細節不多,但足夠典型。其實這個問題與最終的業務邏輯沒有關係,但是事實往往是,只有在業務邏輯無法正常時,這類底層的問題才會暴露,這是TCP/IP協議棧的性質所致。此類問題的排查要點在於,你要用最快的速度把它與高層協議隔離開來,並且不能陷入任何細節。
TCP細節:為何不必考慮TCP細節?這類場景既不特殊,又不復雜,如果陷入TCP細節的話,會掩蓋或者忽略大量橫向的問題,比如你會死盯著TCP的重傳機制做細緻研究,或者細緻地研究RTT計算方法,最終也不一定能得到什麼結論。換句話說,你一定要相信TCP是正常的。
服務程式細節:這個也是要隔離的。因為伺服器並沒有真的開始服務,且故障是100%重現的,因此可以確定這不是什麼複雜的問題所導致,真正複雜的問題往往不是100%重現,即便是你挖掘出其重現規律,也夠你喝一壺的。
TCP問題和IP問題的相異:它們雖然都是網路協議棧的一員,但是使用方式卻大不相同。實際上TCP提高了使用者的門檻,一般而言,TCP是讓程式去使用的,因此你要想TCP跑起來,起碼要理解其大致原理,或者說懂socket機制,如果你上網瀏覽網頁,雖然也是用的TCP,它確實跑起來了,但是使用者不是你,而是你的瀏覽器。IP就不同,IP的配置者可以是小白,並且隨意配置都不會報錯。再往下,佈線問題,拓撲問題,幾乎沒有什麼門檻,但是卻更加容易出錯。因此首先要排除的就是這類問題。
防火牆策略或者程式BUG:實際上,第一步就需要詢問管理員,是不是防火牆上特殊的策略所致,然而對於無法得到這個訊息的時候,你就不能從這兒開始了。接下來,與之平等的是懷疑程式的處理BUG,此時,隔離出原有的業務邏輯細節是重要的,現象是大包無法收到ACK,此時就要忽略掉這個大包的內容以及其上下文,直接傳送一個任意大包進行測試。

       因此,這類問題的排查是一個逐步隔離的過程,相對四年前的那次NAT bug的排查,這個故障在技術上要更容易些,所有的複雜性和時間的耽擱全部在人員協調交流上,人員之間資訊的誤傳或者漏傳也是一個難點,四年前的那個NAT bug,是一個技術上更加深入的問題,涉及到了核心協議棧程式碼級別,同時在此之前,我還要找到這個點,然而它的容易點在於,這個問題只涉及到我一個人,而且也是100%重現。

天與地,貴在沒有記憶,一切傷痕總是會被沖刷,一切榮耀,總是會了無痕跡......

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30312926/viewspace-1727594/,如需轉載,請註明出處,否則將追究法律責任。

相關文章