10 | WebRTC NAT穿越原理

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

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

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

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

在WebRTC处理过程中的位置

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

WebRTC 处理过程图

NAT 的种类

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

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

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

1. 完全锥型 NAT

完全锥型 NAT 图

完全锥型 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

IP限制锥型 NAT 图

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. 端口限制锥型

端口限制锥型 NAT 图

端口限制锥型比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

对称型 NAT 图

对称型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类型呢?

NAT类型检测图

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

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

第一步,判断是否有NAT防护

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

第二步,探测NAT环境

  1. 在 NAT 环境下,主机向服务器#1发请求,服务器#1通过另一个网卡的IP和不同端口给主机返回响应消息。
  2. 如果此时主机可以收到响应消息,说明它是在一个完全锥型NAT之下。如果收不到消息还需要再做进一步判断。
  3. 如果主机收不到消息,它向服务器#2(也就是第二台服务器)发请求,服务器#2使用收到请求的IP地址和端口向主机返回消息。
  4. 主机收到消息后,判断从服务器#2获取的外网IP和端口与之前从服务器#1获取的外网IP和端口是否一致,如果不一致说明该主机是在对称型NAT之下。
  5. 如果IP地址一样,则需要再次发送请求。此时主机向服务器#1再次发送请求,服务器#1使用同样的IP和不同的端口返回响应消息。
  6. 此时,如果主机可以收到响应消息说明是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 之间无法打洞成功呢?如果打洞失败,你又该如何让通信双方实现互联呢?

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

