一、前言
在某一次写 udp
服务时,发现一个奇特的现象,udp
client
在调用 write
方法写入数据时会报 connection refused
。
对于懂得 socket
编程却不懂网络的人可能认为这根本就不是一个问题。
因为这报错显然就是远端服务对客户端发送的数据进行了拒绝,也许会认为远端服务没有起端口。
有时无知也是一种幸福。但是对于熟悉 TCP/IP 五层协议栈的同学肯定就会想不通,udp
本身就没有面向连接这一说法,它调用write
时,对端没有接收大不了就是丢包,那么为何还有 connection refused
这一说法?
二、复现代码
下面有一段代码就可以复现这种现象
udp 客户端 client.go
package main
import (
"fmt"
"net"
"time"
)
func main() {
conn, err := net.Dial("udp", "39.96.14.232:9981")
if err != nil {
fmt.Println("拨号发生错误:", err.Error())
return
}
defer conn.Close()
for {
time.Sleep(time.Second)
if _, err := conn.Write([]byte("hello")); err != nil {
fmt.Println("写数据发生错误:", err.Error())
continue
}
}
}
udp 服务端 server.go
package main
import (
"fmt"
"net"
)
func main() {
listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("0.0.0.0"), Port: 9981})
if err != nil {
fmt.Println(err)
return
}
data := make([]byte, 1024)
for {
n, remoteAddr, err := listener.ReadFromUDP(data)
if err != nil {
fmt.Printf("读发生错误: %s\n", err)
continue
}
fmt.Printf("从 %s 读取到的内容为 %s \n", remoteAddr, data[:n])
}
}
当把 server.go
ctrl+c
杀死时,我们会发现客户端开始报错,就像下面这样。
三、解析原因
首先我相信公理是不会出错的,既然这里 udp
客户端写入报错,肯定是有其它深层次的原因。
不妨我们用 tcpdump
进行抓包一探究竟
tcpdump -i any host 39.96.14.232 -s0 -w ./udp.pcap
Wireshark
分析结果如下
从中可以发现,当 udp
客户端发包至服务端时,接收方发现自身对应的 udp
端口没有打开,会通过 icmp
协议回复一个端口不可达的信息。
因此ICMP的包内容就是出错的那个原始数据包,根据这个原始数据包可以找出一个五元组,根据该五元组就可以对应到一个本地的udp socket
,进而把错误消息传输给该socket
,应用程序在调用socket接口函数的时候,就可以得到该错误消息。
根据这一现象,在一定程度上可以做 udp
端口探测,但这种探测不一定好使,在有些网络环境下 icmp
不可达消息会被过滤掉。
例如当udp
服务端所在宿主机对于udp
协议的安全组没开放时,我发现就算无论如何客户端都不会收到该报错。相反,当把 udp
协议的安全组放开时,并关闭udp
服务端时,就会收到接收包回复的 icmp
包现象。
以此记录刚毕业那年遇到的疑问,终于在今日通过实验被解答了。
文章评论(0)