为好奇的人提供的 WebRTC|03 连接

本系列文章跳转目录如下:

  1. 是什么,为什么,如何使用
  2. 信令(交换信令消息后,WebRTC Agent才可以直接相互通信。)
  3. 连接( 为什么WebRTC需要专用的子系统进行连接,又是怎样连接的?)
  4. 安全性( WebRTC具有哪些安全性保障,又是如何做到的?)
  5. 搭建实时网络(网络在实时通信中的重要性以及如何处理网络中的各种问题)
  6. 媒体通信(WebRTC媒体通信的作用及其工作原理)
  7. 数据通信(WebRTC数据通信的作用及其工作原理)
  8. WebRTC应用场景(人们使用WebRTC构建什么以及他们是如何实现的)
  9. 调试(如何分析并定位相关问题,以及一些流行的调试工具)
  10. 历史(对WebRTC一系列协议作者的采访)
  11. 常见问题( 使用WebRTC时常见的问题及解答)
  12. 术语

为什么WebRTC需要专用的子系统进行连接?

目前,大多数部署的应用程序都通过客户端/服务器方式进行连接。客户端/服务器方式连接要求服务器具有稳定且公开可用的传输地址。客户端与服务器联系,然后服务器做出响应。

WebRTC不使用客户端/服务器模型,它建立点对点(P2P)连接。 在P2P连接中,创建连接的任务被平均分配给两个对等方。这是因为无法猜测WebRTC中的传输地址(IP和端口),而且,在会话过程中,传输地址甚至可能会变更。WebRTC将收集所有可能收集的信息,并将尽力实现两个WebRTC Agent之间的双向通信。

听起来简单,建立点对点连接实际上可能会非常困难。这些Agent可能位于没有直接连接的不同网络中。即使在两个Agent可以直接连接的情况下,你可能还会遇到其他问题。比如在某些情况下,两个客户端使用不同的网络协议(UDP <-> TCP)或使用不同的IP版本(IPv4 <-> IPv6)。

尽管在建立点对点连接方面存在一些困难,在WebRTC提供的下面这些属性的帮助下,你仍然可以获得相对于传统客户端/服务器技术的一些优势。

降低带宽成本

由于媒体通信直接发生在peer之间,因此你无需为之付费,也无需托管一个单独的服务器来转发媒体。

更低延迟

直接通信时速度更快!当用户必须通过你的服务器运行所有内容时,这会使传输速度变慢。

安全的端到端通信

直接通信更安全。由于用户数据根本没有通过你的服务器,因此用户压根不需要考虑你的服务器会不会解密其数据。

它是如何工作的?

上面描述的连接过程是通过Interactive Connectivity Establishment(交互式连接建立/ICE) 实现的。这是另一个在WebRTC之前就已经出现的协议。

ICE是一种用来寻找两个ICE Agent之间通信的最佳方式的协议。每个ICE Agent都会发布如何访问自己的方式,这些路径被称为候选地址(candidates)。候选地址本质上是一个传输地址,ICE Agent认为这个传输地址可能可以被对端访问到。接下来ICE将确定候选地址的最佳搭配。

本章稍后将详细介绍实际的ICE过程。要了解ICE为什么存在,最好先了解我们要面临的网络特性。

现实世界的网络限制

ICE就是克服现实世界网络限制的方法。在我们开始讨论ICE如何解决问题之前,先讨论一下有哪些实际问题。

不在同一个网络中

在大多数情况下,两个WebRTC Agent不在同一个网络中。典型的呼叫通常是在没有直接连接的不同网络中的两个WebRTC Agent之间进行的。

下面是通过公共互联网连接的两个不同网络的示意图。在每个网络中,你拥有两个主机。

image

对于同一网络中的主机来说,互相连接非常容易。例如在192.168.0.1 -> 192.168.0.2之间通讯就很容易!这两个主机无需任何外部帮助即可相互连接。

但是,使用Router B的主机无法直接访问Router A背后的任何主机。你如何区分Router A后面的191.168.0.1主机和Router B后面相同IP的主机之间的区别呢?它们都使用内网IP!使用Router B的主机可以将数据直接发送到Router A,但是请求在那里就结束了。Router A怎么知道它应该将消息转发给哪台主机呢?

协议限制

有些网络不允许UDP通信,或者也有可能不允许TCP。有些网络的MTU(Maximum Transmission Unit/最大传输单元)可能非常低。网络管理员可以更改许多变量,这些修改可能会使通信变得困难。

