Ch3nyang's blog

home

home

person

about

hive

project

collections_bookmark

mindclip

rss_feed

rss

NAT穿透——原理与实践

calendar_month 2023-12
archive 网络
tag nat

今年暑假的时候,学校为了护网,禁掉了所有的内网穿透工具。尽管这种拔网线的方式会给远程科研带来巨大的麻烦,但其无疑是极为有效的安全措施。那究竟什么是内网穿透?它又有哪些安全风险呢?

[TOC]

NAT 是什么

我们先简单回顾一下什么是 NAT。

随着可用 IPv4 的数量不断减少,人们开始探索如何压榨出单个 IPv4 地址的最大价值,让一个地址可以被多台电脑使用,这就是 NAT (Network address translation) 技术。NAT 技术的原理是,让一些电脑组成一个私有子网,这些电脑共享同一个公网 IP 地址,但是每台电脑都有自己的内网 IP 地址。

举个例子,我在并夕夕上下单了一个人形玩偶,收货地址是宿舍的地址。但快递员在送货时,只会将人形玩偶送到学校快递站。快递站再根据我的具体地址,把人形玩偶交到我手中。同样的,如果我要寄一封情书给南大的女同学,我也应该先把情书交给快递站,然后由快递站统一寄出。

在网络上,公网中任意主机的 IP 地址就是并夕夕商家或者南大女同学的地址,本地子网内的 IP 地址就是我的地址,NAT 路由就是这个快递站。

我们用一个具体的例子来说明这一过程。假设 NAT 路由有一个公网地址 5.5.5.5,本机分到的内网地址为 1.0.0.2,需要访问的服务器地址为 2.2.2.2

nat1

发送报文的过程为:

  • 内网主机把数据包的源 IP 地址设为 1.0.0.2,目的 IP 地址设为 2.2.2.2,并发送出去
  • 数据包到达 NAT 路由后,NAT 路由把源 IP 地址改为 5.5.5.5:69,并记住 69 端口对应内网的 7.7.7.7
  • NAT 路由将报文发给公网的 2.2.2.2

接收报文的过程为:

  • 公网服务器把数据包的源 IP 地址设为 2.2.2.2,目的 IP 地址设为 5.5.5.5:69,并发送出去
  • 数据包到达 NAT 路由后,NAT 路由查表发现 69 端口对应着内网的 1.0.0.2
  • NAT 路由将报文发给内网的 1.0.0.2

如果你想进一步了解 NAT 的具体实践,我推荐你阅读这篇文章:https://www.karlrupp.net/en/computer/nat_tutorial

NAT 穿透的四种写法

在 NAT 中,内网主机可以作为客户端向公网服务器发送报文,并接收服务器的回复。但如果内网主机要作为服务器,则面临两个严峻的问题:

  • NAT 映射:在内网主机不主动发送报文的情况下,公网用户并不知道 NAT 给内网主机分配的端口号。

    nat2

  • 有状态防火墙:NAT 往往是单向的。只有内网主动发送报文后,NAT 才会允许对应的回应报文发往内网。

    nat3

请注意,这两个问题是整个内网穿透的核心。我们接下来的所有努力都是在解决这两个问题。

