NAT 原理以及 UDP 穿透

本文详细介绍了 NAT 的原理,并以此作为基础介绍了 UDP 穿透的原理和实现。

一、NAT基础和分类

NAT(Network Address Translation)全称为「网络地址转换」,用于为了解决 IPv4 地址短缺的问题。NAT 可以将私有 IP 地址转换为公有 IP 地址,以便多台内网主机只需要共用一个公有 IP 地址,便可以正常与互联网进行通信。

NAT 可以分为两大类:

  1. 基础NAT:网络地址转换(Network Address Translation)
  2. NAPT:网络地址端口转换(Network Address Port Translation)
    nat_1.png

1. 基础NAT

基础 NAT 仅对网络地址进行转换,要求对每一个当前连接都要对应一个公网IP地址,所以需要有一个公网 ip 池;基础 NAT 内部有一张 NAT 表以记录对应关系,其映射关系如下:

内网ip外网ip
192.168.1.11.2.3.4
192.168.1.121.2.3.5
192.168.1.1231.2.3.6

基础 NAT 又分为:静态 NAT 和 动态 NAT ,其区别在于:静态要求内网ip和外网ip存在固定的一 一对应关系,而动态不存在这种固定的对应关系。

2. NAPT

NAPT 需要对网络地址和端口都进行转换,这种类型允许多台主机共用一个公网 ip 地址,NAPT 内部同样有一张 NAT 表,并标注了端口,以记录对应关系,其映射关系如下:

内网ip外网ip
192.168.1.10:10251.2.3.4:1025
192.168.1.11:33331.2.3.5:3344
192.168.1.12:77881.2.3.6:2000

NAPT又分为:锥型 NAT 和 对称型 NAT ,其对于映射关系有不同的权限限制。
锥型 NAT 在网络拓扑图上像圆锥,而对称型 NAT 在网络拓扑图上呈现对称性。

二、NAPT分类

总的来说, NAPT 可分为四种类型:1.完全锥型,2.受限锥型,3.端口受限锥型,4.对称型。

目前市场上常见的都是 NAPT 类型,我们常说的 NAT 一般也是特指 NAPT ,故下文均用 NAT 代表 NAPT

1. 完全锥型

从同一个内网地址端口(192.168.1.1:7777)发起的请求都由 NAT 转换成公网地址端口(1.2.3.4:10000)。
而内网地址端口(192.168.1.1:7777)可以收到任意外部主机发到公网地址端口(1.2.3.4:10000)的数据报。
nat_2.png

2. 受限锥型

受限锥型也称地址受限锥型,其在完全锥型的基础上,对 ip 地址进行了限制。

从同一个内网地址端口(192.168.1.1:7777)发起的请求都由 NAT 转换成公网地址端口(1.2.3.4:10000),其访问的服务器为(8.8.8.8:123)。
只有当(192.168.1.1:7777)主动向(8.8.8.8:123)发送一个报文后,(192.168.1.1:7777)才可以收到(8.8.8.8:xxxx)发往(1.2.3.4:10000)的报文,进而发送给(192.168.1.1:7777),而其它地址如(9.9.9.9:456)发送的报文则会被 NAT 丢弃。
nat_3.png

3. 端口受限锥型

端口受限锥型,是在受限锥型的基础上,对端口也做了进一步的限制。

从同一个内网地址端口(192.168.1.1:7777)发起的请求都由 NAT 转换成公网地址端口(1.2.3.4:10000),其访问的服务器为(8.8.8.8:123)。
只有当(192.168.1.1:7777)主动向(8.8.8.8:123)发送一个报文后,(192.168.1.1:7777)才可以收到(8.8.8.8:123)发往(1.2.3.4:10000)的报文,进而发送给(192.168.1.1:7777),而其它地址端口如(8.8.8.8:456)和(9.9.9.9:456)发送的报文则会被 NAT 丢弃。
nat_4.png

4. 对称型

在对称型 NAT 中,只有来自于同一个内网地址端口、且针对同一目标地址端口的请求才会被 NAT 转换至同一个公网地址端口,否则的话, NAT 将为之分配一个新的公网地址端口。