防火墙/IDS规则

另一个问题是深度数据包检查和其他智能过滤方式。某些网络管理员将运行一些软件,这些软件会试图处理每个数据包。很多时候,这些软件无法识别WebRTC的数据包,由于它们不知道如何处理,它们可能会阻拦这些数据包,例如,它们可能将WebRTC数据包视为不在端口白名单上的可疑UDP数据包。

NAT映射

NAT(网络地址转换)映射是使得WebRTC连接成为可能的魔法。WebRTC就是使用NAT让处于完全不同的子网中的两个peer进行通信,从而解决了上述"不在同一网络中"的问题。尽管它带来了新的挑战,但让我们先来解释一下NAT映射是如何工作的。

NAT映射不使用中继,代理或服务器。跟上一个例子一样,我们有Agent 1Agent 2,它们位于不同的网络中。然而,流量穿透了路由器。看起来就像这样:

image

想要这样通信的话,你需要创建一个NAT映射。Agent 1使用端口7000与Agent 2建立WebRTC连接。这将创建一个192.168.0.1:70005.0.0.1:7000的绑定。然后,Agent 2将数据包发送到5.0.0.1:7000时,数据包会被转发给Agent 1。在这个例子中,创建一个NAT映射,就像是在路由器中做了一次自动化的端口转发。

NAT映射的缺点是:映射的形式不止一种(例如静态端口转发),并且映射的实现方式在不同的网络中也是不一样的。ISP和硬件制造商可能会以不同的方式来实现NAT映射。在某些情况下,网络管理员甚至可能禁用它。

好消息是,NAT映射的所有行为都是可以理解和观察到的,因此ICE Agent能够确认其创建了NAT映射,并确认该映射的属性。

描述这些行为的文档是 RFC 4787

创建映射

创建映射是最简单的部分。当你将数据包发送到网络外部的地址时,一个映射就被创建出来了!NAT映射只是由NAT分配的一个临时的公共IP和端口。出站的消息将被重写,使得其源地址变为新创建的映射地址。如果有消息被成功发到映射地址,消息会被自动路由返回给NAT网络中创建这个映射地址的主机。说到映射相关的细节,这就开始变得复杂了。

映射创建的行为

映射创建分为三类:

端点无关的映射

这种创建方式为NAT网络中的所有发送者只创建一个映射。如果你将两个数据包发送到两个不同的远程地址,这个NAT映射将被重用。两个远程主机将看到相同的源IP和端口。如果远程主机响应,它将被发送回相同的本地侦听器。

这是最好的情况。要使得呼叫能够建立起来,至少一侧必须是这种类型。

地址相关的映射

每次将数据包发送到新地址时,都会创建一个新的映射。如果你将两个数据包发送到不同的主机,则会创建两个映射。如果将两个数据包发送到同一远程主机,但目标端口不同,则不会创建新的映射。

地址和端口相关的映射

如果远程IP或端口不同,则会创建一个新的映射。如果将两个数据包发送到同一远程主机,但目标端口不同,则将创建一个新的映射。

映射过滤行为

映射过滤是关于允许谁使用映射的规则。它们分为三个类似的类别:

端点无关的过滤

任何人都可以使用该映射。你可以与其他多个peer共享该映射,他们都可以向该映射发送流量。

地址相关的过滤

只有为其创建映射的主机才能使用该映射。如果你将数据包发送到主机A,则它可以根据需要响应任意数量的数据包。如果主机B尝试将数据包发送到该映射,将被忽略。

地址和端口相关的过滤

仅有创建映射的主机和端口可以使用该映射。如果你将数据包发送到主机A:5000,则它可以根据需要响应任意数量的数据包。如果主机A:5001尝试将数据包发送到该映射,将被忽略。

映射的刷新

通常的建议是,如果5分钟未使用映射,则应将其销毁。但这完全取决于ISP或硬件制造商。

{{< hint info >}}
译注:换个说法,NAT映射的创建即是NAT网络中的主机发送数据时,路由器的处理方式;而过滤即是接收数据时,路由器的处理方式。映射的刷新即是路由器释放映射的处理方式。不同网络情况不同,因此某些特定的搭配会导致两个网络间无法建立P2P连接。在穿透相关的技术中,将不同的情况称为不同的锥形
{{< /hint >}}