假如我们在内网中有一台服务器,内网地址为 1.0.0.2,在 80 端口上提供 HTTP 服务。公网中有一个地址为 2.2.2.2 的用户想要访问该服务,那么有几种选择:

  1. 端口映射 (Port Mapping):在 NAT 路由支持的情况下,我们可以让 NAT 路由直接映射需要的端口。我们可以让 NAT 路由开放 80 端口,并将这个端口对应到内网的 1.0.0.2:80。这样,用户只需要使用 http://5.5.5.5/ 正常访问 NAT 路由即可。通常,这是通过 UPnP (Universal Plug’n’Play)、PMP (Port Mapping Protocol)、PCP (Port Control Protocol) 等协议实现的。如果 NAT 路由开启了相关协议,内网用户就只需要让 NAT 路由把自己的某个端口映射到公网的某个端口即可。当然,出于安全原因,很多时候 NAT 路由并不会开启相关功能。

    nat4

  2. 中继服务器 (Relay Server):如果我们钱比较多,那么可以在公网上在搞一台轻量级的服务器用作流量转发,并向用户开放 80 端口。内网服务器首先要和中继服务器建立稳定连接,然后告诉用户中继服务器的地址。用户访问 http://3.3.3.3/ 时,中继服务器将流量转发给内网服务器;内网服务器回应时,也通过中继服务器转发。这样的模式被称为 TURN (Traversal Using Relays around NAT)

    nat5

  3. 反向连接 (Reverse Connection):如果我们提前知道谁要和我们通信,那就可以先发制人,主动连接。此时,相当于我们自身成为了客户端。

    nat6

  4. STUN (Session Traversal Utilities for NAT):如果公网上的用户也是自己人,那就可以利用 STUN 协议预先获得 NAT 分配的端口号,把端口号告诉公网用户,然后直接连接。

    nat7

这些方法可以单独使用,也可以结合使用。在这四种方法中,端口映射和反向连接通常都是条件不允许的;中继服务器往往可行,但会带来额外的开销;只有 STUN 几乎是万能的。我们接下来进一步探究其工作原理。

坚不可摧的防火墙

前面提到,当内网主机发出的报文经过 NAT 时,NAT会将数据包的源地址和端口全部修改,服务器也只能看到修改过后的地址和端口号。那么,如果这台服务器受我们控制,不就可以获得端口号了吗?

因此,我们只需要一台 STUN 服务器,并让其返回收到的地址和端口号。

nat8

现在,我们看起来已经可以知道端口号了。但问题还没有解决。

有些 NAT 路由会针对每一条链接分配不同的端口。比如,在和外网主机 A 交互时走的是 69 端口,但和主机 B 交互时走的是 96 端口。如果主机 B 依然把报文发送到 69 端口,那么就会被防火墙拦截。这种更高级、也更安全的防火墙被称为端点相关有状态防火墙 (Endpoint-Dependent firewall)。

nat9

这时候,我们就无法提前使用 STUN 服务器获取到开放的端口了。但这也不是没有解决办法。比如,我们可以把 65535 个端口全部扫描一遍,直到找到正确的那个!这样的方法理论上是可行的,但会消耗大量时间,而且很可能会触发 NAT 侧的入侵检测。

减少扫描次数的方法也并非没有。我们可以基于生日悖论,在 NAT 多打几个洞,这样扫描到可用的端口的概率会大幅度提升。

而当扫描完全不可行时,我们就不得不使用中继服务器了。中继模式在绝大多数情况下可以作为一个后备选项——只是其性能太差了一些。

好多 NAT

我们前文都在讨论单个 NAT 的情况,接下来我们看点更复杂的。

某些场景下,通信双方都在 NAT 的子网中,比如在家连接办公室网络工作。这时候该如何发送信息呢?

nat10

前文讲过,NAT 防火墙的特性是不发送就不肯接收。如果我们在家庭子网向办公网发送一条消息,那么肯定会被办公网的 NAT 挡在外面。但是,如果办公网几乎同时也发了一条消息给家庭网,由于家庭网已经发过消息,所以办公网发来的消息是可以进入家庭网的!这时,由于办公网也发过消息了,所以家庭网后续的消息便也可以进入办公网。

发现问题没有?问题就在于这个“同时”。要想接收消息,就必须发送消息;要想发送消息,又必须接收到消息。事情陷入了死循环。必须有一种方法,能帮助家庭网和办公网完成“同时发送消息”这一目标。

可行的方案只能是持续不断的发包,干等家庭网的连接——这会消耗大量的资源。另一种可行的方案是使用第三方服务器(比如中继服务器)帮助完成协商,这样的方法谈不上很好,但总比不停发包强。