例如:内网地址端口(192.168.1.1:7777)发起请求到(8.8.8.8:123),由 NAT 转换成公网地址端口(1.2.3.4:10000),随后内网地址端口(192.168.1.1:7777)又发起请求到(9.9.9.9:456),则 NAT 将分配新的公网地址端口(1.2.3.4:20000),而其它地址端口如(8.8.8.8:456)和(9.9.9.9:123)发送的报文则会被 NAT 丢弃。
nat_5.png

对称型 NAT 其实也可以理解为升级版的端口受限锥型 NAT ,其在端口受限锥型的基础上,增加了公网地址端口一对一分配功能。
可以这么说,在锥型 NAT 中映射关系和目标地址端口无关,而在对称型 NAT 中则有关。锥型 NAT 正因为其于目标地址端口无关,所以网络拓扑是圆锥型的。

如下图所示,在锥型 NAT 中对于同一个内网地址端口来说其公网地址端口(1.2.3.4:10000)是固定的。
nat_6.png

三、NAT的工作流程

按照上文的描述,我们可以很好的理解 NAT 对传输层协议(TCP/UDP)的处理,这里举例来更加深入的理解 NAT 的原理。

1. 发送数据

当一个 TCP/UDP 的请求 ( 192.168.1.1:7777 => 8.8.8.8:123 )到达 NAT 网关( 1.2.3.4 )时,由 NAT 修改报文的源地址和源端口以及相应的校验码,随后再发往目标:

192.168.1.1:7777 => 1.2.3.4:10000 => 8.8.8.8:123

2. 接收数据

随后( 8.8.8.8:123 )返回响应数据到( 1.2.3.4:10000 ), NAT 查询映射表,修改目的地址和目的端口以及相应的校验码,再将数据返回给真实的请求方:

8.8.8.8:123 => 1.2.3.4:10000 => 192.168.1.1:7777

3. 其他协议

对于常见的网络协议来说,除了 TCP/UDP 协议外还有 ICMP 等特殊协议,以及自定义实现的私有协议。例如 ICMP 协议工作在 IP 层,没有端口信息,NAT 以 ICMP 报文中的 identifier 作为标记,以此来判断这个报文是内网哪台主机发出的。下图为 Cisco Packet Tracer 下,在客户端发起 TCP/UDP/ICMP 请求后的 NAT translations
nat_7.png

当然还有一些特殊的协议,比如 FTP 协议,当请求一个文件传输时,主机在发送请求的同时也通知对方自己想要在哪个端口接受数据,NAT 必须进行特殊处理才能支持这种通信机制。
在 NAT 中有一个应用网关层(Application Layer Gateway, ALG),以此来统一处理这些协议问题。

4. 映射老化时间

建立了 NAT 映射关系后,这些映射什么时候失效呢?

不同协议有不同的失效机制,比如 TCP 的通信在收到 RST 过后就会删除映射关系,或 TCP 在某个超时时间后也会自动失效。而 ICMP 在收到 ICMP 响应后就会删除映射关系,当然超时后也会自动失效。具体的实现还和各个厂商有关系。

四、NAT类型探测

