视频加载失败

【计算机网络】UDP打洞

1778 字
9 分钟
【计算机网络】UDP打洞
【计算机网络】UDP打洞

UDP打洞#

指用于NAT穿越的一种技术,也即内网穿透技术。

背景介绍#

IPv4枯竭#

现阶段大多网络设备是用IPv4地址作为互联网通信地址,IPv4使用32位(4字节)地址,因此只有4,294,967,296(232)个地址可供使用,其间有一些地址是为特殊用途而保留的,如专用网络(约1800万个地址)和多播网络(约2.7个地址),这将减少了互联网中可用的地址。随着时间的变化,接入互联网的设备越来越多,出现了IPv4地址的枯竭的问题。

NAT技术#

NAT全称:Network Address Translation,网络地址转换。是一种在IP数据包通过路由器或防火墙时重写来源IP或目的IP的技术。该技术可以实现多个网络设备共用一个对外IP或公网IP。

因此NAT技术可以缓解IPv4枯竭的问题,目前基本上家用宽带运营商都使用NAT技术为用户赋予IP地址,通常是一层或一栋楼共用一个公网IP(详情根据当地运营商而定)。

NAT.jpg
NAT.jpg

假如现有内网设备A、NAT(1.1.1.2)和公网服务器(1.1.1.1)

  1. A向服务器发送请求包。
  2. A请求包经过NAT,NAT从中得到请求的目的地址(1.1.1.1)。
  3. NAT保存A的地址和A的目的地址(1.1.1.1),并给A随机分配映射一个端口(1234)。
  4. NAT将A请求包的源地址(A的地址)和源端口,替换为NAT的公网IP和分配给A的端口(1234)。
  5. NAT将请求包向公网服务器(1.1.1.1)发出。
  6. 服务器(1.1.1.1)接收到请求包并返回回应包。
  7. NAT接收到回应包,并从中得到回应的端口(1234)。
  8. NAT找查记录,发现与端口(1234)对应的设备是A,并且回应包发送者的IP(1.1.1.1)也在其中。(若两者其中一个不满足就会将此包丢弃)
  9. NAT将回应包的目的IP(1.1.1.2)和目的端口(1234)改为A的地址。
  10. NAT将回应包发送给A。

默认情况下在NAT内网的设备可以主动访问公网IP的设备,但公网IP设备却不能主动访问NAT内网的设备(因为NAT没有记录,对应上述例子第8步).

打洞#

因为NAT技术的原因,造成两个NAT内网的设备无法直接通信(例如:不能直接访问没有公网IP的电脑)。 打洞顾名思义,就是在NAT内网中打出一个洞,让两个不同内网的设备直接访问。

原理#

从上述NAT技术的第4步和第9步,可以看出NAT需要将内网设备映射到一个端口并保存其访问的公网地址和端口,才可以使内网设备和公网设备通信。所以打洞给关键一步就是让两个设备相互访问对方公网对应的IP和端口,让自己的NAT保存记录。

假如现有内网设备A、NAT A(1.1.1.2)、内网设备B、NAT B(1.1.1.3)以及公网服务器(1.1.1.1),内网设备A和内网设备B想要直接通信。

UDPASS.jpg
UDPASS.jpg

  1. A和B向服务器访问,NAT A和NAT B分别为AB分配映射端口4530与6540。
  2. 服务器收到请求后,保存IP和端口(1.1.1.2:4530,1.1.1.3:6540)。
  3. 服务器向A返回B的IP和端口,向B返回A的IP和端口。
  4. A向B的地址发送数据(NAT B因为没有A的地址记录会丢弃A的包,但是这一步为的是让NAT A记住B的地址,让NAT A以后将带有B地址的包转发给A)。
  5. B再向A发送数据。(这一步也是让NAT B记住A的地址,但上一步NAT A已经有了B的地址,所以这一次B发送的数据可以正常到达A)。
  6. 从此A和B就可以直接双向发送数据,洞就被打通了。

UDP#

UDP全称:User Datagram Protocol,用户数据报协议。是一种无连接的协议。

