<audio id="audio" title="10 | WebRTC NAT穿越原理" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a7/d3/a7b46a59c17d3f2366f3b9a7af0a78d3.mp3"></audio>

在 WebRTC 中， NAT 穿越是非常重要的一部分内容，也是比较有深度、比较难以理解的一部分知识。当然，等你学完本文，并完全理解了这部分知识后，你也会特别有成就感！

在我们真实的网络环境中，NAT 随处可见，而它的出现主要是出于两个目的。**第一个是解决 IPv4 地址不够用的问题**。在 IPv6 短期内无法替换 IPv4 的情况下，如何能解决 IP 地址不够的问题呢？人们想到的办法是，让多台主机共用一个公网 IP 地址，然后在内部使用内网 IP 进行通信，这种方式大大减缓了 IPv4 地址不够用的问题。**第二个是解决安全问题**，也就是主机隐藏在内网，外面有NAT挡着，这样的话黑客就很难获取到该主机在公网的IP地址和端口，从而达到防护的作用。

不过凡事有利也有弊，NAT 的引入确实带来了好处，但同时也带来了坏处。如果没有NAT，那么每台主机都可以有一个自己的公网IP地址，这样每台主机之间都可以相互连接。可以想象一下，如果是那种情况的话，互联网是不是会更加繁荣？因为有了公网IP地址后，大大降低了端与端之间网络连接的复杂度，我们也不用再费这么大力气在这里讲 NAT 穿越的原理了。

如果从哲学的角度来讲，“世上的麻烦都是自己找的”，这句话还是蛮有道理的。

## 在WebRTC处理过程中的位置

下面我们来看一下本文在 WebRTC 处理过程中所处的位置吧。通过下面这张图，你可以清楚地了解到本文我们主要讲解的是传输相关的内容。

<img src="https://static001.geekbang.org/resource/image/38/b9/3885178d5747a92ef95819fd0fe618b9.png" alt="">

## NAT 的种类

随着人们对 NAT 使用的深入，NAT 的设置也越来越复杂。尤其是各种安全的需要，对 NAT 的复杂性起到了推波助澜的作用。

经过大量研究，现在NAT基本上可以总结成 4 种类型：**完全锥型、IP限制锥型、端口限制锥型和对称型**。

下面我们就对这 4 种类型的 NAT 做下详细介绍。

### 1. 完全锥型 NAT

<img src="https://static001.geekbang.org/resource/image/88/af/8836f91edfcc9a2420e3fd11098f95af.png" alt="">

完全锥型 NAT 的特点是，当 host 主机通过 NAT 访问外网的 B 主机时，就会在 NAT 上打个“洞”，所有知道这个“洞”的主机都可以通过它与内网主机上的侦听程序通信。

实际上，这里**所谓的“打洞”就是在 NAT 上建立一个内外网的映射表**。你可以将该映射表简单地认为是一个 4 元组，即：

```
{
	内网IP，
	内网端口，
	映射的外网IP，
	映射的外网端口
}

```

在 NAT 上有了这张映射表，所有发向这个“洞”的数据都会被 NAT 中转到内网的 host 主机。而在 host 主机上侦听其内网端口的应用程序就可以收到所有的数据了，是不是很神奇？

还是以上面那张图为例，如果 host 主机与 B 主机“打洞”成功，且 A 与 C 从 B主机那里获得了 host 主机的外网IP及端口，那么A与C就可以向该IP和端口发数据，而 host 主机上侦听对应端口的应用程序就能收到它们发送的数据。

如果你在网上查找NAT穿越的相关资料，一定会发现大多数打洞都是使用的 UDP 协议。之所以会这样，是因为**UDP是无连接协议**，它没有连接状态的判断，也就是说只要你发送数据给它，它就能收到。而 TCP 协议就做不到这一点，它必须建立连接后，才能收发数据，因此大多数人都选用 UDP 作为打洞协议。

### 2. IP限制锥型 NAT

<img src="https://static001.geekbang.org/resource/image/63/8a/6358816cf33831f22338cb26016d028a.png" alt="">

