由于 ipv4 地址数量的有限性,导致实际网络部署模式中存在大量的 NAT 网络。对于 NAT 内部的主机,可以主动发起去公网的流量,但对于位于不同 NAT 内的两台主机而言,想要直接进行点对点的连接,就需要用到打洞技术了。
常见的 NAT 类型
-
完全锥型(Full Cone NAT):在完全锥型NAT中,一个内部IP地址和端口的组合在转换为公共IP地址和端口后,可以与任何外部主机进行通信。同时,外部主机可以通过任何地址和端口向内部主机发送数据。这种类型的NAT不限制内部主机与外部主机的通信,因此具有最大的灵活性。
-
受限锥型(Restricted Cone NAT):在受限锥型NAT中,一个内部IP地址和端口的组合在转换为公共IP地址和端口后,只能与一个特定的外部主机进行通信。这个外部主机是在内部主机首次向其发送数据时确定的,之后只有来自这个外部主机的数据才会被NAT转发给内部主机。其他外部主机无法与内部主机直接通信。
-
端口受限锥型(Port Restricted Cone NAT):与受限锥型类似,但还限制了端口号。
-
对称型(Symmetric NAT):在对称型NAT中,一个内部IP地址和端口的组合在转换为公共IP地址和端口后,只能与一个特定的外部主机进行通信。与受限锥型NAT不同的是,这个特定的外部主机是根据内部主机与外部主机之间的通信请求动态选择的,每次与不同外部主机通信时,NAT都会分配一个新的公共IP地址和端口。
对称NAT是一个请求对应一个端口,非对称NAT是多个请求对应一个端口,所以像锥型。
STUN
STUN(Session Traversal Utilities for NAT)是一种基于网络协议的技术,用于解决网络中的NAT(Network Address Translation)问题。STUN协议允许设备在NAT后面找到可用的公共IP地址和端口,以建立点对点连接。它通过一个称为STUN服务器的中间节点来实现。设备首先向STUN服务器发送请求,服务器会在响应中返回设备的公共IP地址和端口。设备可以将这些信息用于直接通信,而无需通过NAT。
STUN协议可以用于多种应用场景,例如实时通信(如VoIP电话、视频聊天)和P2P文件共享等。它为设备提供了一种机制,可以绕过NAT并建立直接的点对点连接,从而提高通信的效率和质量。
STUN 打洞流程
参考官方文档可以很清楚的看懂整个打洞的流程:
+--------+
| Test |
| I |
+--------+
|
|
V
/\ /\
N / \ Y / \ Y +--------+
UDP <-------/Resp\--------->/ IP \------------->| Test |
Blocked \ ? / \Same/ | II |
\ / \? / +--------+
\/ \/ |
| N |
| V
V /\
+--------+ Sym. N / \
| Test | UDP <---/Resp\
| II | Firewall \ ? /
+--------+ \ /
| \/
V |Y
/\ /\ |
Symmetric N / \ +--------+ N / \ V
NAT <--- / IP \<-----| Test |<--- /Resp\ Open
\Same/ | I | \ ? / Internet
\? / +--------+ \ /
\/ \/
| |Y
| |
| V
| Full
| Cone
V /\
+--------+ / \ Y
| Test |------>/Resp\---->Restricted
| III | \ ? /
+--------+ \ /
\/
|N
| Port
+------>Restricted
Figure 2: Flow for type discovery process
该流程使用了三个测试:
- 测试 I,客户端向服务器发送STUN绑定请求,不在CHANGE-Request属性中设置任何标志,也不设置RESPONSE-ADDRESS属性。服务器响应的源地址和端口与客户端请求的目的是一样的。
- 测试 II,客户端发送一个绑定请求,其中包含change-Request属性集中的“更改IP”和“更改端口”标志。服务器响应的原地址和端口都将改变。
- 测试 III,客户端发送仅设置了“更改端口”标志的绑定请求。服务器响应的源地址与客户端请求的目的地址一样,但源端口发生改变。
第一步:客户端首先启动测试 I,如果此测试没有响应,则客户端立即知道它无法进行 UDP 连接。如果测试产生响应,客户端将检查MAPPED-ADDRESS属性:如果此地址和端口与用于发送请求的套接字的本地IP地址和端口相同,则客户端知道它不在 NAT后,如果不同,则在 NAT后面。
第二步:接着执行测试 II,如果收到响应,客户端就知道它是全锥型 NAT 或者位于公网(无需第三步了)。第一步中如果判断是在公网,第二步失败则说明有防火墙限制;第一步如果判断在 NAT 后,第二步失败则说明在某种限制锥型或者对称 NAT 后面。
第三步:第一步判断在NAT后,第二步没有收到响应,它会再次执行测试 I,但这次是对测试 I 的响应中 CHANGED-address 属性的地址和端口执行测试 I(即换了测试 I 的目的地址和端口号)。如果 MAPPED-address 属性中返回的地址和端口与第一个测试 I 中的不同,则客户端知道其位于对称 NAT 之后(无需第四步了)。如果地址和端口相同,则客户端位于受限 NAT 或端口受限NAT之后。
第四步:为了确定在哪个中受限 NAT 后面,客户端启动测试 III。如果收到响应,则它在受限NAT 后面,如果没有收到响应,它在端口受限 NAT 后面。
附
打洞客户端:GitHub - ccding/go-stun: A go implementation of the STUN client (RFC 3489 and RFC 5389)
公用stun服务器:公用 Stun 服务器列表 - FreeSWITCH-CN中文社区