C/S UDP通訊實踐踩坑記錄與對於ICMP的進一步認識

2022-12-31 21:00:46

背景

最近有個業務場景需要伺服器端(簡稱S)與使用者端(簡稱C)設計一套基於UDP的通訊協定--要求儘可能快的前提下可容忍一定丟包率,得以比較深入地學習和了解UDP通訊和實踐,在開發偵錯期間先後碰到了C端UDP發包無響應、響應Host Unreachable、響應Port Unreachable、再次C端UDP發包無響應這四種錯誤情況,不同於以往連線偵錯成功後萬事大吉不再細究,這次有了好奇心想刨根問底的弄清楚造成不同錯誤的原因與錯誤通知的原理,並最終進一步瞭解了ICMP這個熟悉又陌生的協定。

錯誤問題與原因分析

為了便於更清晰、方便的闡明問題,以下對問題的順序和出現場景進行了藝術加工--和實際發生的情況並不一致,畢竟實際問題並不會講道理的一個一個順序給你出現,而是經常多個問題混在在一起形成所謂的bug漸欲迷人眼==!

C端UDP發包無響應

sudo hping -2 -k -s 3000 -p 9999 test.demoabc.com -d 2 # hping引數含義:-2表示UDP模式, -s表示源埠固定3000, -p表示目的埠9999,  test.demoabc.com為目的主機, -d 2表示封包payload為2位元組
HPING test.demoabc.com (en0 119.x.x.100): udp mode set, 28 headers + 2 data bytes
^C
--- test.demoabc.com hping statistic ---
11 packets tramitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms

如上,使用hping向test.demoabc.com:9999 傳送了11個UDP包,但是沒有得到任何迴應,S上的監聽程序也沒有接收到這11個UDP包中的任意一個,如果說UDP本身不可靠會導致可能丟包的話,在網路鏈路質量正常的情況下這11個包理論上是不可能100%丟包的。略一思考很快想到應該是由於防火牆沒有開放UDP埠,於是防火牆既不會將UDP包轉給後面正在監聽9999埠的S程序,也不會給C端回包,而是直接丟棄處理。
解決方案:防火牆開放UDP對應埠即可。

Host Unreachable

防火牆放開埠後,自測聯調C、S的發包、回包已經調通,於是交付使用者端,結果使用者端反饋UDP發包有問題,並且ping 目標host會報Host Unreachable,這就奇怪了,自測已經調通了,監聽程序已經在執行且能夠收到使用hping命令發包的UDP包了,使用者端怎麼就有問題呢?
仔細一看:嗯,C端host寫錯了--之前還沒有設定測試域名的時候,直接給了C端一個公網ip想快速測試,結果由於種種原因最終實際使用的是另外一臺伺服器,舊IP對應的伺服器回收了,所以使用者端ping會報Host Unreachable。
ping命令大概是這麼個效果:

 ping 119.x.x.90
PING 119.x.x.90 (119.x.x.90) 56(84) bytes of data.
From 192.168.0.105 icmp_seq=1 Destination Host Unreachable
From 192.168.0.105 icmp_seq=2 Destination Host Unreachable
From 192.168.0.105 icmp_seq=3 Destination Host Unreachable
From 192.168.0.105 icmp_seq=4 Destination Host Unreachable
From 192.168.0.105 icmp_seq=5 Destination Host Unreachable
^C
--- 119.x.x.90 ping statistics ---
8 packets transmitted, 0 received, +5 errors, 100% packet loss, time 7167ms

解決方案: 使用者端更改為正確host請求即可。

Port Unreachable

解決了上面ping 結果Host Unreachable的問題後,使用者端表示ping是OK的,但是UDP通訊還是會報 Port Unreachable錯誤,真是怪事天天有,今天特別多,繼續排查。
嗯,經過排查,使用者端的UDP埠寫錯了,簡單來說給使用者端的連線地址是test.demoabc.com:9999, 但是使用者端實際使用的時候用的域名是test.demoabc.com,但是埠卻使用了預設的HTTP 80埠,TCP的80埠確實起著nginx在監聽著,但是UDP的80埠可是沒有任何程序監聽的,於是就會導致Port Unreachable,大概類似於以下hping的請求

