漫谈NAT(一):各种NAT类型

好久没有写也没有填系列文章了,正好之前文章提到了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。

在改写包的过程中,最关键的过程就是确定eAddrePort。这也是不同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表:

  1. (192.168.10.2, 10086, 11451, 6.6.6.6)
  2. (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表:

  1. (192.168.10.2, 10086, 11451, 6.6.6.6, 23333)
  2. (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表:

  1. (192.168.10.2, 10086, 11451, 6.6.6.6, 23333)
  2. (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行为,这里选取其中相对重要的三个进行介绍:映射行为、过滤行为、端口分配行为。

映射行为

映射行为对应于我们之前介绍中的发送行为,也就是从内部网络发送至外部网络,主要可以分为三种:

  1. 端点独立映射(Endpoint-Independent Mapping):发送时通过(iAddr, iPort)查表
  2. 地址依赖映射(Address-Dependent Mapping):发送时通过(iAddr, iPort, dAddr)查表
  3. 地址与端口依赖映射(Address and Port-Dependent Mapping):发送时通过(iAddr, iPort, dAddr, dPort)查表

所有锥型NAT都是端点独立映射,而对称NAT则是地址与端口依赖映射。当查表使用的信息越多,唯一确定一个映射所需要的条件也就更多,对端点的复用效果就越好,但同时也降低了连通性(不同的连接中无法保持同一个端口映射)。

过滤行为

过滤行为对应于我们之前介绍中的接收行为,也就是从外部网络发送至内部网络,主要也是分为三种:

  1. 端点独立过滤(Endpoint-Independent Filtering):接收时通过(ePort)查表
  2. 地址依赖过滤(Address-Dependent Filtering):接收时通过(ePort, dAddr)查表
  3. 地址与端口依赖过滤(Address and Port-Dependent Filtering):接收时通过(ePort, dAddr, dPort)查表

可以看到,1-3分别对应了锥型NAT的三种类型,而对称NAT则是地址与端口依赖过滤。当查表使用的信息越多,在接收外部网络数据包时的条件也就更加严苛,同样也是提高了端点的复用效果,但是降低了连通性。

通过行为分类,可以清楚的了解到RFC3489分类法的局限(遗漏了很多种行为组合),也能更好的理解四种NAT——全锥体NAT最为宽松、对称NAT最为严格。

端口分配行为

端口分配行为是RFC3489分类法没有提到的,但是也是NAT的一个非常重要的行为。端口分配发生在第一次映射行为时,用来产生映射的目标端口ePort

  1. 端口保留(port preservation):NAT将尽可能保证ePort == iPort。比如在映射时替换掉之前拿到ePort的内部端口,或者维护一个地址池,分配不同外部地址的ePort端口eAddr:ePort
  2. 端口重载(port overloading):即使外部端口冲突也依旧进行端口保留。端口重载会影响相关程序的正确性,因此RFC4787要求NAT程序不可以具备端口重载行为。
  3. 非端口保留(no port preservation):NAT不刻意保证ePort == iPort

后记

本篇文章介绍了一些常见的NAT类型与实现原理。下一篇文章将结合本文提到的分类介绍UDP NAT穿透技术。

勘误

  • 2023/10/27:在翻译 RFC4787 的 “Address-Dependent” 和 “Address and Port-Dependent” 时误将 “Dependent” 翻译为 “独立”,实际应该翻译为 “地址依赖” 和 “地址与端口依赖”。感谢 @NaCl 的指正!

参考文献

  1. RFC2663 – IP Network Address Translator (NAT) Terminology and Considerations(https://datatracker.ietf.org/doc/html/rfc2663
  2. RFC3489 – STUN – Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs)(https://datatracker.ietf.org/doc/html/rfc3489
  3. RFC4787 – Network Address Translation (NAT) Behavioral Requirements for Unicast UDP(https://datatracker.ietf.org/doc/html/rfc4787
  4. Network address translation(https://en.wikipedia.org/wiki/Network_address_translation
分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

评论

  1. joe 2023.09.06 9:47上午

    等大佬的第二篇NAT穿透,

在此评论中不能使用 HTML 标签。