精选留言

  • 花果山の酸梅汤

    2019-08-06 16:30:10

    类型(A-B) 建立状况
    完全锥型-完全锥型 A通过server获得B的IP:port开始通信
    完全锥型-IP限制型 B通过server获得A的IP:port开始通信
    完全锥型-port限制型 B通过server获得A的IP:port开始通信
    完全锥型-对称型 B通过server获得A的IP:port开始通信
    IP限制型-IP限制型 A通过server获得B的IP:port,A向B发送UDP包,数据通过自己的NAT时会为B建立NAT映射条目;B通过server获得A的IP:port,发送UDP包为A建立NAT映射条目,二者之后就可以开始通信。
    IP限制型-port限制型 A通过server获得B的IP:port,A向B发送UDP包,数据通过自己的NAT时会为B建立NAT映射条目;B通过server获得A的IP:port,发送UDP包为A建立NAT映射条目,二者之后就可以开始通信。
    IP限制型-对称型 A通过server获得B的IP:port1,A向B发送UDP包,数据通过自己的NAT时会为B建立NAT映射条目;B通过server获得A的IP:port,发送UDP包为A建立NAT映射条目,A收到B的UDP包,获得B新的IP:port2;A向B的新地址IP:port2发送数据,可以开始通信。
    port限制型-对称型 A通过server获得B的IP:port1,A向B发送UDP包,数据通过自己的NAT时会为B建立NAT映射条目;B通过server获得A的IP:port,发送UDP包为A建立NAT映射条目,但由于B更换了新端口port2,A刚建立的映射无法使用,A故此无法收到B为A通信使用的新端口,无法建立NAT映射,两者无法互通。
    对称型-对称型 同上,对称型开启新端口对方无法获悉,无法建立链接。
    无法P2P就需要用TURN服务器转发数据了…
    作者回复

    非常赞!描述的非常清楚!

    2019-08-07 22:23:44

  • 李新

    2019-11-03 20:43:56

    请教一下,除了生日攻击,还有其他方法提高P2P的打洞成功率吗?
    作者回复

    在中国,P2P 成功率很低,尤其是移动互联网。

    2019-11-08 13:43:36

  • Benjamin

    2020-02-11 23:03:02

    https://static001.geekbang.org/resource/image/b1/c3/b112ac2cd7e557d86dd5669e2858f6c3.png

    一图胜千言,这图一看到总算搞明白了。
  • 杨凯

    2020-05-14 12:04:04

    老师您好,请问下:客户端C与服务端S进行socket通讯,TCP握手连接成功后,通过抓包分析端口是25568,然而,S端收到的第一个数据包的端口变成25567,导致C端报错connection reset,能说下什么原因吗?
    作者回复

    socket的端口实现上代表的是一个应用(一个应用程序)。本来是 25568 这个应用在提供服务,给果有一个包来了说让 25567 对应的应用来提供服务,当你的操作系统收到这个包时,就将数据传给 25567这个应用。但实际上你的系统上就没有 25567 这个应用,那系统怎么办呢?它的作法很简单,就是返回一个reset,告诉发送端这个连接是不存在的,请重新进行三次握手。

    2020-05-24 12:19:47

  • Happy~张🤔

    2019-11-10 09:54:40

    请问一下,stun服务器作为net打洞,那打洞成功后,后面传输还是会利用stun进行转发媒体信息进行通信吗?如果不是外网信息怎么转到内网的?
    作者回复

    WebRTC内部首先会判断对方是否与自己在同一个网段,也就是说先判断通信双方是否在内网。如果是在内容就直接进行通信了。如果不在内网,会偿试 P2P 穿越,一旦穿越成功,双方就直接通信了,后面的传输不会用到 STUN服务器。

    2019-11-15 09:21:30

  • 李新

    2019-11-03 20:47:36

    请教一下,NAT类型的探测这么复杂,而且需要的时间也久,webrtc在P2P的时候是不是不需要探测NAT类型,而只是对着ICE Candidate相互发包就可以了?
    作者回复

    是进行 NAT 探测的,逻辑复杂,但执行速度很快!

    2019-11-08 13:42:02

  • Jason

    2019-08-06 16:22:57

    尝试回答思考题,不知道理解的对不对,还请老师指正:
    对称型NAT之间打洞失败:对称型NAT是内网主机通过STUN服务返回的外网IP:port,只是针对STUN服务的,而跟其他外网设备的链路是直接不能相通的。
    端口受限型NAT与对称型NAT之间打洞失败:端口受限型NAT是指内网主机通过STUN服务返回的外网IP:port,要求外网设备的IP和端口是不能变的,否则链路不通。但对称型NAT针对内网主机的不同端口而映射出来的外网地址,明显不能保证IP和端口不变。
    作者回复

    是可以这么理解的。在本文中我讲的一个重点是映射表,从映射表的角度去思考这各种类型的NAT理解起来会更清晰哈!

    2019-08-07 22:28:40

  • ifelse

    2025-07-15 12:33:06

    学习打卡
  • wengjia

    2023-07-24 19:10:39

    老师我有一个问题请教我在服务器上配了一个coturn的stun服务器。客户端html,用本地index.html打开的方式peerconnection配置了coturn的地址可以连接成功。如果我用htttp://ip:port/index.html方式打开peerconnection连不上。老师我该如何排查这个问题,有点没头绪
  • 信信

    2023-01-11 17:12:59

    这章是目前为止花的时间最久的
  • 在路上

    2022-08-05 11:41:20

    问题提高「每台服务器都是双网卡的,而每个网卡都有一个自己的公网 IP 地址」 ,公网IP地址可以是一个NAT网关的IP吗?
  • Vincent.永生

    2022-04-29 21:35:19

    老师请教下,按总结,不做nat探测,每个打洞都按端口限制型-端口限制型来穿越就行了?还省了双网卡的服务器
  • piboye

    2020-12-23 23:38:28

    对称型可以通过生日悖论来突破吧,只是要尝试的次数太多,时间延迟较大
    作者回复

    是的

    2021-03-30 11:11:55

  • SherwinFeng

    2019-11-23 20:58:51

    打洞失败自然就要用relay服务器啦
  • 斜月浮云

    2019-09-25 14:42:32

    请问,如果对称NAT的B每次改变IP和端口,那么IP限制NAT主机A与对称NAT的B怎么建立连接呢?
    作者回复

    A 先给 B发消息。此时,A 的NAT表里会增加这样一条记录<srcIP, srcPort, dstIP>,然后 B再给A发消息。B的消息过来之后,A的 NAT 发现来的这个主机的IP地址在地址表中,所以放行。

    2019-09-25 16:48:26

  • momo

    2019-08-28 16:14:05

    探测 NAT 环境的时候为什么要向服务器#2发请求, 不能向#1的另外一张网卡发请求吗?
    作者回复

    因为 #1 的第二块网卡后面还要用到,如果同一个网卡有两种用途就无法进行区分了。

    2019-08-31 23:26:06

  • 恋着歌

    2019-08-12 16:11:44

    看了 花果山の酸梅汤 同学的回答。打洞不成功是因为建立了两个连接,但是为什么不用发起者建立的连接来传回数据呢?
    作者回复

    这与你们公司用的网关/路由设备的实现有关

    2019-08-12 20:11:26

  • 刘丹

    2019-08-08 09:28:50

    是否IP限制型NAT、端口限制型NAT都有一个隐含条件:WebRtc客户端与防火墙之间只有1个NAT映射?
    作者回复

    对,只有一个映射的情况是最简单的,如果有多个出口IP的话的,情况会更复杂。

    2019-08-08 23:46:14

  • 2019-08-07 14:32:08

    老师,请教下如何编译resiprocate成ios静态库
    作者回复

    不太清楚,这个好像与 webrtc 没啥关系!

    2019-08-07 22:33:06

  • 彭刚

    2019-08-06 19:59:28

    老师,前端这方面实在有点差,前端方面代码就看你的过一遍可以吗。大概能看懂每一步是干嘛的,后端部分认真写
    作者回复

    这块一定要好好看,如果这块没有学好的话,后面很难深入学习 webrtc

    2019-08-07 22:12:31