IP限制锥型要比完全锥型NAT严格得多，它主要的特点是，host 主机在 NAT 上“打洞”后，NAT 会对穿越洞口的 IP 地址做限制。只有登记的 IP 地址才可以通过，也就是说，**只有 host 主机访问过的外网主机才能穿越 NAT**。

而其他主机即使知道“洞”的位置，也不能与 host 主机通信，因为在通过 NAT 时，NAT 会检查IP地址，如果发现发来数据的 IP 地址没有登记，则直接将该数据包丢弃。

所以，IP 限制锥型 NAT 的映射表是一个 5 元组，即：

```
{
	内网IP，
	内网端口，
	映射的外网IP，
	映射的外网端口，
	被访问主机的IP
}

```

还是以上图为例，host 主机访问 B 主机，那么只有 B 主机发送的数据才能穿越NAT，其他主机 A 和 C 即使从 B 主机那里获得了 host 主机的外网IP和端口，也无法穿越 NAT。因为 NAT 会对通过的每个包做检测，当检查发现发送者的IP地址与映射表中的“被访问主机的IP”不一致，则直接将该数据包丢弃。

需要注意的是，**IP限制型NAT只限制IP地址**，如果是同一主机的不同端口穿越NAT是没有任何问题的。

### 3. 端口限制锥型

<img src="https://static001.geekbang.org/resource/image/d6/0b/d6490bff17fdad51271266cef074920b.png" alt="">

端口限制锥型比IP限制锥型NAT更加严格，它主要的特点是，不光在 NAT 上对打洞的 IP 地址做了限制，而且还对具体的端口做了限制。因此，端口限制型NAT的映射表是一个 6 元组，其格式如下：

```
{
	内网IP，
	内网端口，
	映射的外网IP，
	映射的外网端口，
	被访问主机的IP,
	被访问主机的端口
}

```

在该 6 元组中，不光包括了 host 主机内外网的映射关系，还包括了**要访问的主机的 IP 地址及提供服务的应用程序的端口地址**。

如上图所示，host 主机访问 B 主机的 p1 端口时，只有 B 主机的 p1 端口发送的消息才能穿越 NAT 与 host 主机通信。而其他主机，甚至 B 主机的 p2 端口都无法穿越 NAT。

从上面的情况你应该看出来了，从完全锥型NAT到端口限制型NAT，一级比一级严格。但其实端口型NAT还不是最严格的，最严格的是接下来要讲解的对称型NAT。

### 4. 对称型NAT

<img src="https://static001.geekbang.org/resource/image/a8/7c/a80a2b1c98b8becce0c99e979fa3ba7c.png" alt="">

**对称型NAT是所有 NAT 类型中最严格的一种类型**。通过上图你可以看到，host主机访问 B 时它在 NAT 上打了一个“洞”，而这个“洞”只有 B 主机上提供服务的端口发送的数据才能穿越，这一点与端口限制型NAT是一致的。

但它与端口限制型NAT最大的不同在于，如果 host 主机访问 A 时，它会在 NAT 上重新开一个“洞”，而不会使用之前访问B时打开的“洞”。也就是说对称型 NAT 对每个连接都使用不同的端口，甚至更换IP地址，而端口限制型NAT的多个连接则使用同一个端口，这对称型NAT与端口限制型NAT最大的不同。上面的描述有点抽象，你要好好理解一下。

它的这种特性为 NAT 穿越造成了很多麻烦，尤其是对称型NAT碰到对称型NAT，或对称型NAT遇到端口限制型NAT时，基本上双方是无法穿越成功的。

以上就是NAT的 4 种类型，通过对这 4 种NAT类型的了解，你就很容易理解 NAT 该如何穿越了。

## NAT 类型检测

通过上面的介绍，相信你会很容易判断出NAT是哪种类型，但对于每一台主机来说，它怎么知道自己是哪种NAT类型呢？

<img src="https://static001.geekbang.org/resource/image/b1/c3/b112ac2cd7e557d86dd5669e2858f6c3.png" alt="">

