前言
之前工作中有机会接触到p2p相关内容,目标是实现一个p2p下载器,基本可以算是从0开始,看了NAT、torrent、KCP等相关内容。
- torrent 相对成熟,做上层开发也很简单,但是并不适合我们需求的场景; 不过自己下载器实现的时候也参考了
libtorrent
源码上的一些设计。 - KCP 传输上的设计也给了不少启发。
- NAT 这里我认为最完整也最棒的文档来自华为
NAT 简介
- network address translation. 将IP数据报文头中的IP地址转换为另一个IP地址的过程
- 目的: 简单点说是IPV4地址(公网)不够大家用了,NAT设备通过IP层地址替换的方式让多个人使用一个IP地址(公网)
- 问题: NAT也带来最直接的问题就是大家基于N个IP地址被划分成了N个块,而由于NAT类型的限制,我们不容易直接暴露自己的网络到公网了,而P2P网络要求通信双方都可以主动发起访问,NAT也就阻碍了P2P网络应用的使用
NAT STUN
- 简要:STUN协议(Simple Traversal of UDP Through NAT),基于UDP的NAT打洞技术,为了解决NAT给P2P网络带来的问题
- 定义:NAT会话穿越应用程序STUN(Session Traversal Utilities for NAT)是一种由RFC定义的网络协议,作为其他协议(例如SIP、FTP、DNS)处理NAT穿越问题的一个工具。其可以用于检查网络中NAT设备的存在,并确定两个通信端点被NAT设备分配的IP地址和端口号。然后,通过ICE(Interactive Connectivity Establishment),自动创建一条能够进行NAT穿越的数据通道。
NAT 类型(STUN标准)
华为官方文档
https://support.huawei.com/enterprise/zh/doc/EDOC1100112409/fd829977
- Full Cone NAT(完全锥型NAT)
所有从同一个私网IP地址和端口(IP1:Port1)发送过来的请求都会被映射成同一个公网IP地址和端口(IP:Port)。并且,任何外部主机通过向映射的公网IP地址和端口发送报文,都可以实现和内部主机进行通信。
这是一种比较宽松的策略,只要建立了私网IP地址和端口与公网IP地址和端口的映射关系,所有的Internet上的主机都可以访问该NAT之后的主机。 - Restricted Cone NAT(限制锥型NAT)
所有从同一个私网IP地址和端口(IP1:Port1)发送过来的请求都会被映射成同一个公网IP和端口号(IP:Port)。与完全锥型NAT不同的是,当且仅当内部主机之前已经向公网主机发送过报文,此时公网主机才能向私网主机发送报文。 - Port Restricted Cone NAT(端口限制锥型NAT)
与限制锥型NAT很相似,只不过它包括端口号。也就是说,一台公网主机(IP2:Port2)想给私网主机发送报文,必须是这台私网主机先前已经给这个IP地址和端口发送过报文。 - Symmetric NAT(对称NAT)
所有从同一个私网IP地址和端口发送到一个特定的目的IP地址和端口的请求,都会被映射到同一个IP地址和端口。如果同一台主机使用相同的源地址和端口号发送报文,但是发往不同的目的地,NAT将会使用不同的映射。此外,只有收到数据的公网主机才可以反过来向私网主机发送报文。
这和端口限制锥型NAT不同,端口限制锥型NAT是所有请求映射到相同的公网IP地址和端口,而对称NAT是不同的请求有不同的映射。
NAT能访问的时候是怎么样的呢?
- Full Cone NAT(完全锥型NAT)
- 首先192.168.0.11:1024端口请求了102.101.10.1的任意端口
- NAT设备做了地址映射,把192.168.0.11:1024映射到456端口上
- 这样任意访问NAT设备上456端口的数据都会被映射到192.168.0.11:1024上
- 也就是说203.201.20.2发送到NAT:456的数据会被转到192.168.0.11:1024上
- Restricted Cone NAT(限制锥型NAT)(其实这里我更喜欢叫做address-restricted-cone,对应下面的port-restricted-cone)
- 同上 192.168.0.11:1024端口请求了102.101.10.1的任意端口
- NAT设备做了地址映射,把192.168.0.11:1024
+102.101.10.1
映射到456端口上 - 这时候203.201.20.2发送到NAT:456的数据会因为找不到映射被丢弃掉
- 如果 192.168.0.11:1024端口请求了203.201.20.2的任意端口
- NAT设备做了地址映射,把192.168.0.11:1024
+203.201.20.2
映射到456端口上(这里映射一样是456,因为源都是192.168.0.11:1024) - 然后203.201.20.2发送到NAT:456的数据就可以找到正确的私网二元组了
- Port Restricted Cone NAT(端口限制锥型NAT)
- 同上 192.168.0.11:1024端口请求了102.101.10.1的
2048
端口 - NAT设备做了地址映射,把192.168.0.11:1024+102.101.10.1:
2048
映射到456端口上 - 这时候不仅其他IP发送到NAT:456的数据会找不到映射被丢掉,102.101.10.1非
2048
端口的数据发送到NAT:456的数据会因为找不到映射被丢弃掉 - 如果 192.168.0.11:1024端口请求了203.201.20.2的
2048
端口 - NAT设备做了地址映射,把192.168.0.11:1024+203.201.20.2:
2048
映射到456端口上(这里映射一样是456,因为源都是192.168.0.11:1024) - 然后203.201.20.2:2048发送到NAT:456的数据就可以找到正确的私网二元组了
- 同上 192.168.0.11:1024端口请求了102.101.10.1的
- Symmetric NAT(对称NAT)
- 流程和Port Restricted Cone NAT一样
- 只是NAT上的映射不一样了,我们这里假设192.168.0.11:1024+102.101.10.1:2048被映射到了456端口,把192.168.0.11:1024+203.201.20.2:2048被映射到其他端口比如789上
NAT 侦测
这部分华为是个主文字的文档,也建议先看一遍文字版本
STUN 报文
https://tools.ietf.org/html/rfc3489
1 | All STUN messages consist of a 20 byte header: |
STUN 侦测流程图
- 图里有2处虚线的部分,是当时因为bug看到的异常情况,当时发现如果Test II加上一步Test III,会大概率导致结果变成Full Cone,暂时没有找到相关文档说明,大家可以多试试(这也帮助我修改了后面穿透的流程,使得port-restricted-cone和symmetric有更大机会直接穿透)
穿透
穿透的难度
- 根据上面NAT类型映射的方式,我们可以看到非对称类型的NAT设备,如果102.101.10.1是个我们自建的服务器,就可以拿到给203.201.20.2用的二元组,让它可以正常发送消息给192.168.0.11机器
- 对称NAT设备,除了暴力遍历或者一定算法下的遍历试出来789这个端口,基本没有其他办法穿透NAT设备了
原因不明的投机取巧
- 也就是基于上面探测流程里虚线部分的逻辑
- 假设192.168.0.11对应NAT设备10.227.90.131是个symmetric类型,映射的NAT端口是未知的X
- 假设192.168.1.22对应NAT设备201.127.10.1是个port-restricted-cone类型,映射的NAT端口是已知的1000
- step 1. symmetric 给 cone 类型已知端口发送N次消息(这里N次只是尽可能让udp消息到达),消息会被NAT丢掉
- step 2. cone 再给 symmetric 发送N次消息
- 大概率是可以发送成功的,试了很多次,觉得反正这两种类型本就不好穿透,既然这样可行就先备在代码里了