UDP负责将数据发送出去,但对方有没有接收到就不是UDP责任了(就算是错误的IP或端口,UDP也照样发送),因此也是一种不可靠的协议。

但是就很适合使用UDP来做NAT打洞,因为在上述原理第4步的过程中,NAT B没有A的记录就将A的包给丢弃了,在计划里这一步丢包是必然的,而UDP不管对方收到或丢弃。若是用其他可靠协议比如TCP,可能会造成打洞失败的情况,而解决办法就是修改系统内核,但这又非常麻烦。

代码实现#

这里使用Golang进行网络编程。

客户端之所以使用net.ListenUDP()而不是net.DialUDP(),是因为net.ListenUDP()的*UDPConn是unconnected,而net.DialUDP()的*UDPConn是connected的。

如果*UDPConn是connected,读写方法是Read和Write。

如果*UDPConn是unconnected,读写方法是ReadFromUDP和WriteToUDP(以及ReadFrom和WriteTo)。

使用ReadFromUDP和WriteToUDP进行读写可以指定读写任何UDP地址,而且net.ListenUDP()还可以固定本机UDP端口不被系统更换。

服务端demo#

type j struct {
Name string `json:"name"` //客户端名
Addr string `json:"addr"` //地址
To string `json:"to"` //要连接的客户端名
}
dev := make(map[string]string) //客户端地址
addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:7091") //服务器端口为7091
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer conn.Close()
for {
data := make([]byte, 512)
n, remoteAddr, err := conn.ReadFromUDP(data) //获取客户端地址
if err != nil {
fmt.Println("f", err)
return
}
var J j
json.Unmarshal(data[:n], &J)
fmt.Println(J)
dev[J.Name] = remoteAddr.String() //客户端注册
if !strings.EqualFold(J.To, "")&& dev[J.To]!="" {
outA,_:=json.Marshal(&j{
Name: J.To,
Addr: dev[J.To],
})
outB,_:=json.Marshal(&j{
Name: J.Name,
Addr: remoteAddr.String(),
})
conn.WriteToUDP(outA,remoteAddr)
adr,_:=net.ResolveUDPAddr("udp",dev[J.To])
conn.WriteToUDP(outB,adr)
}
}

客户端demo#

type j struct {
Name string `json:"name"`
Addr string `json:"addr"`
To string `json:"to"`
}
var name = flag.String("name", "", "") //客户端名
var to = flag.String("to", "", "") //要连接的客户端
var msg = flag.String("msg", "", "") //发送消息
var server_ip = flag.String("server", "1.1.1.1", "") //服务器IP
var server_port = flag.Int("sport", 9091, "") //服务器端口
flag.Parse()
conn, _ := net.ListenUDP("udp", &net.UDPAddr{
IP: net.ParseIP("0.0.0.0"),
Port: 9091,
})
defer conn.Close()
put, _ := json.Marshal(&j{
Name: *name,
To: *to,
})
conn.WriteToUDP(put, &net.UDPAddr{
IP: net.ParseIP(*server_ip),
Port: *server_port,
})
res := make([]byte, 128)
n, _, _ := conn.ReadFromUDP(res)
var J j
json.Unmarshal(res[:n], &J)
fmt.Println(string(res[:n]))
if *to != "" {
time.Sleep(1 * time.Second)
}
adr, _ := net.ResolveUDPAddr("udp", J.Addr)
conn.WriteToUDP([]byte("qwe"), adr)
n, _, _ = conn.ReadFromUDP(res)
fmt.Println(string(res[:n]))

使用: A运行:./main -name A B运行:./main -name B -to A -msg "qwe"

参考#

1 NAT - wikipedia

2 UDP - wikipedia

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
【计算机网络】UDP打洞
https://rehtt.com/posts/242/
作者
Rehtt
发布于
2021-02-28
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Rehtt
Hello, I'm Rehtt.
公告
欢迎来到我的博客!
分类
标签
最新动态
站点统计
文章
150
分类
17
标签
73
总字数
67,772
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录