STUN

STUN(NAT会话传输实用程序)是一种用来配合NAT使用的协议。这是WebRTC(和ICE!)之前的另一项技术。它由RFC 5389定义,该文件还定义了STUN数据包结构。STUN协议也在ICE/TURN中被使用。

STUN很有用,因为它允许以编程方式创建NAT映射。在STUN之前,我们能够创建NAT映射,但是我们不知道映射的IP和端口是什么!STUN不仅使你能够创建映射,还可以让你获取映射的详细信息,你可以他人分享这些详细信息,然后他们便可以通过你刚刚创建的映射向你传回数据。

让我们从对STUN的基本描述开始。稍后,我们再将话题扩展到TURN和ICE的用法。现在,我们只打算描述请求/响应流程来创建映射。然后,我们将讨论如何获取该映射的详细信息以便与他人共享。当你在ICE URLs中有一个用于WebRTC PeerConnection的stun:服务器时,此过程就会发生。简而言之,STUN向NAT外部的STUN服务器发送请求,服务器返回其在请求中观察到的内容,STUN根据这些内容来帮助NAT后面的端点找出已创建的映射。

协议结构

每个STUN数据包都具有以下结构:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0|     STUN Message Type     |         Message Length        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Magic Cookie                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                     Transaction ID (96 bits)                  |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             Data                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

STUN 消息类型

每个STUN数据包都有一个类型。目前,我们仅关心以下几种:

  • Binding Request - 0x0001
  • Binding Response - 0x0101

为了创建一个NAT映射,我们发出一个Binding Request。然后服务器回应一个Binding Response

消息长度

这就是Data段的长度。这一段中包含由消息类型所定义的任意数据。

Magic Cookie

指的是固定值0x2112A442,以网络字节顺序发送。这个值有助于将STUN流量与其他协议区分开。

交互(Transaction)ID

一个96-bit的标识符,用于唯一标识一个请求/响应对。这可以帮助你配对请求和响应。

数据

数据将包含一个STUN属性的列表。一个STUN属性具有以下结构:

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Type                  |            Length             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Value (variable)                ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

STUN Binding Request不使用任何属性。这意味着一个STUN Binding Request仅包含header。

STUN Binding Response使用一个XOR-MAPPED-ADDRESS (0x0020)。此属性包含一个IP和一个端口。这正是所创建的NAT映射的IP和端口!

创建NAT映射

使用STUN创建NAT映射只需要发送一个请求!你向STUN服务器发送一个STUN Binding Request。然后,STUN服务器回应一个STUN Binding Response
STUN Binding Response将包含映射地址映射地址是STUN服务器看到你的方式,也是你的NAT映射
如果你希望某人向你发送数据包,那么你应该共享该映射地址

人们还会将映射地址称为公网IPServer Reflexive Candidate

确定NAT类型

不幸的是,映射地址可能并非在所有情况下都可用。如果是地址相关的映射,则只有STUN服务器才能将流量发送回给你。如果你共享它,那么另一个peer尝试向该地址发送的消息将被丢弃。这使得该peer无法与别的peer交流。如果STUN服务器还可以为你将数据包转发给对端peer,你可能会发现地址相关的映射问题实际上是可以解决的!这也就是下面将要说到的TURN解决方案。

RFC 5780定义了一种方法,可以运行一个测试来确定你的NAT类型。这很有用,因为你可能会提前知道是否可以进行直接连接。

TURN

在无法建立直接连接的情况下,RFC 5766中定义了TURN(使用中继穿透NAT)。当你的两个peer的NAT类型不兼容,或者双方使用不同协议时,就需要使用TURN!TURN也可以被用于保护隐私的目的。如果通过TURN运行所有通讯,客户的真实地址在对端是被隐藏的。

TURN使用专用服务器。该服务器充当客户端的代理。客户端连接到TURN服务器并创建一个对应的Allocation。通过创建该Allocation,客户端将获得一个临时IP/端口/协议三元组,其他peer可以使用该IP/端口/协议将数据发送给该客户端。这个新的监听地址被称为中继传输地址。你可将其视为转发地址并分享给他人,以便其他人可以通过TURN向你发送流量!对于每个将获得该中继传输地址的peer,你必须为其创建一个新的Permission,以允许它与你进行通信。

当你通过TURN发送出站流量时,它会通过中继传输地址发送。当远程peer获得该出站流量时,他们会看到数据来自TURN服务器。