sudo hping -2 -k -s 3000  -p 80 test.demoabc.com -d 2
HPING test.demoabc.com (en0 119.x.x.100): udp mode set, 28 headers + 2 data bytes
ICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.com
ICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.com
ICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.com
ICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.com
ICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.com
ICMP Port Unreachable from ip=119.x.x.100 name=test.demoabc.com
^C
--- test.demoabc.com hping statistic ---
6 packets tramitted, 6 packets received, 0% packet loss

解決方案:使用者端更改為正確host+port請求即可。

再次C端UDP發包無響應

解決了上面三個問題後,使用者端、伺服器端UDP通訊終於是調通了,可以繼續快樂的開發後續邏輯了,結果某天使用者端突然反饋使用者端發包正常,但是收不到任何回包,重新自己用hping進行測試確實也是類似的結果,hping結果如下:

sudo hping -2 -k -s 3000  -p 9999  test.demoabc.com -d 2
HPING test.demoabc.com (en0 119.x.x.100): udp mode set, 28 headers + 2 data bytes
^C
--- test.demoabc.com hping statistic ---
19 packets tramitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms

看上去和剛開始防火牆導致的問題確實毫無區別,但是又check了防火牆規則確認並不存在問題,使用tcpdump抓包也確認收到了來自C端的UDP包,最後還在server程式碼中新增了UDP 收包後直接列印原始內容的log,也能夠確認資料已經被交付到了監聽程序,可是C端為什麼收不到任何響應呢?
仔細思考UDP的原理,UDP本身是無連線、不可靠的,它不像TCP那樣協定保證每個發包都會保證送達,協定會保證有對應的ack回包--即便業務程式碼不給C端回包,協定本身也會保證有ack的回包,所以理論上如果S端收到了C端的UDP包,本身卻不做任何迴應的話,對於發包的C端來說其實並不能知道封包是在傳送途中默默丟失了、被目標防火牆拒收丟棄了還是最終被S收到了但未做任何迴應。
前面已經確認了防火牆設定正確,tcpdump抓包和業務log也驗證了監聽程序確實已經收到了C端封包,那麼問題就只可能出在S端給C端的回包邏輯上了,程式碼中為了測試對於C端的發包是有一次固定回包的,S端固定1s間隔還會給C端傳送心跳包,這在之前幾天其實無論自測還是C端使用上都是正常的,結果現在突然就不work了。依據豐富的bug經驗--推斷應該是最近的業務程式碼改動出bug了--很合理的解釋,仔細一查伺服器端近期並沒有程式碼改動,但是程式碼中會有一些異常條件判斷提前return的邏輯,在相應地方新增詳細錯誤log後重新測試,終於真相大白--使用者端使用的序列化協定錯了,C端最近一次改動序列化生成的二進位制資料S端會解析出錯,於是提前return,不會走後面的回包邏輯,而固定心跳包機制未生效的原因也破解了--只有成功走到S回包流程的C端ip/port才會被加入S的活躍C端列表,才會傳送心跳包,這裡由於C端的資料全部錯誤提前返回了,未能加入活躍C端列表,也就不會傳送心跳包了。而自己使用hping測試之前正常現在卻是失敗的原因也是由於剛開始並沒有加這塊解析錯誤提前return的邏輯,只是簡單驗證發包回包連通性,現在已經有這塊解析校驗return的邏輯的情況下使用hping當然也一樣收不到回包了。
解決方案:C端fix錯誤的序列化程式碼,S端增加更全面、詳細的錯誤log方便後續更快排查問題。

對於ICMP的進一步認識

到這一步UDP通訊聯調碰到的4個問題已經闡述完畢,似乎已經可以結束整篇blog了,但是好像標題中的ICMP到目前為止還沒有絲毫提及的樣子?
對於有一定網路基礎、經驗的小夥伴,其實應該都能意識到之前雖然一直沒有提到ICMP的名字,但ICMP本身的使用其實已經被多次提及,從ping命令執行的響應Host Unreachable,hping命令和C端發UDP包得到的響應 Port Unreachable這些其實都是依賴的ICMP協定。
但是對於網路基礎較弱的小夥伴,這一點就並不那麼明顯了,包括自己在內其實之前從來沒有把 Unreachable這些報錯響應和ICMP直接聯絡起來過--換句話說,課堂上學習過ICMP,知道其全稱是Internet Control Message Protocol,也大概知道ping命令和ICMP有關係,但是更進一步:ICMP到底是幹啥的?什麼場景下會有ICMP響應?為什麼有時候響應是Host Unreachable,有時候是Port Unreacable,有時候又直接是timeout而沒有任何響應呢?ICMP是哪一層的協定?UDP、TCP和ICMP又有什麼關係?
更詳細的ICMP介紹網上已經有很多的資料了,這裡僅簡單講述一下自己對以上問題的理解--有錯漏歡迎大家指正,想進一步瞭解的小夥伴推薦閱讀小林coding的20 張圖解: ping 的工作原理,圖文並茂講的非常之贊。

