1. NAT
1.1 介绍
NAT(Network Address Translation)指的是网络地址转换。
网络分为私网和公网两个部分,NAT网关设置在私网到公网的路由出口位置,私网与公网间的双向数据必须都要经过NAT网关。
组织内部的大量设备,通过NAT就可以共享一个公网IP地址,解决了IPv4地址不足的问题。同时NAT也起到隐藏内部设备,安全防护的作用。
下图所示,有两个组织,每个组织的NAT分配一个公网IP,分别是1.2.3.4以及1.2.3.5。

1.2 问题

左边私网地址为192.168.1.100的设备要跟右边组织内的设备进行通信,由于右边组织多台设备共享一个公网IP,所以不能直接通过公网IP地址端口号进行通信,数据发过去了,根本不知道送到哪台设备,这样两个组织内的设备就不具有点对点通信的能力。
数据要怎么穿过NAT到达私网内,NAT网关要如何转发数据到指定设备呢?
2. NAT穿透技术
NAT的存在使得设备间不能直接进行点对点通信。这些相关技术就是NAT穿透(NAT traversal)。
目前常见的NAT穿透技术、方法主要有:
- 应用层网关;
- 中间件技术;
- 打洞技术(Hole Punching);
- Relay(服务器中转)技术。
没有一种完美的NAT穿透,常常是多种技术互相配合,最常见的一种方案是打洞配合中转,例如后面说到的ICE方案。
2.1 NAT打洞技术(私网主动)
工作在传输层,最为常见。
NAT网关维护着一张关联表,进行公网/私网地址端口的转换,结构如下所示:
私网IP | 公网IP |
---|---|
192.168.1.100:5566 | 1.2.3.4:9200 |
192.168.1.101:80 | 1.2.3.4:9201 |
192.168.1.102:4465 | 1.2.3.4:9202 |
如下图,左边组织的公网IP为1.2.3.4NAT网关收到发到1.2.3.4:1234的数据,假如关联表中还未存在映射关系,NAT对外部发来的数据包直接丢掉。

所以网络访问只能先由私网侧发起,公网无法主动访问私网主机,既然这样,就由私网侧主动点。如下图:

私网地址10.0.0.100的设备发送数据包到公网。NAT网关关联表中创建了该设备私网地址:端口到公网地址:端口的映射,即上图中的10.0.0.100:1234到1.2.3.4:1234。
这相当于在NAT上打了一个洞,其它人就可以通过这个“洞”把数据传进来,这就是为什么叫打洞技术了。
接下来,通过某种方式将打好的洞信息:NAT映射后的公网地址:端口告诉要通信那方,要通信那方就向该洞:1.2.3.4:1234发数据。NAT收到发到1.2.3.4:1234的数据,NAT网关在关联表中找到映射,然后转发数据到对应私网设备(10.0.0.100:1234)。

这里我们总结下打洞技术:
1)首先位于NAT后的Peer1节点需要向外发送数据包,以便让NAT建立起私网Endpoint1(IP1:PORT1)和公网Endpoint2(IP2:PORT2)的映射关系;
2)然后通过某种方式将映射后的公网Endpoint2通知给对端节点Peer2;
3)最后Peer2往收到的公网Endpoint2发送数据包,然后该数据包就会被NAT转发给私网的Peer1。
2.2 Relay(服务器中转)技术
在有些情况下,打洞会失败。
此时只能通过部署在公网的第三方服务器进行数据转发,间接实现通信。
3. NAT类型
由于存在不同的NAT部署方式,所以产生了不同类型的NAT。

