<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          讓人迷糊的 socket udp 連接問題

          共 5705字,需瀏覽 12分鐘

           ·

          2022-03-05 21:59

          公司內(nèi)部的一個 golang 中間件報 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 協(xié)議既沒有三次握手,又沒有 TCP 那樣的狀態(tài)控制報文,那么如何判定對端的 UDP 端口是否已打開?

          通過抓包可以發(fā)現(xiàn),當服務端的端口沒有打開時,服務端的系統(tǒng)向客戶端返回 icmp ECONNREFUSED 報文,表明該連接異常。

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

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

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

          使用 tcpdump 抓包

          請求命令:

          先找到一個可以 ping 通的主機,然后用 nc 模擬 udp 客戶端去請求不存在的端口,出現(xiàn) Connection refused

          [root@ocean?~]#?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.

          抓包信息如下:

          [root@ocean?~]#?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 服務端程序關閉,但系統(tǒng)還存在時,對方系統(tǒng)會 `icmp ECONNREFUSE 錯誤。
          • 當對方有操作 iptables udp port drop 時,通??蛻舳艘矔@示成功。

          IP 無法聯(lián)通時:

          [root@host-46?~?]$?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

          [root@host-46?~?]$?nc?-vzu?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 連接成功。??

          下面是 nc udp 命令執(zhí)行的過程。

          setsockopt(3,?SOL_SOCKET,?SO_BROADCAST,?[1],?4)?=?0
          connect(3,?{sa_family=AF_INET,?sin_port=htons(30000),?sin_addr=inet_addr("172.16.0.111")},?16)?=?0
          select(4,?[3],?[3],?[3],?NULL)??????????=?1?(out?[3])
          getsockopt(3,?SOL_SOCKET,?SO_ERROR,?[0],?[4])?=?0
          write(2,?"Ncat:?",?6Ncat:?)???????????????????=?6
          write(2,?"Connected?to?172.16.0.111:29999."...,?33Connected?to?172.16.0.111:29999.
          )?=?33
          sendto(3,?"\0",?1,?0,?NULL,?0)??????????=?1

          // select 多路復用方法里加入了超時邏輯。
          select(4,?[3],?[],?[],?{tv_sec=2,?tv_usec=0})?=?0?(Timeout)

          write(2,?"Ncat:?",?6Ncat:?)???????????????????=?6
          write(2,?"UDP?packet?sent?successfully\n",?29UDP?packet?sent?successfully
          )?=?29
          write(2,?"Ncat:?",?6Ncat:?)???????????????????=?6
          write(2,?"1?bytes?sent,?0?bytes?received?i"...,?481?bytes?sent,?0?bytes?received?in?2.02?seconds.
          )?=?48
          close(3)????????????????????????????????=?0

          使用 golang/ python 編寫的 UDP 客戶端,給無法連通的地址發(fā) UDP 報文時,其實也不會報錯,這時候通常會認為發(fā)送成功。

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

          UDP 連接信息

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

          [root@host-46?~?]$?netstat?-tunalp|grep?29999
          udp????????0??????0?172.16.0.46:44136???????172.16.0.46:29999???????ESTABLISHED?1285966/cccc

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

          [root@host-62?~?]#?netstat?-tunalp|grep?29999
          udp???????0??????0?:::29999????????????????:::*????????????????????????????????4038720/ss

          客戶端重新實例化問題?

          當 client 跟 server 已連接,server 端手動重啟后,客戶端無需再次重新實例化連接,可以繼續(xù)發(fā)送數(shù)據(jù),當服務端再次啟動后,照樣可以收到客戶端發(fā)來的報文。

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

          Golang 測試代碼

          服務端代碼:

          package?main

          import?(
          ????"fmt"
          ????"net"
          )

          //?UDP?服務端
          func?main()?{
          ????listen,?err?:=?net.ListenUDP("udp",?&net.UDPAddr{
          ????????IP:???net.IPv4(0,?0,?0,?0),
          ????????Port:?29999,
          ????})

          ????if?err?!=?nil?{
          ????????fmt.Println("Listen?failed,?err:?",?err)
          ????????return
          ????}
          ????defer?listen.Close()

          ????for?{
          ????????var?data?[1024]byte
          ????????n,?addr,?err?:=?listen.ReadFromUDP(data[:])
          ????????if?err?!=?nil?{
          ????????????fmt.Println("read?udp?failed,?err:?",?err)
          ????????????continue
          ????????}
          ????????fmt.Printf("data:%v?addr:%v?count:%v\n",?string(data[:n]),?addr,?n)
          ????}
          }

          客戶端代碼:

          package?main

          import?(
          ????"fmt"
          ????"net"
          ????"time"
          )

          //?UDP?客戶端
          func?main()?{
          ????socket,?err?:=?net.DialUDP("udp",?nil,?&net.UDPAddr{
          ????????IP:???net.IPv4(172,?16,?0,?46),
          ????????Port:?29999,
          ????})
          ????if?err?!=?nil?{
          ????????fmt.Println("連接UDP服務器失敗,err:?",?err)
          ????????return
          ????}
          ????defer?socket.Close()

          ????for?{
          ????????time.Sleep(1e9?*?2)
          ????????sendData?:=?[]byte("Hello?Server")
          ????????_,?err?=?socket.Write(sendData)
          ????????if?err?!=?nil?{
          ????????????fmt.Println("發(fā)送數(shù)據(jù)失敗,err:?",?err)
          ????????????continue
          ????????}

          ????????fmt.Println("已發(fā)送")
          ????}
          }

          總結(jié)

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

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

          • 當 udp 服務端程序關閉,但系統(tǒng)還存在時,對方系統(tǒng)通過 icmp ECONNREFUSE 返回錯誤,客戶端會報錯。

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

          • 客戶端和服務端互通數(shù)據(jù),當服務進程掛了時,UDP 客戶端不能立馬感知關閉狀態(tài),只有當再次發(fā)數(shù)據(jù)時才會被對方系統(tǒng)回應 icmp ECONNREFUSE 異常報文,客戶端才能感知對方掛了。

          瀏覽 90
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚欧无码线免费观看视频 | 欧美激情亚洲色图 | 青青草色成人网站视频 | 欧美精品一卡二卡 | 九九九九九色 |