上面这张图清楚地表达了主机进行 NAT 类型检测的流程。其中蓝框是几个重要的检测点，通过这几个检测点你就可以很容易地检测出上面介绍的4种不同类型的 NAT了。

接下来，我们就对上面这张图做下详细的解释。**这里需要注意的是，每台服务器都是双网卡的，而每个网卡都有一个自己的公网IP地址**。

### 第一步，判断是否有NAT防护

1. 主机向服务器#1的某个IP和端口发送一个请求，服务器#1收到请求后，会通过同样的IP 和端口返回一个响应消息。
1. 如果主机收不到服务器#1返回的消息，则说明用户的网络**限制了UDP协议，直接退出**。
1. 如果能收到包，则判断返回的主机的外网IP地址是否与主机自身的IP地址一样。如果一样，说明主机就是一台**拥有公网地址的主机**；如果不一样，就跳到下面的步骤6。
1. 如果主机拥有公网IP，则还需要进一步判断其防火墙类型。所以它会再向服务器#1发一次请求，此时，服务器#1从另外一个网卡的IP和不同端口返回响应消息。
1. 如果主机能收到，说明它是一台没有防护的公网主机；如果收不到，则说明有**对称型的防火墙**保护着它。
1. 继续分析第3步，如果返回的外网IP地址与主机自身IP不一致，说明主机是处于 NAT 的防护之下，此时就需要对主机的 NAT防护类型做进一步探测。

### 第二步，探测NAT环境

1. 在 NAT 环境下，主机向服务器#1发请求，服务器#1通过另一个网卡的IP和不同端口给主机返回响应消息。
1. 如果此时主机可以收到响应消息，说明它是在一个**完全锥型NAT**之下。如果收不到消息还需要再做进一步判断。
1. 如果主机收不到消息，它向服务器#2（也就是第二台服务器）发请求，服务器#2使用收到请求的IP地址和端口向主机返回消息。
1. 主机收到消息后，判断从服务器#2获取的外网IP和端口与之前从服务器#1获取的外网IP和端口是否一致，如果不一致说明该主机是在**对称型NAT**之下。
1. 如果IP地址一样，则需要再次发送请求。此时主机向服务器#1再次发送请求，服务器#1使用同样的IP和不同的端口返回响应消息。
1. 此时，如果主机可以收到响应消息说明是**IP 限制型 NAT**，否则就为**端口限制型NAT**。

至此，主机所在的 NAT 类型就被准确地判断出来了。有了主机的 NAT 类型你就很容易判断两个主机之间到底能不能成功地进行 NAT 穿越了。

再后面的事件就变得比较容易了，当你知道了 NAT 类型后，如何进行NAT 穿越也就水到渠成了呢！

## 小结

通过上面的介绍，我想你应该已经对 NAT 的 4种类型了然于胸了。理解了NAT的4种类型，同时又清楚了主机如何去判断自己的 NAT 类型之后，你应该自己就可以想清楚不同 NAT 类型之间是如何进行 NAT 穿越的了。

了解了NAT 穿越的理论知识，你就很容易理解 WebRTC 底层是如何进行音视频数据传输了吧？WebRTC中媒体协商完成之后，就会对Candidate pair进行连通性检测，其中非常重要的一项工作就是进行 NAT 穿越。

它首先通过上面描述的方法进行 NAT 类型检测，当检测到双方理论上是可以通过 NAT 穿越时，就开始真正的 NAT 穿越工作，如果最终真的穿越成功了，通信双方就通过该连接将音视频数据源源不断地发送给对方。最终，你就可以看到音视频了。

## 思考时间

为什么对称型 NAT 与对称型 NAT 之间以及对称型 NAT 与端口限制型NAT 之间无法打洞成功呢？如果打洞失败，你又该如何让通信双方实现互联呢？

欢迎在留言区与我分享你的想法，也欢迎你在留言区记录你的思考过程。感谢阅读，如果你觉得这篇文章对你有帮助的话，也欢迎把它分享给更多的朋友。