探测 NAT 的类型是 NAT 穿透中的第一步,我们可以通过客户端和两个服务器端的交互来探测 NAT 的工作类型,以下是来源于 STUN 协议(https://tools.ietf.org/html/rfc3489) 的探测流程图,在其上添加了一些标注:
nat_8.png

如图所示,我们可以整理出:

  1. 客户端使用同一个内网地址端口分别向主服务器和协助服务器(不同IP)发起 UDP 请求,主服务器获取到客户端出口地址端口后,返回给客户端,客户端对比自己本地地址和出口地址是否一致,如果是则表示处于 Open Internet 中。
  2. 协助服务器同样也获取到了客户端出口地址端口,将该信息转发给主服务器,同样将该信息返回给客户端,客户端对比两个出口地址端口(1、主服务器返回的,2、协助服务器返回的)是否一致,如果是则表示处于 Symmetric NAT 中。
  3. 客户端再使用不同的内网地址端口分别向主服务器和协助服务器(不同IP)发起 UDP 请求,主服务器和协助服务器都可以获得一个新的客户端出口地址端口,协助服务器将客户端出口地址端口转发给主服务器。
  4. 主服务器向协助服务器获取到的客户端出口地址端口发送 UDP 数据,客户端如果可以收到数据,则表示处于 Full-Cone NAT 中。
  5. 主服务器使用另一个端口,向主服务器获取到的客户端出口地址端口发送 UDP 数据,如果客户端收到数据,则表示处于 Restricted NAT 中,否则处于 Restricted-Port NAT 中。

实际网络往往都更加复杂,比如:防火墙、多层 NAT 等原因,会导致无法准确的探测 NAT 类型。

当然,实际上我们并不需要手动实现上述过程,我们有现成的NAT测试工具可用:
GitHub:NatTypeTester
下载地址:https://github.com/HMBSbige/NatTypeTester/releases/download/3.4/NatTypeTester.exe
打开软件,点击左侧菜单栏RFC 3489:
nat_9.png

上方菜单栏中可选择STUN服务器,大陆境内推荐使用QQ和MIWIFI:
nat_10.png

点击TEST即可得到测试结果,如上图的FullCone即为NAT1 (完全锥型)

五、UDP穿透

在 NAT 的网络环境下,P2P 网络通信需要穿透 NAT 才能够实现。在熟悉 NAT 原理过后,我们就可以很好的理解如何来进行 NAT 穿透了。NAT 穿透的思想在于:如何复用 NAT 中的映射关系?

锥型NAT中,同一个内网地址端口访问不同的目标只会建立一条映射关系,所以可以复用,而对称型NAT不行。同时,由于 TCP 工作比较复杂,在 NAT 穿透中存在一些局限性,所以在实际场景中 UDP 穿透使用得更广泛一些,这里我们详细看看 UDP 穿透的原理和流程。

我们以 Restricted-Port NAT 类型作为例子,因为其使用得最为广泛,同时权限也是最为严格的,在理解 Restricted-Port NAT 类型穿透后,Full-Cone NATRestricted NAT 就触类旁通了;
在实际网络场景下往往都是非常复杂的,比如:防火墙、多层NAT、单侧NAT,这里我们选择了两端都处于一层 NAT 的场景来进行演示讲解,可以让我们更容易的进行理解。

在我们的演示环境下,有 PC1,Router1,PC2,Router2,Server 五台设备;公网服务器用于获取客户端实际的出口地址端口,UDP 穿透的流程如下:

  1. PC1(192.168.1.1:7777) 发送 UDP 请求到 Server(9.9.9.9:1024),此时 Server 可以获取到 PC1 的出口地址端口(也就是 Router1 的出口地址端口) 1.2.3.4:10000,同时 Router1 添加一条映射 192.168.1.1:7777 <=> 1.2.3.4:10000 <=> 9.9.9.9:1024
  2. PC2(192.168.2.1:8888) 同样发送 UDP 请求到 Server,Router2 添加一条映射 192.168.2.1:8888 <=> 5.6.7.8:20000 <=> 9.9.9.9:1024
  3. Server 将 PC2 的出口地址端口(5.6.7.8:20000) 发送给 PC1
  4. Server 将 PC1 的出口地址端口(1.2.3.4:10000) 发送给 PC2
  5. PC1 使用相同的内网地址端口(192.168.1.1:7777)发送 UDP 请求到 PC2 的出口地址端口(Router2 5.6.7.8:20000),此时 Router1 添加一条映射 192.168.1.1:7777 <=> 1.2.3.4:10000 <=> 5.6.7.8:20000,与此同时 Router2 没有关于 1.2.3.4:10000 的映射,这个请求将被 Router2 丢弃
  6. PC2 使用相同的内网地址端口(192.168.2.1:8888)发送 UDP 请求到 PC1 的出口地址端口(Router1 1.2.3.4:10000),此时 Router2 添加一条映射 192.168.2.1:8888 <=> 5.6.7.8:20000 <=> 1.2.3.4:10000,与此同时 Router1 有一条关于 5.6.7.8:20000 的映射(上一步中添加的),Router1 将报文转发给 PC1(192.168.1.1:7777)
  7. 在 Router1 和 Router2 都有了对方的映射关系,此时 PC1 和 PC2 通过 UDP 穿透建立通信。

nat_11.png

六、拓展研究

在实践了以上步骤后,我们对 锥型NAT 下的 UDP 穿透已经有了大致的了解,那我们接着再拓展研究一下「其他场景」。

1. Symmetric NAT可以穿透吗?

根据 Symmetric NAT 的特性我们可以知道当请求的目标端口地址改变后,会创建新的一对映射关系,我们无法知晓新的映射关系中的端口号;但是在实际场景下,部分路由器对于 Symmetric NAT 的生成算法过于简单,新的端口可能呈现于:递增、递减、跳跃等特征,所以这种条件下,我们可以基于端口猜测,来穿透 Symmetric NAT

如果两端的 Symmetric NAT 路由器是已知的,我们可以直接逆向分析映射生成算法,即可准确预测端口号。

2. TCP穿透有哪些难点?

TCP 穿透的流程基本和 UDP 穿透一样。

在标准 socket 规范中,UDP 可以允许多个 socket 绑定到同一个本地端口,但 TCP 不行,在 TCP 中我们不能在同一个端口上既 listen 又进行 connect;不过在部分操作系统下 socket 提供了端口复用选项(SO_REUSEADDR / SO_REUSEPORT) 可以允许 TCP 绑定多个 socket。

在使用端口复用选项后,TCP 就按照 UDP 穿透的流程一样借助公网服务器然后向对端发送 syn 报文了,其中靠后的 syn 报文就可以正确穿透完成 TCP 握手并建立连接。

但是在实际场景下还有诸多的阻碍,不同厂商的 NAT 实现机制有一些差异,比如某些针对 TCP 的实现有:

  1. 对端 NAT 在接收到 syn 由于没有找到映射而返回 RST 报文,而本端 NAT 在接收到 RST 报文后删除了此条映射
  2. 由于主机生成的 syn 报文中的 seq 序号为随机值,如果 NAT 开启了 syn 过滤,对于没有标记过的 seq 的报文将直接丢弃
  3. 其它不可控因素等等

3. 无第三方服务器的穿透

目前可行的一种不需要第三方服务器实现 NAT 穿透的项目:
https://github.com/samyk/pwnat

文中作者先提出了一种便于理解的网络拓扑,客户端位于公网,服务器位于 NAT 下,我们必须预先知道服务器的公网地址;在这个方法下,服务器不断的向外部未分配的地址发送 ICMP(ECHO REQUEST) 消息,服务器端的 NAT 将保留一条 ICMP 响应的映射,由于目的地址未分配所以没有设备会响应服务器发出的请求,此时由客户端发送一条伪装的 ICMP(DESTINATION UNREACHABLE) 给服务器,服务器可以收到该条消息并从中获取到客户端的地址;随后便可以根据预先约定的端口进行穿透并通信了。

但是如果客户端也位于 NAT 下呢,由于 NAT 可能会更改源端口信息(不同厂商的NAT实现不同),导致无法向上文一样使用预设端口进行通信,所以这里需要和 Symmetric NAT 穿透一样进行端口猜测。

七、总结

本文从 NAT 原理出发,详细介绍了不同 NAT 类型的工作流程和原理,在此基础上我们深入学习和实现了 锥型NAT 的穿透,并拓展介绍了一些特殊的穿透场景。

NAT 的出现极大的缓解了 IPv4 地址短缺,同时也延迟了 IPv6 的推广,但 IPv6 是大势所趋,未来使用 NAT 的场景可能会慢慢减少;但无论怎样, NAT 的原理和策略都非常值得我们学习,比如:

  1. NAT 是一个天然的防火墙,
  2. NAT 其实可以看作是代理服务器,
  3. NAT 可以作为负载均衡服务器,
  4. 等等。