【udp】關于 udp socket 連接問題分析

一、 udp問題抓包分析

公司內部的一個 中間件報 UDP 連接異常的日志,問題很明顯,對端的服務掛了,自然重啟下就可以了。

讓人疑惑的問題是 udp 是如何檢測對端掛了?

err:  write udp 172.16.44.62:62651->172.16.0.46:29999: write: connection refused

err:  write udp 172.16.44.62:62651->172.16.0.46:29999: write: connection refused

err:  write udp 172.16.44.62:62651->172.16.0.46:29999: write: connection refused

UDP 協議既沒有三次握手,又沒有 TCP 那樣的狀態控制報文,那么如何判定對端的 UDP 端口是否已打開?

通過抓包可以發現,當服務端的端口沒有打開時,服務端的系統向客戶端返回 icmp ECONNREFUSED 報文,表明該連接異常。

通過抓包可以發現返回的協議為 ICMP,但含有源端口和目的端口,客戶端系統解析該報文時,通過五元組找到對應的 socket,并 errno 返回異常錯誤,如果客戶端陷入等待,則喚醒起來,設置錯誤狀態。

(上面是 udp 異常情況下的 icmp,下面是udp 正常 情況下的icmp)

image.png
image.png

寫UDP socket程序的時候,在調用sendto或者recvfrom的時候,會發現有Connection refused錯誤返回,錯誤碼是ECONNREFUSED。

對于懂得socket接口但是不很很懂網絡的人,可能這根本就不是個問題,他會根據錯誤碼知道遠端沒有這個服務端口,正如socket api的man手冊中描述的那樣:

ECONNREFUSED
A remote host refused to allow the network connection (typically because it is not running the requested service).

如果你十分精通TCP/IP棧,那么就想不通了,UDP既然無連接,怎么知道遠端的情況呢?
UDP不正如協議標準描述的那樣,發出去就不管了嗎?
對于接收,沒有數據就一直等,如果設置了NOWAIT,則直接返回EAGAIN,表示稍后再試。不管怎么說,也不會有ECONNREFUSED這么詳細的信息返回才對啊。

既然UDP不會從對端返回任何錯誤信息,那么一定有別的什么返回了,這就涉及到了網絡協議設計中的數據平面和控制平面了,對于控制平面的消息,可以是帶內傳輸,也可以是帶外傳輸。

數據分為兩種,一種是帶內數據,一種是帶外數據。
帶內數據就是我們平常傳輸或者說是口頭叫的數據,帶外數據就是我們接下來講的內容。

許多的傳輸層都具有帶外數據(也稱為 經加速數據 )的概念,想法就是連接的某段發生了重要的事情,希望迅速的通知給對端。這里的迅速是指這種通知應該在已經排隊了的帶內數據之前發送,也就是說,帶外數據擁有更高的優先級。帶外數據可以使用一條獨立的傳輸層連接,也可以映射到傳輸普通數據的連接中。其中,UDP沒有實現帶外數據。

TCP中telnet、rlogin和ftp等,除了這樣的遠程非活躍應用之外,幾乎很少有使用到帶外數據的地方。

TCP利用其頭部中的緊急指針標志以及緊急指針字段,給應用程序提供里一種緊急方式,所以TCP是利用傳輸普通數據的連接來傳輸帶外數據。

對于TCP而言,無疑是帶內傳輸的,因為它本身就是有連接的協議,協議本身會處理任何的錯誤和異常,然而對于UDP而言,因為其設計目的就是保持簡單性,故不再附帶有任何帶內的控制消息邏輯,互聯網上為了彌補這一類協議的控制邏輯的缺失,ICMP協議才顯得尤為重要!

實際上,ICMP,根據名稱就可以看出它是一種專門的控制協議,控制和指示IP層發生的事件。

ECONNREFUSED正是ICMP返回的,然而并不是所有的UDP socket都可以享用ICMP帶來的錯誤提示,畢竟帶外控制消息和協議本身的關聯太松散了。
UDP socket必須顯式的connect對端才可以。

現在問題又來了,既然UDP根本就是一個無連接的協議,connect的意義何在呢?
這其實是socket接口設計的范疇,和協議本身沒有任何關系,當一個UDP socket去 connect一個遠端時,并沒有發送任何的數據包,其效果僅僅是在本地建立了一個五元組映射,對應到一個對端,該映射的作用正是為了和UDP帶外的ICMP控制通道捆綁在一起,使得UDP socket的接口含義更加豐滿。

image.png

我們知道,ICMP錯誤信息返回時,ICMP的包內容就是出錯的那個原始數據包,根據這個原始數據包可以找出一個五元組,根據該五元組就可以對應到一個本地的connect過的UDP socket,進而把錯誤消息傳輸給該socket,應用程序在調用socket接口函數的時候,就可以得到該錯誤消息。
如果一個UDP socket沒有調用過connect,那么即使有ICMP數據包返回,由于socket保持了UDP的完整語義,協議棧也就不保存關于該socket和對端關聯的任何信息,因此也就無法找到一個特定的五元組將錯誤碼傳給它。

你不能太指望這個Connection refused以及一切帶外返回的錯誤信息,因為你不能保證一定能收到遠端發送的ICMP包,如果中間的某個節點或者本機禁掉了ICMP,socket api調用就無法捕獲這些錯誤了。