TURN生命周期

下面就是一个客户端创建TURN allocation时必须做的所有事情。对于其他peer而言,与使用TURN服务器的客户端进行通信和其他客户端没有任何区别,先获得IP和端口,然后像跟其他任何主机一样通信。

Allocations

Allocations是TURN的核心。本质上,一个allocation就是一个"TURN会话"。要创建一个TURN allocation,你需要与TURN Server Transport Address(服务器传输地址,通常在3478端口)进行通信。

创建allocation时,你需要提供/确定以下内容:

  • 用户名/密码 - 创建TURN allocation时需要身份验证。
  • Allocation传输方式 - 中继传输地址可以是UDP或TCP方式。
  • 连续端口 - 你可以为多个allocation请求顺序排列的一系列端口,这点与WebRTC无关。

如果请求成功,你将在TURN服务器上获得响应,在响应的数据部分,包含以下的STUN属性:

  • XOR-MAPPED-ADDRESS - TURN ClientMapped Address。当有人将数据发送到中继传输地址时,数据将被转发到该地址。
  • RELAYED-ADDRESS - 这是你提供给其他客户端的地址。如果有人将数据包发送到该地址,数据包会被转发到TURN客户端。
  • LIFETIME - Allocation被销毁的时间。你可以通过发送Refresh请求来延长这一时间。

{{< hint info >}}
译注:上面两个地址很拗口,但实际上理解起来并不复杂。Mapped Address是Turn Client的实际地址,也就是Turn Server收到数据包时的目标地址。而Relayed Address是Turn Client的名义地址,也就是其他WebRTC Agent要发送数据给这个Turn Client时,所使用的地址。
{{< /hint >}}

权限

在你为远程主机创建权限之前,远程主机是无法通过你的中继传输地址发送数据的。所谓创建权限,即是告知TURN服务器一个"可以用来发送入站流量"的IP和端口。

远程主机需要先为你提供TURN服务器上使用的IP和端口。这意味着它应该先向TURN服务器发送一个STUN绑定请求。 有时会发生这样一个常见的错误情况,即是远程主机发送STUN绑定请求到另外一台服务器,然后再要求TURN服务器为此IP创建权限。

对于上面那种错误情况,假设你要为一个使用地址相关的映射的NAT网络的主机创建权限,如果你从其他TURN服务器生成映射地址,则所有入站流量都将被丢弃。因为每次他们与其他主机通信时,它都会生成一个新的映射。如果未被刷新,权限将在5分钟后过期。

{{< hint info >}}
译注:对于这个常见的错误情况,实际指的是被连接的主机从TURN服务器以外的STUN/TURN服务器获取本机IP,再告知发起连接的主机这样的情况。当被连接的主机使用地址相关的映射类型的NAT时,它获取的IP在当前的TURN服务器上是无效的。
{{< /hint >}}

SendIndication/ChannelData

这是TURN客户端将消息发送到远端peer时所使用的两个消息。

SendIndication是一个自包含的消息。它包含你希望发送的数据,以及你希望发送的目标。如果你要向远端peer发送大量消息的话,这种方式很昂贵。因为如果要发送1,000条消息,目标IP地址就被重复了1,000次!

ChannelData允许你发送数据,但不需要重复IP地址。你需要先创建一个具有IP和端口的通道(Channel)。然后使用ChannelId发送,IP和端口将在服务器端被填充进去。如果你要发送大量消息,这是更好的选择。

刷新

Allocations将自动销毁。要避免其过早销毁,TURN客户端必须在创建allocation时指定的LIFETIME到来之前,及时刷新它们。

TURN 使用方法

TURN有两种用法。通常情况下,一个peer会作为"TURN客户端"连接,而另一方则直接进行通信。在某些情况下,你可能在两侧都需要使用TURN服务。举例来说,当两个客户端都位于在禁用UDP的网络中时,只能通过TCP连接到各自的TURN服务器来建立连接。

下面这些图有助于说明TURN的用法。

单个 TURN Allocation 通信

image

双重 TURN Allocation 通信

image

{{< hint info >}}
译注:单个TURN Allocation的情况,指的是一个TURN Client和另一个可访问的UDP Client的通信。双重TURN Allocation的情况,指的是两个TURN Client之间通信。
{{< /hint >}}

ICE