如果两个 NAT 都是端点相关有状态防火墙呢?这时候就麻烦得多了,因为两道防火墙均需要扫描,扫描需要的尝试次数变成了原来的平方。

那如果两个 NAT 路由的防火墙朝向一致——或者说,NAT 里套了一个 NAT 呢?

nat11

其实,这对这个信息传输是没有任何影响的,刚刚的方法依然适用。此时,我们操纵的是最靠近内网主机的那台 NAT 路由。但是缺点也是有的,我们在这中情况下,无法操纵到别的 NAT 路由——甚至无法得知它们的存在。特别当外层 NAT 路由是运营商路由 (CG-NAT) 时,我们根本无法控制到,更不用说做端口映射了。因此,嵌套式的 NAT 通常是不被推荐的。

但是,由于 IPv4 极度紧张,CG-NAT 其实较为常见。这时候,便引出了我们看起来最复杂的一个问题:NAT 内的多个 NAT 如何互相穿透?

nat12

如果其中一个 NAT 可以进行 NAT 映射,那么很好,问题轻松解决。如果不能,那就要用到外层 NAT 路由的 hairpinning 模式。该模式会把内网的流量通过 NAT 路由的 internel 端口转发到内网。此时,和之前提到的普通的双 NAT 穿透没什么不同。

但是,事情远远没有这么简单。很多 NAT 路由压根不支持 hairpinning 模式,当它们遇到内网到内网的包时,会选择直接扔掉!怎么办?那就不得不使用中继服务器了。

IPv6

上面各种难题、各种解决方案归根到底还是因为 IPv4 太少了。但现在 IPv6 依然没有普及开来,对于 IPv6-only 的子网,又该如何穿透给 IPv4 公网呢?

nat13

NAT64 就是做这个工作的。它可以将 IPv4 和 IPv6 地址互相映射,使得对于内网主机来讲,它看到的全是 IPv6。而如果内网主机需要访问 IPv4 资源,DNS64 会给出一个虚构的 IPv6 地址,并将真正的 IPv4 地址和虚构的 IPv6 地址的对应关系记录下来。内网主机访问该虚构的 IPv6 时,DNS64 会首先介入,将地址转换为真正的 IPv4 地址。

换句话说,对于 IPv6-only 子网内的设备,

  • 连接公网 IPv4 设备时
    • NAT64 负责修改源地址,用于欺骗公网 IPv4 设备
    • DNS 负责修改目的地址,用于欺骗内网 IPv6 设备
  • 连接公网 IPv6 设备时,可以直接连接,不需要穿透。

上面这一套技术被称为 CLAT (Customer-side transLATor),移动端设备往往会自带。这是因为不少运营商网络是 IPv6-only 的,移动设备必须实现无感穿透。

终极方案

以上讲了这么多情况的解决方案,有没有一劳永逸、把所有方案结合到一起的办法呢?这就是 ICE (Interactive Connectivity Establishment) 协议,其会去寻找最好的信道。简单来讲,ICE 协议下的内网穿透分为以下几个步骤:

  1. 以中继模式建立连接
  2. 确定自己的信息
    • 内网 IPv6 地址
    • 内网 IPv4 地址
    • 公网 IPv4 地址(通过 STUN 服务器获取 / 通过端口映射获取 / 其它方式)
  3. 通过中继模式提供的旁路信道(或第三方服务器)和 peer 交换自己的信息
  4. 不断探测对方给的所有地址,并找出效果最好的那个(LAN > WAN > WAN+NAT)
  5. 在最好的信道上通信,并不断发送 PING/PONG 保活
  6. 如果没有找到可用的地址,则在中继模式下进行通信

最后,我们再简单探讨一下内网穿透的安全性。从其原理可以发现,内网穿透本身并没有什么安全问题,其安全性基本取决于上层协议的安全性。内网穿透最可能的安全问题是中继服务器被劫持,但如果使用了 P2P 加密,则无需考虑这个问题。

主要参考资料

Comments

Share This Post