好久没有写也没有填系列文章了,正好之前文章提到了Fullcone,所以干脆写一篇文章来好好聊聊NAT相关的内容。NAT作为当今现实网络中不可或缺的一部分,虽然应用广泛,但是对它的介绍却远不及其他网络协议。另一方面,IETF也把NAT视为IPv4的权宜之计,在很长一段时间内都寄解决地址短缺问题之希望于大力推广IPv6。从RFC的提出时间就可以看出,很多NAT穿透相关的RFC提出时间都晚IPv6不少。而现在看来,IPv6的推广乃至IPv4的废弃还有相当长的路要走,所以可以预见,NAT还将陪伴我们不少时日。
NAT的概念
NAT(Network address translation)就是网络地址转换技术。按照Wikipedia的解释,它就是一个在路由设备上修改IP首部的地址,从而把一个地址变成另一个地址的技术,简而言之就是针对IP地址的重命名。比如在路由器上设定把来自A网络的IP包中的地址10.0.0.2
重命名成10.1.1.3
,然后转发到B网络,反之亦然。这样对于B网络来说,访问10.1.1.3
就等同于访问A网络中的10.0.0.2
了。更加复杂的NAT技术还可能涉及对TCP、UDP协议中端口号的修改,不过总而言之,NAT就是一个修改数据包头部完成“重命名”的技术。
目前NAT技术最广泛的应用是解决IPv4地址短缺问题。它的思路非常简单,就是重复利用同一个IP地址,并在路由器转发数据包的时候进行“重命名”。比如在常识中,无论在家、学校还是餐厅里网上冲浪,路由器管理页面的地址总是192.168.0.1
、手机的地址也总是192.168
开头。而IP协议中要求每个设备都有不同的IP地址,否则就会混淆不同的设备。之所以我们还能继续网上冲浪,就是因为路由器上使用了NAT技术把这些192.168
开头的内网地址“重命名”成路由器自身的地址,然后转发给互联网。这样,不同的内网就可以使用同一个内网地址(比如学校和家里都有可能有192.168.0.233
这个设备),但也不影响它们接入互联网。而如何完成“重命名”并避免可能发生的冲突就是NAT技术的关键。
NAT的种类(主要是传统NAT)
要进一步理解NAT,首先就是了解NAT的分类。RFC2663把NAT分成了四类:传统NAT、双向NAT、两次NAT、多宿主NAT。由于最常见的就是传统NAT,所以我就偷个懒,只介绍传统NAT了。
传统NAT主要做的就是维护一个内部网络,就像上一节里介绍的那样。它位于内部网络与外部网络(比如互联网)之间,保证内网地址不会被泄露到外网中去。如果再对重命名方式进行细分,传统NAT还可以分成两类:基本NAT(Basic NAT)、NAPT(Network Address Port Translation,网络地址端口转换)。
基本NAT
基本NAT就是只针对IP地址的“重命名”。由于基本NAT并不考虑更高层的协议,所以它只是实现了一个内部IP地址到外部IP地址的一一对应。不妨把已使用的内部IP、NAT设备拥有的外部IP看成In、Ex两个集合。如果内部IP数量更少,left| In right| leq left| Ex right|,那么每个内部地址都能被映射到一个外部地址。如果外部地址数量少于内部IP的话,left| In right| gt left| Ex right|,就不能保证同一时间每个内部设备都能访问外部网络了(可能分配不到外部地址)。
NAPT
不难看出,基本NAT对于IP地址的复用效果相当有限。假设如果NAT设备只有一个外部地址的话,同一时间就只能有一个内部设备可以访问外部了,显然这对我们网上冲浪带来了极大地不便。NAPT对此的解决方法是,考虑高层传输协议TCP、UDP的端口(其实不只是端口,任何传输层标志都行,比如ICMP的ID),以(IP地址, 端口)
为单位进行重命名。这样操作空间就突然变大了65535倍,复用效率直接拉满。所以大多数路由设备都实现了NAPT,日常生活中见到最多的也是NAPT。平常我们说的NAT也基本上就指的是NAPT。
鉴于NAPT的重要性,接下来的文章就着重介绍下不同的NAPT类型和它们的实现原理。
例子:NAPT的基本过程
通过之前对NAT大致分类的介绍,相信你对NAT已经建立了一个大致的印象。接下来我想通过一个例子来详细介绍下NAPT的原理。
常见的NAPT拓扑
图示是一种常见的NAPT拓扑。当内网设备访问访问目标时,它发送包[iAddr:iPort -> dAddr:dPort]
给路由器。路由器的NAPT程序转换内部地址,改写包为[eAddr:ePort -> dAddr:dPort]
,之后转发到外部网络。反之,当访问目标答复内网设备时,它发送包[dAddr:dPort -> eAddr:ePort]
给路由器,路由器接收后通过NAPT程序改写包为[dAddr:dPort -> iAddr:iPort]
然后转发给内网设备。可以看到,发送过程(内部到外部)中NAPT程序改写数据包的源地址,进行源NAT(SNAT)。在接受过程(外部到内部)中改写数据包的目标地址,进行目标NAT(DNAT)。这两个相对应的过程一并组成了NAPT。
在改写包的过程中,最关键的过程就是确定eAddr
与ePort
。这也是不同NAPT实现的主要区别。
RFC3489分类:锥形与对称
由于NAPT用到了传输层协议的标志,因此具体实现无法脱离具体的传输层协议,所以对NAPT的分类也是和传输层协议挂钩的。首先介绍的就是最常见的一种对UDP NAT的分类:RFC3489分类。RFC3489把UDP NAT分成了:全锥体NAT(Full Cone NAT)、地址限制锥体NAT(Restricted Cone)、端口限制锥体NAT(Port Restricted Cone)、对称NAT(Symmetric NAT)。
理解这些不同NAT的关键是理解它们各自的实现原理,这就要介绍一个关键的数据结构——NAT表。NAT表记录了一次NAT需要的全部信息,比如内部地址、外部地址、过期时间等等。在发送、接收时,NAT设备会根据数据包中地址查表或在不存在记录时填表,从而确定改写的eAddr与ePort。
全锥体NAT
- 全锥体NAT的NAT表存储:
(iAddr, iPort[, eAddr], ePort)
。由于一般情况下NAT设备都只有一个外部地址(除非是运营商NAT等等大型网络),所以之后我都会省略eAddr
。之后出现ePort
的地方都可默认为(eAddr, ePort)
。 - 发送时,通过
(iAddr, iPort)
查出(ePort)
。如果查不到相关记录,则分配一个ePort
并记录到NAT表。 - 接收时,通过
(ePort)
查出(iAddr, iPort)
可以发现,全锥体NAT下一个外部IP上的端口ePort
,会被唯一分给一个内网设备的端口iAddr:iPort
。所以同一时间内全锥体NAT最多只能分配65535(实际还要少很多)个内网设备端口。而由于端口分配是一一对应的,所以其他外部地址也能通过ePort
访问到iAddr:iPort
。
全锥体NAT(图源Wikipedia)
地址限制锥体NAT
- 地址限制锥体NAT的NAT表存储:
(iAddr, iPort, ePort, dAddr)
- 发送时,通过
(iAddr, iPort)
查出(ePort)
- 接收时,通过
(ePort, dAddr)
查出(iAddr, iPort)
可以看到,比起全锥体NAT,地址限制锥体NAT多存储了一个dAddr
。这让我们可以在一定程度上复用NAT设备的外部端口ePort
。比如对于这样的NAT表:
(192.168.10.2, 10086, 11451, 6.6.6.6)
(192.168.10.3, 10086, 11451, 8.8.8.8)
不难看出,11451这个ePort
得以被分给两个映射。不过坏处是,不同的外部服务器dAddr'
就没办法通过ePort
访问之前的映射了,只有同一服务器的另一端口dAddr:dPort'
可以访问。
地址限制锥体NAT(图源Wikipedia)
端口限制锥体NAT
- 端口限制锥体NAT的NAT表存储:
(iAddr, iPort, ePort, dAddr, dPort)
- 发送时,通过
(iAddr, iPort)
查出(ePort)
- 接收时,通过
(ePort, dAddr, dPort)
查出(iAddr, iPort)
可以发现,它比地址受限型NAT还要多查找了一个dPort
。因此如今同一个dAddr:dPort
可以连接最多65535个内部端口iAddr:iPort
了。比如对于NAT表:
(192.168.10.2, 10086, 11451, 6.6.6.6, 23333)
(192.168.10.3, 10086, 11451, 6.6.6.6, 10000)
可以看到它进一步提升了ePort
的复用效果。当然代价是同一服务器的其他端口也不能通过ePort
访问之前的映射了。
端口限制锥体NAT(图源Wikipedia)
对称NAT
- 对称NAT的NAT表存储:
(iAddr, iPort, ePort, dAddr, dPort)
- 发送时,通过
(iAddr, iPort, dAddr, dPort)
查出(ePort)
- 接收时,通过
(ePort, dAddr, dPort)
查出(iAddr, iPort)
对称NAT与锥形NAT最大的不同就是对发送的限制。所有锥形NAT都有一个特点,就是一旦建立映射,iAddr:iPort
发送的包一定会被映射到eAddr:ePort
,而限制都只针对接收时的查表。也就是说,锥形NAT都是在分配外部端口给一个内部端口。而对称NAT就是把外部端口分配个一次“连接”了,在发送的时候也关注目标地址。比如NAT表:
(192.168.10.2, 10086, 11451, 6.6.6.6, 23333)
(192.168.10.2, 10086, 11452, 8.8.8.8, 10000)
可以看到,同一个内部端口可以映射到多个外部端口。因此端口分配不再是一对多而是多对多,这也是对称NAT名字的由来←我猜的。
对称NAT(图源Wikipedia)
RFC4787:行为描述
虽然RFC3489对UDP NAT给出了一个分类,但是这个分类显然不太能涵盖所有种类的NAT。比如发送时,为什么不能通过(iAddr, iPort, dAddr)
查表,而是分成了锥型和对称型呢?此外,这个分类还遗漏了其他的NAT实现细节,比如在NAT表中不存在相关记录时,要怎么生成新的ePort
?是优先复用还是随机分配?NAT表项的过期时间到底是多久?基于种种问题,IETF废弃了这种分类方式,并在RFC4787中重新制定了一套对NAT行为的描述,以针对各种不同的NAT实现。
RFC4787中描述了多种NAT行为,这里选取其中相对重要的三个进行介绍:映射行为、过滤行为、端口分配行为。
映射行为
映射行为对应于我们之前介绍中的发送行为,也就是从内部网络发送至外部网络,主要可以分为三种:
- 端点独立映射(Endpoint-Independent Mapping):发送时通过
(iAddr, iPort)
查表 - 地址独立映射(Address-Dependent Mapping):发送时通过
(iAddr, iPort, dAddr)
查表 - 地址与端口独立映射(Address and Port-Dependent Mapping):发送时通过
(iAddr, iPort, dAddr, dPort)
查表
所有锥型NAT都是端点独立映射,而对称NAT则是地址与端口独立映射。当查表使用的信息越多,唯一确定一个映射所需要的条件也就更多,对端点的复用效果就越好,但同时也降低了连通性(不同的连接中无法保持同一个端口映射)。
过滤行为
过滤行为对应于我们之前介绍中的接收行为,也就是从外部网络发送至内部网络,主要也是分为三种:
- 端点独立过滤(Endpoint-Independent Filtering):接收时通过
(ePort)
查表 - 地址独立过滤(Address-Dependent Filtering):接收时通过
(ePort, dAddr)
查表 - 地址与端口独立过滤(Address and Port-Dependent Filtering):接收时通过
(ePort, dAddr, dPort)
查表
可以看到,1-3分别对应了锥型NAT的三种类型,而对称NAT则是地址与端口独立过滤。当查表使用的信息越多,在接收外部网络数据包时的条件也就更加严苛,同样也是提高了端点的复用效果,但是降低了连通性。
通过行为分类,可以清楚的了解到RFC3489分类法的局限(遗漏了很多种行为组合),也能更好的理解四种NAT——全锥体NAT最为宽松、对称NAT最为严格。
端口分配行为
端口分配行为是RFC3489分类法没有提到的,但是也是NAT的一个非常重要的行为。端口分配发生在第一次映射行为时,用来产生映射的目标端口ePort
。
- 端口保留(port preservation):NAT将尽可能保证
ePort == iPort
。比如在映射时替换掉之前拿到ePort
的内部端口,或者维护一个地址池,分配不同外部地址的ePort
端口eAddr:ePort
。 - 端口重载(port overloading):即使外部端口冲突也依旧进行端口保留。端口重载会影响相关程序的正确性,因此RFC4787要求NAT程序不可以具备端口重载行为。
- 非端口保留(no port preservation):NAT不刻意保证
ePort == iPort
。
后记
本篇文章介绍了一些常见的NAT类型与实现原理。下一篇文章将结合本文提到的分类介绍UDP NAT穿透技术。
参考文献
- RFC2663 – IP Network Address Translator (NAT) Terminology and Considerations(https://datatracker.ietf.org/doc/html/rfc2663)
- RFC3489 – STUN – Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs)(https://datatracker.ietf.org/doc/html/rfc3489)
- RFC4787 – Network Address Translation (NAT) Behavioral Requirements for Unicast UDP(https://datatracker.ietf.org/doc/html/rfc4787)
- Network address translation(https://en.wikipedia.org/wiki/Network_address_translation)