ICE(交互式连接建立)是WebRTC连接两个Agent的方式。这也是一项WebRTC前就有的技术,在RFC 8445中定义!ICE是用于建立连接的协议。它会确定两个peer之间所有可能的路由,然后确保你保持连接状态。

这些路由被称为Candidate Pair(候选地址对),也就是本地地址和远程地址的配对。这就是STUN和TURN在ICE中发挥作用的地方。这些地址可以是你的本地IP地址,NAT映射中继传输地址。通信双方需要收集它们要使用的所有地址,交换这些地址,然后尝试连接!

两个ICE Agent使用ICE ping数据包(正式名称为连通性检查)通信以建立连接。一旦建立连接后,他们就可以发送任何数据。感觉就像使用普通socket一样。连通性检查使用STUN协议。

创建ICE Agent

ICE Agent要么处于控制中,要么处于受控中控制中的 Agent是决定选择候选对的 Agent。通常来说,发送offer的peer是控制中的一方。

每一方都必须有一个用户片段和一个密码。必须先交换这两个值,接下来才能进行连接性检查。用户片段以纯文本形式发送,用于多个ICE会话的解复用(demux)。
密码用于生成MESSAGE-INTEGRITY属性。在每个STUN数据包的末尾,都有这个属性,该属性是使用密码作为密钥的整个数据包的哈希值。这用于验证数据包并确保它未被篡改。

对于WebRTC,所有这些值都通过上一章中所述的会话描述进行分发。

候选地址收集

现在,我们需要收集所有可能联通的地址。这些地址被称为候选地址。这些候选地址也通过会话描述来分发。

主机

主机候选地址直接在本地接口上侦听。可以是UDP或TCP方式。

mDNS

mDNS候选地址类似于主机候选地址,但是其IP地址是隐藏的。你不必给对方提供你的IP地址,只需要给他们提供一个UUID作为主机名。然后设置一个多播监听器,并在有人请求你发布的UUID时进行响应。

如果你与Agent位于同一网络中,则可以通过多播找到彼此。如果不在同一网络中,则将无法连接(除非网络管理员明确配置网络以允许多播数据包通过)。

这对于保护隐私很有用。以前,用户可以通过WebRTC使用主机候选地址(甚至无需尝试与你连接)来找出你的本地IP地址。而使用mDNS候选地址的话,他们只能获得随机的UUID。

服务器自反(Server Reflextive)

服务器自反候选地址是通过对STUN服务器执行STUN绑定请求时生成的。

当你收到STUN绑定响应时,XOR-MAPPED-ADDRESS就是你的服务器自反候选地址。

Peer自反

Peer自反候选地址是指,当你从你不知道的地址收到入站请求时,由于ICE是经过身份验证的协议,因此你知道这些传输是合法的,这只是意味着远端Peer是通过它也不知道的地址与你通信。

这通常会发生在这样的情况下,当主机候选地址服务器自反候选地址进行通信时,由于你是在子网外部进行通信,因此创建了一个新的NAT映射。还记得我们说过的连通性检查实际上是STUN数据包吗?STUN响应的格式自然允许peer报告Peer自反地址。

中继

中继候选地址是通过使用TURN服务器生成的。

在与TURN服务器进行初始握手之后,你将获得RELAYED-ADDRESS,这就是你的中继候选地址。

连通性检查

现在我们知道了远程Agent的用户片段密码和候选地址。我们可以尝试连接了! 候选地址可以相互配对。因此,如果每边有3个候选地址,那么现在就有9个候选地址对。

看起来像这样

候选地址选择

控制中的Agent和受控中的Agent都开始在每个候选地址对上发送流量数据。这样是必须的,因为如果一个Agent位于一个地址相关映射的网络中,这样会创建Peer自反候选地址

每个收到流量数据的候选地址对,会被提升为有效候选地址对。接下来,控制中的Agent将指定一个有效候选地址对。这就是提名候选地址对。然后,控制中的Agent和受控中的Agent再尝试进行一轮双向通信。如果成功,则提名候选地址对将成为选定的候选地址对!它将被用于后面的会话中。

重新启动

如果选定的候选地址对由于任何原因停止工作(如:NAT映射到期,TURN服务器崩溃等),则ICEAgent将进入失败状态。此时可以重新启动两个Agent,然后重新完整执行整个过程。

原文地址:Connecting | WebRTC for the Curious

推荐阅读
作者信息
相关专栏
前端与跨平台
90 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。