當 UDP 連接異常時,可以通過 tcpdmp 工具指定 ICMP 協議來抓取該異常報文,畢竟對方是通過 icmp 返回的 ECONNREFUSED。

我們使用 tcpdump 抓包,先找到一個可以 ping 通的目標主機, 然后用 nc 模擬 udp 客戶端去請求不存在的端口,出現 Connection refused.

# yum  -y install  nc 

# nc -vzu 172.16.0.46 8888
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 172.16.0.46:8888.
Ncat: Connection refused.
# tcpdump   -i   any   icmp   -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
17:01:14.075617 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37
17:01:17.326145 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37
17:01:17.927480 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37
17:01:18.489560 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37

注意: telnet 不支持 udp,,只支持 tcp,建議使用 nc 來探測 udp。

二、各種case的測試

case小結:

  1. 當 ip 無法連通時, udp 客戶端連接時,通常會顯示成功
  2. 當 udp 服務端程序關閉, 但系統還存在時, 對方系統會 icmp ECONNREFUSE 錯誤
  3. 當對方有操作 iptables udp port drop 時,通常客戶端也會顯示成功.
# ping 172.16.0.65

PING 172.16.0.65 (172.16.0.65) 56(84) bytes of data.
From 172.16.0.46 icmp_seq=1 Destination Host Unreachable
From 172.16.0.46 icmp_seq=2 Destination Host Unreachable
From 172.16.0.46 icmp_seq=3 Destination Host Unreachable
From 172.16.0.46 icmp_seq=4 Destination Host Unreachable
From 172.16.0.46 icmp_seq=5 Destination Host Unreachable
From 172.16.0.46 icmp_seq=6 Destination Host Unreachable
^C
--- 172.16.0.65 ping statistics ---
6 packets transmitted, 0 received, +6 errors, 100% packet loss, time 4999ms
pipe 4

# nc   -zuv 172.16.0.65 8888
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 172.16.0.65:8888.
Ncat: UDP packet sent successfully
Ncat: 1 bytes sent, 0 bytes received in 2.02 seconds.

再次明確一點 udp 沒有類似 tcp 那樣的狀態報文, 所以單純對 UDP 抓包是看不到啥異常信息.
那么當 IP 不通時, 為啥nc udp 命令顯示成功 ?

netcat nc udp 的邏輯

為什么當 ip 不連通或者報文被 drop 時,返回連接成功 ?

因為 nc 默認的探測邏輯很簡單,只要在 2 秒鐘內沒有收到 icmp ECONNREFUSED 異常報文, 那么就認為 UDP 連接成功。

所以, UDP 客戶端,給無法連通的地址發 UDP 報文時,其實也不會報錯, 這時候通常會認為發送成功。

還是那句話 UDP 沒有 TCP 那樣的握手步驟,像 TCP 發送 syn 總得不到回報時, 協議棧會在時間退避下嘗試 6 次,當 6 次還得不到回應,內核會給與錯誤的 errno 值。

UDP 連接信息

在客戶端的主機上, 通過 ss lsof netstat 可以看到 UDP 五元組連接信息。

$ netstat  -tunalp | grep 29999

udp   0   0 172.16.0.46:44136  172.16.0.46:29999   ESTABLISHED 1285966/client

通常在服務端上看不到 UDP 連接信息, 只可以看到 udp listen 信息 !

# netstat -tunalp|grep 29999

udp    0      0 :::29999     :::*     4038720/server

客戶端重新實例化問題 ?
當 client 跟 server 已連接,server 端手動重啟后,客戶端無需再次重新實例化連接,可以繼續發送數據。當服務端再次啟動后,照樣可以收到客戶端發來的報文。

udp 本就無握手的過程,他的 udp connect() 也只是在本地創建 socket 信息. 在服務端使用 netstat 是看不到 udp 五元組的 socket。

image.png
image.png

總結

當 udp 服務端的機器可以連通且無異常時,客戶端通常會顯示成功。
但當有異常時,會有以下的情況:

  1. 當 ip 地址無法連通時, udp 客戶端連接時,通常會顯示成功

  2. 當 udp 服務端程序關閉, 但系統還存在時, 對方系統通過 icmp ECONNREFUSE 返回錯誤,客戶端會報錯

  3. 當對方有操作 iptables udp port drop 時,客戶端也會顯示成功

4 客戶端和服務端互通數據,當服務進程掛了時,UDP 客戶端不能立馬感知關閉狀態,只有當再次發數據時才會被對方系統回應 icmp ECONNREFUSE 異常報文, 客戶端才能感知對方掛了。

三、參考

讓人迷糊的 socket udp 連接問題
https://xiaorui.cc/archives/7255

TCP/IP 某些最常見的錯誤原因碼 (errno)列表
https://www.cnblogs.com/jiu0821/p/5895723.html

技術分享之網絡編程的那些事兒
https://xiaorui.cc/archives/7271

TCP 帶外數據(即緊急模式的發送和接受)
https://blog.csdn.net/liushengxi_root/article/details/82563181

TCP帶外數據
https://www.cnblogs.com/c-slmax/p/5553857.html

TCP-帶外數據(緊急數據)
http://www.lxweimin.com/p/65a4b8c059d4

什么是帶外管理和帶內管理?
https://zhuanlan.zhihu.com/p/341264872

FAQ-什么是帶外管理和帶內管理?它們的區別是什么?
https://support.huawei.com/enterprise/zh/knowledge/EKB1000055297

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容