3.1 完全圆锥型NAT(最宽松)
对于完全圆锥形 NAT,内网 IP 和内网端口号,被映射为外部 IP 和外部端口号。当路由器收到来自外部的报文时,只要报文的目的 IP 和目的端口号,匹配到 NAT 表项的外部 IP 和外部端口号,都会转换为对应的内网 IP 和内网端口号,转发到内网设备。
对于外部报文,路由器并不关心报文的源 IP 和源端口号(即报文来自谁),只要收到匹配 NAT 表项的报文,都能发送到内网设备。所以,完全圆锥形 NAT 是最宽松的 NAT,打洞最方便。
【前人栽树,所有人都可乘凉】
3.2 地址受限圆锥型NAT(IP受限)
与完全圆锥形 NAT 相比,受限圆锥形 NAT,在内网设备向外发送报文时,路由器除了生成 NAT 表项,还会根据报文的目的 IP,记录下内网设备正在与哪些外部设备通信。
这样,只有内网设备先发送报文给外部设备,外部设备回应的报文,才会被转发到内网设备。而其他外部设备发送过来的报文,即使匹配 NAT 表项,也无法发送到内网设备。
这样的 NAT 安全性有一定的提高,但是也提高了打洞难度。两台内网设备需要互相给对方发送一个报文,才能打洞成功。
【两台主机,必须建立过关系】
3.3 端口受限圆锥型NAT(IP端口受限)
端口受限圆锥形 NAT 和受限圆锥形 NAT 类似,但增加了检查的严格程度:受限圆锥形 NAT,只会外部设备的 IP 地址,来检查内网设备与哪些外部设备通信过。而端口受限圆锥形 NAT,会同时根据 IP 地址和端口号来进行检查。
【两台主机,包括端口,都要建立过关系】
3.4 对称NAT(每次都新端口号)
前面的三种圆锥形 NAT,会根据内网设备发出去的报文的源 IP、源端口号两个信息建立 NAT 表项,将内网 IP 和内网端口号映射到外部 IP 和外部端口号。内网设备发出去的报文,无论目的 IP 和目的端口号如何变化,不管发给哪台外部设备,都会被映射为相同的外部 IP 和外部端口号。
而对称 NAT,会同时根据内网设备出方向报文的源 IP、源端口号、目的 IP、目的端口号四个信息来建立 NAT 表项。如果报文的目的 IP、目的端口号发生了变化,映射到的外部端口号也会发生改变。
对于对称 NAT,我们再来回顾一下前文中 NAT 打洞的过程。内网设备首先和第三方服务器通信,内网 IP 和内网端口号会被映射为一个外部 IP 和外部端口号。接下来,内网设备和另一台设备通信,相同的内网 IP 和内网端口号,又会被映射为另外一个外部端口号。这样,NAT 打洞就无法成功。
所以,在对称 NAT 下,很难进行 NAT 打洞。
【变成多个,无法使用】
3.5 总结
安全性系数, 对称型 > 端口受限锥型 > 受限锥型 > 全锥型
如果NAT是完全圆锥型的,那么双方中的任何一方都可以发起通信。
如果NAT是受限圆锥型或端口受限圆锥型,双方必须一起开始传输。
若有一方位于对称NAT后,就无法打洞成功,只能使用中转方案。
4. NAT穿透方案
4.1 STUN
STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序),是基于UDP的完整的穿透NAT的解决方案,属于我们前面说到的打洞技术。
STUN是一种Client/Server的协议,也是一种Request/Response的协议,默认端口号是3478。
它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的公网端端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间创建UDP通信。
四种主要NAT类型中有三种是可以使用STUN进行穿透:完全圆锥型NAT、受限圆锥型NAT和端口受限圆锥型NAT,对称型NAT则不能使用。
4.2 TURN
TURN主要用在使用STUN无法穿透的场景下,例如前面说到的对称型NAT,只能通过TURN server进行数据中转。
TURN(Traversal Using Relay NAT,通过Relay方式穿越NAT),是一种数据传输协议。允许通过TCP或UDP方式穿透NAT或防火墙。
TURN是一个Client/Server协议。TURN的NAT穿透方法与STUN类似,都是通过取得应用层中的公网地址达到NAT穿透。
但实现TURN client的终端必须在通讯开始前与TURN server进行交互,并要求TURN server产生”relay port”,也就是relayed-transport-address。
这时TURN server会建立peer,即远端端点(remote endpoints),开始进行中继(relay)的动作,TURN client利用relay port将数据传送至peer,再由peer转传到另一方的TURN client。
4.3 ICE
ICE(Interactive Connectivity Establishment,互动式连接建立)。ICE定义了穿越方法而不是协议。
ICE整合了STUN与TURN。ICE使得两个NAT后的端点通信更加便捷。ICE使用STUN进行打洞,若失败,则使用TURN进行中转。
例如用户Alice要与Bob进行通信,这两个用户都位于NAT后,公网部署了STUN与TURN服务器。

收集候选地址(candidate)
首先客户端要收集candidate。candidate表示候选地址,由IP地址与端口组成。收集的candidate要与对方的candidate组成candidate pair,进行连通性检查。candidate主要有三种:
- Host candidate(host):从本地网卡上获取的地址
- Server reflexive candidate(srflx):STUN server 观察的该客户端的地址
- Relay reflexive candidate(relay):TURN server 为该客户端分配的中继地址
客户端通过向STUN服务器发送STUN数据包,STUN服务器做出回应,告知其在数据包中监测到的IP地址以及端口。
下图中,Alice与Bob通过STUN以及TURN服务器收集了三种类型的candidate。

连通性检查
收集candidate后把通过信令offer与answer方式双方交换candidate,进行candidate两两配对,然后ICE连通性检查。这个连通性检查按一定规则的。
本地的candidate与远端candidate构成的每一对都有一定的优先级,按优先级排序进行连通性检查。
数据传输
最后从有效的candidate组合中选择优先级最高的作为传输地址,用于数据传输。

若要让我们的程序支持ICE,我们可以借助第三方库。常见的支持ICE的库有Libjingle,Libnice。Libjingle集成在WebRTC里,不方便独立使用,这里我们推荐使用Libnice,常见的WebRTC服务器,例如janus,licode都是使用libnice进行P2P通信,具体可访问Libnice官网了解。