ICMP到底是幹啥的

顧名思義,ICMP是用於控制報文傳輸的協定,主要分為兩類:查詢報文和差錯報文。

什麼場景下會有ICMP響應

可以先思考一個問題,當源主機傳送一個IP包、UDP包或者TCP包給目標主機時,如果在送達過程中出現了某些而被丟棄--比如目標主機關機了,源主機怎麼能知道某個包被丟棄了/主機不可達呢?如果沒有一種機制負責通知源主機的話,源主機可能只能傻等到timeout了,這就是ICMP的通知機制的一種使用場景,節點會在丟棄封包的同時向源主機傳送ICMP報文通知,明確告知其目標主機unreachable。
非常常用的ping命令就是基於查詢報文實現,查詢報文可用於測試到目的主機鏈路是否可達,目的主機在收到查詢報文時預設會回覆一個響應報文給源主機--除非目的主機禁止了策略回送,在簡單網路延遲測試、鏈路連通故障檢測方面使用ping命令大家應該都已經十分熟悉了。
而差錯報文型別就是在IP封包送達目的主機過程中出錯時,出錯節點給源主機回送的具體差錯資訊,比如封包到達了目的主機前一跳路由器,目的主機已關機,路由器無法送達封包就會給告知源主機 Host Unreachable, 又比如源主機傳送UDP包到目標主機的9999埠但是9999埠的監聽程序掛了沒起來,目標主機發現收到了UDP包但是卻沒有可交付的程序,就會告知源主機Port Unreachable。

為什麼有時候響應是Host Unreachable,有時候是Port Unreacable,有時候又直接是timeout而沒有任何響應呢?

如果最終發現封包無法送達只能丟棄的節點(可能是中間路由器、最終主機等)沒有禁止對應ICMP訊息響應,那就會給源主機傳送Unreachable響應,簡單來說如果直接是目的host都找不到無法交付會響應Host Unreachable, 如果已經交付到了目標host,但是目標host發現這是個傳輸層TCP、UDP包卻無法找到對應port的接收程序, 響應就是 Port Unreachable。而如果節點禁止對應ICMP訊息響應,那麼節點只是簡單丟棄無法送達的封包,不會有響應操作,此時源主機等待超過一定時間也就只能判定timeout了。

ICMP是哪一層的協定

ICMP本身是網路層協定。

UDP、TCP和ICMP又有什麼關係

UDP、TCP都是傳輸層協定,其和網路層ICMP並沒有直接關係,但是當UDP、TCP封包在送達目的主機的過程中出現問題--如Host UnReachable、Port Unreachable、拒絕分片導致被丟棄時,節點會通過向源主機傳送ICMP報文幫助源主機瞭解傳送失敗原因以進一步處理,其他還有提示資料傳送方更優路徑的Redirect Message和緩解擁堵的Source Quench Message等。

總結

親自實踐了基於UDP的網路程式設計可謂把之前以死記硬背為主的UDP知識進行了一番試煉與提純,真正的加深了理解,同時對於看似熟悉實則陌生的ICMP協定有了直觀得多、深入的多的學習。真正是:紙上得來終覺淺,絕知此事要躬行。與大家共勉。

轉載請註明出處,原文地址: https://www.cnblogs.com/AcAc-t/p/udp_message_and_icmp.html

參考

https://www.cnblogs.com/xiaolincoding/p/12571184.html
https://juejin.cn/post/6993853593476399140
https://www.cnblogs.com/acac-t/p/udp_message_and_icmp.html
https://www.zhihu.com/question/31002474