一茴迁、 udp問題抓包分析
公司內(nèi)部的一個 中間件報 UDP 連接異常的日志秽荞,問題很明顯,對端的服務(wù)掛了像捶,自然重啟下就可以了上陕。
讓人疑惑的問題是 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 協(xié)議既沒有三次握手作岖,又沒有 TCP 那樣的狀態(tài)控制報文唆垃,那么如何判定對端的 UDP 端口是否已打開?
通過抓包可以發(fā)現(xiàn)痘儡,當服務(wù)端的端口沒有打開時,服務(wù)端的系統(tǒng)向客戶端返回 icmp ECONNREFUSED 報文枢步,表明該連接異常沉删。
通過抓包可以發(fā)現(xiàn)返回的協(xié)議為 ICMP,但含有源端口和目的端口醉途,客戶端系統(tǒng)解析該報文時矾瑰,通過五元組找到對應(yīng)的 socket,并 errno 返回異常錯誤隘擎,如果客戶端陷入等待殴穴,則喚醒起來,設(shè)置錯誤狀態(tài)货葬。
(上面是 udp 異常情況下的 icmp采幌,下面是udp 正常 情況下的icmp)
寫UDP socket程序的時候,在調(diào)用sendto或者recvfrom的時候震桶,會發(fā)現(xiàn)有Connection refused錯誤返回休傍,錯誤碼是ECONNREFUSED。
對于懂得socket接口但是不很很懂網(wǎng)絡(luò)的人蹲姐,可能這根本就不是個問題磨取,他會根據(jù)錯誤碼知道遠端沒有這個服務(wù)端口,正如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不正如協(xié)議標準描述的那樣逢净,發(fā)出去就不管了嗎?
對于接收,沒有數(shù)據(jù)就一直等汹胃,如果設(shè)置了NOWAIT婶芭,則直接返回EAGAIN,表示稍后再試着饥。不管怎么說犀农,也不會有ECONNREFUSED這么詳細的信息返回才對啊。
既然UDP不會從對端返回任何錯誤信息宰掉,那么一定有別的什么返回了呵哨,這就涉及到了網(wǎng)絡(luò)協(xié)議設(shè)計中的數(shù)據(jù)平面和控制平面了,對于控制平面的消息轨奄,可以是帶內(nèi)傳輸孟害,也可以是帶外傳輸。
數(shù)據(jù)分為兩種挪拟,一種是帶內(nèi)數(shù)據(jù)挨务,一種是帶外數(shù)據(jù)。
帶內(nèi)數(shù)據(jù)就是我們平常傳輸或者說是口頭叫的數(shù)據(jù)玉组,帶外數(shù)據(jù)就是我們接下來講的內(nèi)容谎柄。
許多的傳輸層都具有帶外數(shù)據(jù)(也稱為 經(jīng)加速數(shù)據(jù) )的概念,想法就是連接的某段發(fā)生了重要的事情惯雳,希望迅速的通知給對端朝巫。這里的迅速是指這種通知應(yīng)該在已經(jīng)排隊了的帶內(nèi)數(shù)據(jù)之前發(fā)送,也就是說石景,帶外數(shù)據(jù)擁有更高的優(yōu)先級劈猿。帶外數(shù)據(jù)可以使用一條獨立的傳輸層連接,也可以映射到傳輸普通數(shù)據(jù)的連接中潮孽。其中揪荣,UDP沒有實現(xiàn)帶外數(shù)據(jù)。
TCP中telnet恩商、rlogin和ftp等变逃,除了這樣的遠程非活躍應(yīng)用之外,幾乎很少有使用到帶外數(shù)據(jù)的地方怠堪。
TCP利用其頭部中的緊急指針標志以及緊急指針字段揽乱,給應(yīng)用程序提供里一種緊急方式,所以TCP是利用傳輸普通數(shù)據(jù)的連接來傳輸帶外數(shù)據(jù)粟矿。
對于TCP而言凰棉,無疑是帶內(nèi)傳輸?shù)模驗樗旧砭褪怯羞B接的協(xié)議陌粹,協(xié)議本身會處理任何的錯誤和異常撒犀,然而對于UDP而言,因為其設(shè)計目的就是保持簡單性,故不再附帶有任何帶內(nèi)的控制消息邏輯或舞,互聯(lián)網(wǎng)上為了彌補這一類協(xié)議的控制邏輯的缺失荆姆,ICMP協(xié)議才顯得尤為重要!
實際上映凳,ICMP胆筒,根據(jù)名稱就可以看出它是一種專門的控制協(xié)議,控制和指示IP層發(fā)生的事件诈豌。
ECONNREFUSED正是ICMP返回的仆救,然而并不是所有的UDP socket都可以享用ICMP帶來的錯誤提示,畢竟帶外控制消息和協(xié)議本身的關(guān)聯(lián)太松散了矫渔。
UDP socket必須顯式的connect對端才可以彤蔽。
現(xiàn)在問題又來了,既然UDP根本就是一個無連接的協(xié)議庙洼,connect的意義何在呢顿痪?
這其實是socket接口設(shè)計的范疇,和協(xié)議本身沒有任何關(guān)系油够,當一個UDP socket去 connect一個遠端時员魏,并沒有發(fā)送任何的數(shù)據(jù)包,其效果僅僅是在本地建立了一個五元組映射叠聋,對應(yīng)到一個對端,該映射的作用正是為了和UDP帶外的ICMP控制通道捆綁在一起受裹,使得UDP socket的接口含義更加豐滿碌补。
我們知道,ICMP錯誤信息返回時棉饶,ICMP的包內(nèi)容就是出錯的那個原始數(shù)據(jù)包厦章,根據(jù)這個原始數(shù)據(jù)包可以找出一個五元組,根據(jù)該五元組就可以對應(yīng)到一個本地的connect過的UDP socket照藻,進而把錯誤消息傳輸給該socket袜啃,應(yīng)用程序在調(diào)用socket接口函數(shù)的時候,就可以得到該錯誤消息幸缕。
如果一個UDP socket沒有調(diào)用過connect群发,那么即使有ICMP數(shù)據(jù)包返回,由于socket保持了UDP的完整語義发乔,協(xié)議棧也就不保存關(guān)于該socket和對端關(guān)聯(lián)的任何信息熟妓,因此也就無法找到一個特定的五元組將錯誤碼傳給它。
你不能太指望這個Connection refused以及一切帶外返回的錯誤信息栏尚,因為你不能保證一定能收到遠端發(fā)送的ICMP包起愈,如果中間的某個節(jié)點或者本機禁掉了ICMP,socket api調(diào)用就無法捕獲這些錯誤了。
當 UDP 連接異常時抬虽,可以通過 tcpdmp 工具指定 ICMP 協(xié)議來抓取該異常報文官觅,畢竟對方是通過 icmp 返回的 ECONNREFUSED。
我們使用 tcpdump 抓包阐污,先找到一個可以 ping 通的目標主機, 然后用 nc 模擬 udp 客戶端去請求不存在的端口休涤,出現(xiàn) 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小結(jié):
- 當 ip 無法連通時, udp 客戶端連接時隘膘,通常會顯示成功
- 當 udp 服務(wù)端程序關(guān)閉, 但系統(tǒng)還存在時, 對方系統(tǒng)會 icmp ECONNREFUSE 錯誤
- 當對方有操作 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 那樣的狀態(tài)報文, 所以單純對 UDP 抓包是看不到啥異常信息.
那么當 IP 不通時, 為啥nc udp 命令顯示成功 ?
netcat nc udp 的邏輯
為什么當 ip 不連通或者報文被 drop 時弯菊,返回連接成功 ?
因為 nc 默認的探測邏輯很簡單纵势,只要在 2 秒鐘內(nèi)沒有收到 icmp ECONNREFUSED 異常報文, 那么就認為 UDP 連接成功。
所以管钳, UDP 客戶端钦铁,給無法連通的地址發(fā) UDP 報文時,其實也不會報錯, 這時候通常會認為發(fā)送成功才漆。
還是那句話 UDP 沒有 TCP 那樣的握手步驟牛曹,像 TCP 發(fā)送 syn 總得不到回報時, 協(xié)議棧會在時間退避下嘗試 6 次,當 6 次還得不到回應(yīng)醇滥,內(nèi)核會給與錯誤的 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
通常在服務(wù)端上看不到 UDP 連接信息, 只可以看到 udp listen 信息 鸳玩!
# netstat -tunalp|grep 29999
udp 0 0 :::29999 :::* 4038720/server
客戶端重新實例化問題 ?
當 client 跟 server 已連接阅虫,server 端手動重啟后,客戶端無需再次重新實例化連接不跟,可以繼續(xù)發(fā)送數(shù)據(jù)颓帝。當服務(wù)端再次啟動后,照樣可以收到客戶端發(fā)來的報文窝革。
udp 本就無握手的過程购城,他的 udp connect() 也只是在本地創(chuàng)建 socket 信息. 在服務(wù)端使用 netstat 是看不到 udp 五元組的 socket。
總結(jié)
當 udp 服務(wù)端的機器可以連通且無異常時聊闯,客戶端通常會顯示成功工猜。
但當有異常時,會有以下的情況:
當 ip 地址無法連通時, udp 客戶端連接時菱蔬,通常會顯示成功
當 udp 服務(wù)端程序關(guān)閉, 但系統(tǒng)還存在時, 對方系統(tǒng)通過 icmp ECONNREFUSE 返回錯誤仔夺,客戶端會報錯
當對方有操作 iptables udp port drop 時,客戶端也會顯示成功
4 客戶端和服務(wù)端互通數(shù)據(jù)医增,當服務(wù)進程掛了時头岔,UDP 客戶端不能立馬感知關(guān)閉狀態(tài),只有當再次發(fā)數(shù)據(jù)時才會被對方系統(tǒng)回應(yīng) icmp ECONNREFUSE 異常報文, 客戶端才能感知對方掛了。
三、參考
讓人迷糊的 socket udp 連接問題
https://xiaorui.cc/archives/7255
TCP/IP 某些最常見的錯誤原因碼 (errno)列表
https://www.cnblogs.com/jiu0821/p/5895723.html
技術(shù)分享之網(wǎng)絡(luò)編程的那些事兒
https://xiaorui.cc/archives/7271
TCP 帶外數(shù)據(jù)(即緊急模式的發(fā)送和接受)
https://blog.csdn.net/liushengxi_root/article/details/82563181
TCP帶外數(shù)據(jù)
https://www.cnblogs.com/c-slmax/p/5553857.html
TCP-帶外數(shù)據(jù)(緊急數(shù)據(jù))
http://www.reibang.com/p/65a4b8c059d4
什么是帶外管理和帶內(nèi)管理税朴?
https://zhuanlan.zhihu.com/p/341264872
FAQ-什么是帶外管理和帶內(nèi)管理?它們的區(qū)別是什么?
https://support.huawei.com/enterprise/zh/knowledge/EKB1000055297