随着因特网规模的飞速发展,联网设备数量不断增加,地址空间大小只有2^32的IPv4地址正面临着枯竭,而作为下一代网络层协议的IPv6虽然拥有巨大的地址数量,但面对庞大的历史遗留问题也显得力不从心。在这种情况下,NAT(Network Address Translation,网络地址转换)技术应运而生。NAT的作用,是让多个拥有独立内网IP的设备,能够共用一个外网IP和外部进行通信。由于不同内网IP互不干涉,不同内网下的设备可以使用同一个内网IP,这样一来,IP地址的短缺现象就大大缓解了。
NAT基本原理和大致流程
NAT技术搭载在路由器上,通过修改经由该路由器转发的数据报来达到目的。众所周知,无论是TCP还是UDP传输协议,都涉及到四个属性:源地址、源端口、目标地址和目标端口。假设设备A要向设备B发送数据,它会把自己的地址,以及设备B的IP地址放入IP数据报的首部。在没有NAT存在的情况下,两个设备之间的路由器会根据这个首部信息和自身的路由表,将数据报转发向另一个更接近B的路由器,直至数据报最终到达设备B。在这个过程中,虽然数据报经过了多个路由器的转发,但它的首部是不变的。
然而,使用了NAT技术的路由器(以下称它为N)会打破这个规则。N会把自己的管理的网段分为内外两侧,位于两侧的主机进行的一切通信都要经过N的转发。在每个外侧网段,N都需要一个全球唯一的外网IP地址(除非这个网段位于另一个NAT的内部,这种情况下,外网IP只需在该网段内唯一即可),而在内侧网段,所有主机,包括N自身的IP都可以任意决定,但为了避免和已有的外网IP冲突导致分不清内外侧的主机,一般都会是一些特殊格式,例如我们常见的“192.168.x.x”。
假设A位于N的内侧,那么在A第一次用自己的端口X向B发送数据的时候,N首先会根据路由表找出B在哪个外侧网段上,在对应网段上为A分配一个端口Y,然后在自身的内存中新建一对映射关系:“外网端口Y对应内网主机A的端口X”。之后,它会偷偷修改数据报的首部,把源地址改成自己对应网段的外网IP,还会修改数据报的内容,把源端口从X改成Y(注意“端口”是传输层的概念,不属于IP数据报的首部)。随后它又回到了一个正常路由器的行为,把数据报转发向下一跳路由器。这样一来,在位于外侧网段的B看来,所有来自A的通信看起来都像是由N发起的,于是它会被“蒙在鼓里”,将N的外网IP和端口Y作为目标地址和目标端口来发送响应。N在收到响应后,会从映射表中查找之前建立的端口Y条目,然后根据条目内容再一次修改数据报,把目标地址改成A,目标端口改成X,最后把数据报发送给A。在A看来,这个数据报也是正常由B发出的,没有任何异常之处,于是它就高兴地接受了这个数据报。
以上就是NAT的完整工作流程了。从上面的描述中可以看出,NAT是一种覆盖网络层和传输层的,对通信双方和中间路由透明的外网地址复用手段。由于内网中的所有IP经NAT转换后,都只对应着一个由NAT设备持有,和它们毫不相干的外网IP,自然就不用考虑为每个内网设备分配一个在外网中唯一的IP了。
NAT的固有缺陷
俗话说“没有银弹”,看似美好的NAT技术也有着一个致命的缺陷:NAT内侧的设备无法作为连接的接受方,换句话说,不能作为服务器。理由很显然:在内侧设备A没有主动向外侧发起连接的情况下,NAT设备N中根本没有对应的映射关系,无论外网设备访问N的哪个端口,都没办法让N将数据转发给A。这个问题在传统的C/S模式下还没那么严重,因为服务器一般都有自己的公网IP;但在P2P网络中,每个主机都需要扮演服务器的角色,NAT后的主机根本无法成为有效的P2P节点。这就是P2P下载速度在国内很慢的最主要原因:国内的ISP大量使用了NAT技术,导致宽带用户无法获取到公网IP。因此,人们对P2P应用的需求,催生了各种NAT穿透技术。
NAT类型
在讲解NAT穿透的各种方式前,首先有必要了解NAT的不同类型,以便 “逐个击破”。总的来说,NAT主要分为两大类:
- 锥型(Cone)
- 对称型(Symmetric)(D类)
其中锥形NAT还分为以下三个子类:
- 完全(Full)(A类)
- 地址受限型(Address Restricted)(B类)
- 端口受限型(Port Restricted)(C类)
习惯上,还可以用后面括号中的字母代指NAT类型。在之后的讨论中,为简略起见,我们假设NAT设备只有一个外网IP。
锥型NAT vs 对称型NAT
锥型NAT和对称型NAT的主要区别,在于内部主机向新的外部地址发送数据时,NAT设备的行为。下面我们假设内部主机的端口X已经和NAT设备的外网端口Y建立了映射关系。在两种NAT中,这个关系都会一直保持下去,直到一段时间没有使用后被删除。
- 在锥型NAT中,只要内部主机继续用端口X发送数据,不管发送给谁,这个数据的外网侧源端口都会被转换成Y。
- 在对称型NAT中,只要发送目标的IP或者端口发生了变化,哪怕源端口依然是X,路由器也会建立一个新的映射关系,用一个新的外网端口向这个目标进行转发。
用形式化的说法来总结的话,锥型NAT的映射函数是(内网源IP,源端口)-》(外网端口),而对称型NAT的映射函数是(内网源IP,源端口,目标IP,目标端口)-》(外网端口)。显然,对称型NAT对端口资源的消耗更大,对内网设备限制也更大,因此穿透难度也更大。
锥型NAT的三个子类
锥型NAT三个子类的主要区别,在于外部主机向已经建立映射关系的端口发送数据时,NAT设备的行为。下面我们依然假设内部主机A的端口X已经和NAT设备的外网端口Y建立了映射关系。此外,假设这个映射关系是A通过访问外网主机B的端口Z建立起来的。
- 在完全锥型NAT中,NAT设备会无条件将Y收到的数据报转发给A的端口X,不论数据报来自何处。
- 在地址受限型NAT中,只有来自B的数据报才会被转发,但不论是来自B的哪个端口。
- 在端口受限型NAT中,只有来自B且端口为Z的数据报才会被转发。
- 对称型NAT在这方面的行为和端口受限型NAT一致。
可见,三个子类的限制依次加强,穿透难度也依次增加。
NAT穿透
首先给出(笔者个人)对NAT穿透的定义:一种在外网中继节点的帮助下,让两个位于各自NAT后的主机建立点对点连接的手段。从以上定义中可以看出:
- 只有通信双方都位于NAT后面时,才需要进行穿透。如果任意一方拥有外网地址,只需让该主机作为连接接受方,另一方作为连接发起方即可。
- NAT穿透必须有一个拥有外网IP的中继节点的帮助,但连接建立后发送的数据并不经过中继节点。
接下来,我们将通信双方的NAT类型分为三种情况,对相应的穿透手段分别进行讨论。下面我们假设A和B之间要建立连接,它们的NAT设备分别为NA和NB,中继节点为S。
双方都是ABC类NAT
这是最简单的情况,流程如下:
- A向S发送一个数据报,S收到的数据报会拥有NA的源地址和一个在NA上新分配的源端口X。
- B向S发出请求,获取NA的地址和端口X。S在收到B向A的连接请求后,会把请求中包含的NB源地址和端口Y回传给A。由于A刚刚在NA上建立了和S的映射关系,这个消息可以成功到达A。
- A向NB的端口Y发送一个任意数据报,这个数据报依旧拥有端口X(回忆下锥型NAT的定义)。它不会到达B,但会在NA上允许来自NB端口Y的数据由端口X传入并到达A。
- B向NA的端口X发出连接请求。由于这个请求依旧拥有端口Y,它会被NA转发给A。
- A向NB的端口Y发送回复,回复会被NB转发给B,连接成功建立。
注意:1、3、5步A发送信息使用的端口必须是同一个,2、4步B的端口也必须是同一个。个中原因留给大家自行思考,结合之前的NAT原理就能很快得出答案。
第三步A向NB发送数据报的过程被形象地称为“打洞”,因为这个数据报成功在NA上开了一个“洞”,来自NB的外部连接可以从“洞”里进来到达A。退一步来说,假设NA是一个完全锥型NAT,甚至连打洞都不需要,B在收到NA的地址和端口后,直接向那里发送连接请求即可。
一方是D类NAT,另一方是AB类NAT
这种情况和上一种差不多,但“打洞”的一方必须是位于AB类NAT后的那方。具体流程和上面相同,只是要把AB类NAT后的一方看成A,另一方看成B。
为什么D类不能打洞?因为在第三步中,就算A使用了和第一步相同的端口,在NA上也会被转换为和X不同的另一个端口。这样一来,B还是无法向X发送数据,因为X并没有和NB建立联系。A也没办法把这个新端口告诉B,因为这个端口A不知道,S也不知道。
一方是D类NAT,另一方是CD类NAT
不幸的是,这种情况下NAT穿透是不可行的,只能通过中继服务器来转发A和B之间的一切数据。
那么,为什么C类也不能打洞了?原因很简单,如果对面是D类NAT,就算打了洞,第四步也会失败,因为B无法用之前约定好的端口Y来连接NA,也就是“进不去洞”。如果打洞者换成B类就好说了,这种情况下只要NB的IP不变,连接请求就可以“进洞”。
NAT穿透相关协议
- 部署STUN协议的服务器可以检测用户的NAT类型,并且告知用户在公网上的IP地址。
- 部署TURN协议的服务器可以在无法穿透时转发用户的TCP和UDP流量。
- UPnP协议允许应用主动在NAT上打洞,并且让这个洞的表现看起来像是完全锥型NAT。
这些协议的具体细节就不再详细叙述了,感兴趣的读者可以自行了解。