网络:NAT STUN

前言

之前工作中有机会接触到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的数据就可以找到正确的私网二元组了
  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
All STUN messages consist of a 20 byte header:

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Transaction ID
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The Message Types can take on the following values:

0x0001 : Binding Request
0x0101 : Binding Response
0x0111 : Binding Error Response
0x0002 : Shared Secret Request
0x0102 : Shared Secret Response
0x0112 : Shared Secret Error Response

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次消息
  • 大概率是可以发送成功的,试了很多次,觉得反正这两种类型本就不好穿透,既然这样可行就先备在代码里了

参考

------ 本文结束 ------
------ 版权声明:转载请注明出处 ------