完整WebRTC技术及应用概要
在本章节和后续章节的讨论中,笔者将根据RFC5245/RFC8445的规范架构,结合笔者的这篇历史文章来讨论WebRTC/ICE的详细技术内容。希望通过笔者完整的讨论,读者可以非常清楚了解WebRTC的流程,特别是关于ICE的处理。笔者在文章中使用的一些本规范的专有名词(例如,check 或者check list,事实上,在本规范中它具有特定的含义),可能没有直接以中文的名称来介绍,这样做的目的是为了保证不会产生歧义,所以笔者尽量使用规范中的专有名词来解释。所以,请读者在阅读时一定要注意。
说明:1)一些专有名词以前的章节中已经发布,这里不再介绍。2)笔者本人水平有限,文章中所使用的专有名词中文命名或解释可能和其他网上的的有所不同,建议读者参考RFC原文理解或者发邮件给规范起草人获得支持。
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095517973.png)
笔者会在接下来的内容中重点讨论关于ICE的背景介绍,ICE概览,ICE使用/ICE候选地址采集和交互,ICE候选对象流程处理,执行连接性检查,完成ICE创建,轻量级ICE使用介绍等话题。
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095945293.png)
1、背景介绍
如果读者看过前面的SDP全解的读者可能已经了解,SIP使用了offer/answer结合模式,通过SDP消息来实现媒体传输,其最终目的是实现媒体流之间的创建和完整传输。ICE英文全名是Interactive Connectivity Establishment。RFC5245(更新的RFC6336)对ICE做了规定。笔者推荐的一般简单的定义是:ICE=STUN+TURN+协商机制+协商路径。
但是,因为网络越来越复杂,终端的环境也发生了根本变化,因此NAT问题也越来越多。RFC3235规范针对NAT(地址转换)发布了一个指导。很多相关的协议希望通过媒体流之间的点对点传输解决媒体本身的问题(例如,低时延,降低丢包,降低部署成本),但是在涉及到NAT环境时,通常会遇到一些更加复杂的问题,导致实际部署的难度大大增加。为了解决NAT的问题,很多针对性的技术规范增加了对NAT环境的支持,常见的规范包括:
- Application Layer Gateways (ALGs)
- Middlebox Control Protocol (RFC 3303)
- STUN(RFC 3489)
- Realm Specific IP(RFCs 3102和3103)
- SDP拓展支持(RFC 4566和RFC 3605)
随着技术本身的不断发展,这些针对NAT支持的规范也带来了很多突出的问题,它们都存在各自的优缺点。这样的话,网络管理就会增加很多的不确定性,给系统管理带来很多问题。为了解决这些问题,一些相关规范组织希望使用一种统一的解决方式或规范,并且这种协议可以提供灵活性来满足目前网络环境对NAT的支持。目前,大家一致的共识就是使用ICE技术(Interactive Connectivity Establishment)。此规范通过offer/answer模式定义ICE来支持基于UDP的媒体流NAT问题(当然,ICE也可以通过拓展支持ICE-TCP传输协议)。
ICE是offer/answer模式的一种拓展形式,通过在SDP的offer/answer消息中包含多个地址和端口,使用这些地址端口和其交互消息来检测点对点的连接性。在SDP中的地址和端口,以及其连接性检测是通过STUN来完成。
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095447726.png)
图片来自于互联网资源
注意,关于STUN的最新规范已经再次更新(更新时间为2020年),如果读者有兴趣做进一步研究,可参考RFC5389和RFC8489。除了STUN以外,ICE也使用了STUN的另外一个拓展协议-TURN(RFC5766)实现穿越转发。另外,因为ICE对每个媒体流进行了多地址和端口交互,它也允许地址选择支持多宿主和双栈主机(IPv4/IPv6),因此也不再支持RFC4091和RFC4092。
2、ICE概览
在一个比较典型的ICE部署环境中,至少需要两个终端需要互相进行通信。终端之间可以通过一些信令结合SDP的offer/answer模式来实现,例如,我们前面讲的SIP协议。读者需要注意,ICE的目的不是为了某些协议(例如SIP)的NAT穿越,关于SIP相关的NAT穿越是通过RFC5626规定的,如果读者有兴趣的话,可以查阅此规范。这里,ICE假设终端之间可以创建协议连接。具体来说,在ICE流程处理开始阶段,agents就忽略了它们本身的技术属性。终端也可能在或不在NAT后(例如后面将的轻量级部署终端),ICE允许终端获取到技术属性的足够信息,然后找到一个或多个潜在的路径,创建数据会话实现相互连接。
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095516486.png)
图片均来自于互联网资源
在以上示例是一个非常典型的ICE部署场景示例。两个终端分别标识为L(左边)和R(右边)。两个终端都有各自的NAT环境,双方也不清楚对端NAT的状态属性,左右双方终端都有意愿通过ICE候选对象交互实现通信。很多时候,双方的交互可能都使用SIP协议。对于双方终端,SIP服务器和NAT来说,在网络中,ICE经常使用STUN和TURN来保证所有对象的协同。每个终端agents可以支持自己独立的STUN和TRUN服务器,也可以同一STUN和TURN同一服务器。基本的ICE工作理念是: 针对传输协议(这里重点讨论UDP)每个agent都有各种候选地址(绑定了IP地址和端口),通过这种候选地址和对端agent实现通信。候选地址可能包括:附加到网络接口的传输地址(server reflexive 地址,其地址是STUN发现的地址,在NAT外),NAT中公网侧的转换后的传输地址,从TURN分配到的传输地址(转发地址)。客观上存在这种可能,任何L侧的候选传输地址都用来和任何R侧的候选传输地址进行通信。但是,在实际场景中,过多的候选传输地址组合可能不能工作。例如,如果L侧和R侧双方都在NAT后的话,直接附加的网络接口地址可能不会直接通信,因此这里需要ICE介入。这里,ICE的目的是发现何种候选地址配对可以工作。ICE的工作方式是通过系统地尝试所有可能的候选配对(排序处理后),直到ICE找到一组或者多组可以工作的候选配对。ICE对Peers的检测配对需要经过几个核心步骤:
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095423543.jpg)
3、采集候选地址
如果需要执行ICE的话,agent首先需要确认候选地址。候选地址是一种特别的地址,它是由IP地址和端口的组合构成,其目的是支持传输协议(我们这里仅讨论UDP)。RFC8445定义了三种候选地址,一种是来自于物理网络接口,另外一种来自于网络的逻辑接口,还有一种是通过STURN或者TURN发现的候选地址。第一种类别的候选地址支持的传输地址直接从本地网络接口获得。这样的候选地址我们称之为“host candidate”,这样类型的本地地址可以从本地网络,WIFI,远端网络或者VPN的方式获得。对agent来说,这样的地址都可以通过分配获得,并且作为本地接口来使用。如果agent是一个多宿主主机的话,它可以从不同的IP地址获得候选地址。具体的候选地址如何获得取决于网络中peer(会话中另外一个agent)的位置。例如,如果agent支持了两个不同的IP地址的话(一个是内网地址,一个是外网地址),不同的peer就可以通过不同的地址和agent通信。
另外一种候选地址是agent使用STUN或TURN获得的额外的候选地址,这些候选地址主要表现为两种地址形式:NAT公网侧的已转换地址(server-reflexive 候选地址),TURN服务器分配的转发地址(relayed 候选地址)。这里读者一定要注意两种地址的获取方式。当使用TURN服务器时,以上两种候选地址都来自于TURN服务器分配地址;当仅使用了STURN服务器的话,agent只能获取到server reflexive地址。以下是候选地址的关系示例图:
候选地址关系
在以上关系图中,两种类型的候选地址都是通过TURN服务器发现获得的。这里的地址端口配对中,大写X表示IP地址,小写x表示UDP端口。当agent对IP地址和端口发送一个(X:x)TURN地址分配请求时,NAT(假设这里有NAT)就会创建一个绑定关系X1':x1',映射server-reflexive地址到主机候选地址。从本地主机候选地址发送出去的数据包将会通过NAT地址转换变成一个server-reflexive候选地址。同样的道理,从server-reflexive候选地址进入到主机候选地址的数据也是通过NAT地址转换完成。这里,base是一个比较重要的概念,需要读者明确,“base"是一个本地主机候选地址,它关联一个给定的server-reflexive候选地址。base指的是一个agent为指定的候选地址发出数据的地址。因此,有时存在一个比较极端的场景,主机候选地址也可以有自己的base地址,这个base 地址和主机候选地址相同。
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331100457416.jpg)
来自于互联网资源
当agent和TURN服务器存在多个NAT穿越时,TRUN请求会为每个NAT创建一个绑定,但是agent仅发现最外部的server-reflexive候选地址(距离TRUN服务器最近的候选地址)。如果agent不在NAT后的话,base 候选地址和server-reflexive候选地址相同,server-reflexive候选地址就会被移除。
Agent发出的分配请求到达TURN服务器端后,TURN服务器将会从它的本地IP地址Y中分配一个端口y,并且生成一个分配响应,分配响应通知一个agent的转发候选地址。TRUN服务器也会通知一个agent的server-reflexive候选地址X1':x1',通知的方式是把分配请求中的源传输地址拷贝到分配响应中来实现。TRUN服务器的工作角色类似于一个在L侧和R侧之间的数据转发。如果R侧想对L侧发送数据的话,R侧需要发送数据到Y:y 地址,然后TRUN服务器端前转到X1':x1'候选地址,然后经过NAT后映射到L侧agent。
当仅使用了STUN服务器时,agent发送一个STUN绑定请求给它的STUN服务器,STUN将会通知一个agent的server-reflexive候选地址X1':x1',其通知的方式是绑定请求中的源传输地址拷贝到绑定响应中。
4、连接性检查
一旦L侧agent采集到它所有的候选地址后,它会把这些地址重新按照从最高到最低的排序,然后通过信令通道发送给R侧agent。这些候选地址通过SDP的offer消息的属性参数发送到R侧agent(开始使用offer/answer交互模式)。当R侧agent收到offer消息后,R侧的agent也会执行一个同样的流程来采集候选地址,然后通过响应消息返回自己的候选地址。双方采集发送流程完成以后,每个agent都有自己的完整的候选地址和对待peer的完整候选地址。通过双方候选地址的配对处理,最后产生一个候选配对。候选地址配对产生以后,agent首先需要知道哪个候选地址配对是可以工作的,每个agent会按时设定一系列的检查。每个检查是一个STUN请求响应的事务,每个终端都会执行具体的候选地址配对流程,配对流程检查通过本地候选地址对远端候选地址发送一个STUN请求。连接性检查(Connectivity Checks)的基本原理非常简单:
- 对候选地址配对进行优先级排序
- 按照优先级排序顺序对候选配对发送检查请求
- 确认从其他agent收到检查状态
因此,在执行候选配对检查时需要一个四次握手的处理:
基本候选地址配对检查四次握手处理流程
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095424614.png)
这里一定要注意,发送STUN请求的目的地和接收源的IP地址和端口,这是完全相同的IP地址和端口,使用此IP地址和端口来传输媒体流(包括RTP和RTCP)。因此,agent会通过数据内容多路分解STUN/RTP/RTCP相关数据,而不使用其接收端口来分解STUN/RTP/RTCP数据。相对于使用端口的方式来说,多路分解方式会更容易处理检查状态,特别是针对RTP和RTCP的数据。因为,STUN绑定请求是用来执行连接检查的,在STUN响应中包含了agent的已转译的传输地址,这个地址是agent和对端peer之间的NAT公网侧地址。如果这个传输地址和agent已学习过的候选地址不同的话,agent就会知道这个候选地址是一个新的地址(PEER REFLEXIVE CANDIDATE),这个新地址和其他候选地址一样,也是ICE测试过的地址。
作为一种优化方式,只要R侧获得了L侧的检查消息,R侧会在同一候选配对中按时对L侧发送一个连接检查消息。这样的话,ICE就会加快发现有效候选流程处理时间,这个过程也称之为“TRIGGERED CHECK”。在双方握手完成以后,双方都知道对对端可以接收或者发送端对端消息。到此为止,连接检查结束。
5、候选地址排序
根据连接检查的介绍,读者可能已经注意到了,前面我们讨论的候选配对查询算法还有一定的问题,假设候选配对存在的话,无论查询方式是何种顺序,查询流程会一直查询直到找到这一对候选配对。显然,查询的顺序肯定影响候选配对的查询结果。因此,为了快速生成候选配对结果,候选地址需要经过排序处理,最后,排序后的候选配对结果就是一个check list。关于排序算法笔者在发送初始offer的章节进行讨论。基本上,排序算法需要遵守两个基本原则:
每个agent给它的候选地址一个优先级数字标识,此优先级标识会随候选地址发送到对端peer。
本地和远端优先级合并,每个agent针对候选配对来说有一个同样的排序。
其中,第二个原则非常重要。如果双方L侧agent和R侧agent的都在NAT后的话,为了确保ICE工作,必须特别注意第二个原则。我们经常可以看到,NAT是不会允许一台外部主机发送数据进入到NAT后的网络环境中,只有在NAT后的agent通过NAT发送数据给这台主机后才被允许交互,外部数据才能进入到NAT后的环境中。这里要注意,直到agent双方都已通过自己相关的NAT穿越发送check信息后,双向的ICE check才能最终成功。agent需要通过check list才能启动工作,所以,它需要周期性地发送一个STUN请求获得列表中的候选配对。这个处理过程称之为 “ORDINARY CHECKS”。
通常情况下,优先级算法的设计是为了同类候选地址直接获得同样的优先级,和一些非直接处理的方式相比,优先级算法在处理候选配对是可能更加高效。因此,通过优先级算法的处理流程可以快速高效地实现直接路由访问,路由处理路径中仅需要几个媒体转发和几个NAT转发。如果采用非直接处理的方式的话,候选配对可能需要更多媒体转发和NAT转发,经过越多的转发就会导致越多的不可控因素。所以,这样的方式不是一种好的推荐的方式。但是,agent都有自己的决定权来优化算法。
6、锁定候选配对
前面的讨论中,我们仅仅描述了一种场景,那就是agents想使用一个组件模块(COMPONENT)创建一个媒体会话。这里,COMPONENT是一个媒体流。在典型的应用环境中,agents实际需要创建一个连接来支持更多的数据流程。网络环境中的很多属性和组件具有非常大的相似度。通常情况下,如果一个组件模块可以工作的话,我们会借用同样的信息来决定最近的候选地址。ICE使用这样的处理方式来处理候选配对流程,这种算法简称为frozen candidates或者锁定候选算法。每个候选地址都和自己的一个属性相关联,我们称这个属性为“FOUNDATION”。如果它们被认为是“相似”的话,我们就会认为他们具有相同的FOUNDATION属性,这表示它们有相同的类型,并且都是从同一host candidate,同样的STUN服务器,使用同样的协议获得的类型数据。否则,这样的候选地址就是不同的。同样的道理,候选配对也有FOUNDATION属性。这个候选配对的FOUNDATION就是通过合并候选地址的FOUNDATION构成。在初始阶段,只有针对FOUNDATION是唯一状态的候选配对进行测试。其他的候选配对会被标识为“frozen” 或者锁定状态。当候选配对的连接检查是成功状态的话,其他带相同FOUNDATION属性的候选配对被unfrozen或者解封。通过这样的方式就可以避免了重复的连接性检查,增加检查的效率。但是,事实上,这种方式也有失败的可能。frozen candidates是ICE处理流程的一部分,ICE有优先级的算法会自动使用正确的顺序确保正确的候选地址被解封。
这里笔者再花费一点时间再一点补充说明。在笔者接触到的资源中,很多关于WebRTC/ICE的技术讨论,以及相关的官方中,包括RFC5245本身也没有非常完整地解释清楚“frozen”的定义。这里,笔者试图借用一定的示例来进一步明确说明frozen candidates的具体含义。首先,我们来解释一下在我们讨论的场景中具体的frozen定义。如果我们单纯从字面意思, 笔者认为可以理解frozen为封冻或者锁定的含义。接下来读者就容易理解frozen candidates的完整含义。从前面的解释,我们可以看到frozen candidates就是一种候选地址的状态。
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095446144.png)
来自于互联网资源
每个在check list中的候选配对都有自己的状态,具体的状态处理经过以下几个步骤:
- Frozen – 处于锁定状态,等待检查。
- Waiting – 从check list中选择最高优先级的候选配对进行处理。
- In-Progress – 为这个配对发送check请求,事务在处理流程中。
- Succeeded – 配对检查成功
- Failed – 配对检查失败
7、安全检查
大家知道,ICE的目的是发现两个agent之间的地址,通过这个地址来发送媒体流数据。当然,ICE必须确保这个地址不能出现安全问题,也必须避免把媒体数据发送到错误的地址。每个STUN的连接性检查都要经过消息认证码(MAC)的计算,消息认证码在信令通道中使用密钥交换的方式进行计算。因为MAC提供了消息完整性和数据原始认证,因此它可以杜绝攻击者伪造和修改连接性检查消息内容。一个比较典型的例子就是SIP的分叉呼叫处理。如果SIP呼叫用户正在使用ICE,并且此呼叫进行了分叉呼叫处理的话,ICE处理就会在每个独立的接收方之间进行。这种使用场景中,在信令通道中的密钥交换帮助每个ICE交换和其分叉的接收方进行交换处理。如果读者不了解分叉呼叫的内容,可以查阅本微信号的历史文章-关于分叉呼叫流程的处理。
8、完成ICE创建
通过前面的介绍,我们知道ICE检查是需要按照顺序处理的。ICE首先处理优先级最高的候选配对,然后再处理低优先级的候选配对。只要每个模块的媒体流检查都成功以后,ICE通过声明一个“成功”表示候选配对成功,整个ICE的创建就完成了。这种操作方式确实也是比较合理的,我们会在后续的文章中介绍具体的算法。但是,任何事情都有其两面性,通过这种按照优先级高低来执行候选配对检查的算法需要考虑一定的风险。如果最高优先级的候选配对产生了数据丢失的问题,这样就会耗费了稍长时间完成配对检查。这种情况下,ICE可能需要花费稍长时间,但是可能会产生比较好的结果。从根本上来说,RFC5245官方规定的优先级算法不一定会产生“优化”的结果。如果其算法的目的是选择低时延的媒体路径的话,使用了转发(转发可能是高延时)作为一种建议提示的话,这种转发建议实际上就没有多大关系。实际场景中可能使用往返时延(RTT)的衡量指标的方式可能比优先级的处理方式更好,在演示中场景中,低优先级的候选配可能比高优先级的候选配对取得更低的时延。
标识成功配对以后,接下来,ICE就会指定agent的功能角色。其中一个agent称之为CONTROLLING AGENT(主控agent),另外一个agnet是CONTROLLED AGENT(被控agent)。主控agent就会从有效的候选配对中指定一个配对来应对媒体处理。主控agent通过两种方式来实现指定候选配对:使用regular nomination或者使用aggressive nomination的方式来指定候选配对。
![](http://www.ctiforum.com/uploadfile/2020/0331/20200331095446385.png)
来自于互联网资源
使用regular nomination正常指定候选配对的话,主控方agent会一直发送请求消息,让check处理进行处理,直到找到至少一个有效的候选配对可以支持媒体。然后选择其有效的候选配对。接下来主控agent仍然会继续发送第二个STUN请求消息,但是,在第二个STUN请求中,主控agent会附加一个flag,通知对端,主控agent已经指定了一个候选配对,将要使用那个候选配对来处理媒体流。当携带flag的这个STUN事务完成以后,双方都取消了进一步的check流程。ICE将使用最终指定的那个有效配对发送媒体。ICE正在使用的这对候选配对称之为“SELECTED PAIR”,成为已选配对。所以,读者一定要明白,ICE是真正使用selected pair来进行媒体处理的,前面所有的候选配对都是为ICE最后选择配对进行准备。
上面,笔者介绍了正常指定配对的方式,这里我们再介绍一下aggressive nomination的使用方式。aggressive nomination的方式相对比较激进或主动一点。主控agent使用aggressive nomination时,主控agent会在每个STUN请求中附加一个flag,一旦找到第一个成功的配对的话,ICE就会结束其他的检查流程,然后使用其配对处理媒体,主控agent不会再发送第二个STUN请求。此selected pair具有最高的优先级。所以,我们通过两种方式的介绍,我们可以看出,aggressive nomination检查速度快,但是缺少灵活性;regular nomination则处理速度慢,但是可能会找到低时延的媒体路径。
一旦所有的媒体处理完成后,如果媒体中“m=”和“c=”行中的候选地址(默认候选)不能匹配selected pair(已选配对)的话,主控端会发送一个更新offer消息。如果ICE结束后,为了支持agent的媒体流,任何一方agent都可以在任何时间重新启动ICE。重新启动ICE可以通过agent发送一个更新offer的方式来处理。
9、ICE 轻量级部署讨论
为了让呼叫支持ICE,双方agent必须都使用ICE。但是,在实际环境中,某些agent本身就带了公网地址,它可以从任何通信端接收数据或者进行通信。为了让这类终端能够方便地支持ICE,ICE定义了一种特别的部署方式,称之为lite implementation,或者轻量级部署方式。当然,相对于lite implementation的支持方式,ICE支持正常的full implementation全部署方式。因为这类带国外地址的终端本身带有公网地址,所以,轻量级的部署方式会减少很多中间处理环节。首先,它不采集候选地址,它也不包含支持媒体的主机候选地址(一般的内网地址)。另外,虽然它们需要返回响应消息,但是,这类agent不会生成连接检查或运行状态机。当两个轻量级agent连接时无需发送检查请求响应消息;可是,当使用轻量级agent和全部署方式连接时,这时,全部署方式的agent会变为主控agent来控制检查流程,轻量级agent就会变为被控agent。笔者将会在后续章节或者文章中讨论这两种部署方式的具体细节。读者需要特别注意,轻量级的部署是针对全部署方式的一种补充方式,是在RFC5245中稍晚时间增加的功能支持。针对那些支持公网地址的终端来说,如果可以实现全部署方式的话,RFC5245推荐使用全部署方式。
以上都是关于ICE的热身流程,包括了基本的概念以及大概的处理流程。后续文章中,笔者将继续介绍ICE处理流程和具体细节,从真正的第一步处理流程开始-初始化offer的处理,包括初始化offer中的Full Implementation 要求和其具体步骤。
参考资料:
https://anyconnect.com/stun-turn-ice/
https://tools.ietf.org/id/draft-ietf-ice-rfc5245bis-13.html
https://tools.ietf.org/html/rfc8445
https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment
https://www.ietfjournal.org/interactive-connectivity-establishment/
https://ietf.org/documents/144/IETF_ICE_intro_92.pdf
![](http://www.ctiforum.com/uploadfile/2019/1224/20191224014316600.jpg)
关注微信公众号:asterisk-cn,获得有价值的Asterisk行业分享
Asterisk freepbx FreeSBC技术文档: www.freepbx.org.cn
融合通信/IPPBX商业解决方案:www.hiastar.com
如何使用FreeSBC,qq技术分享群:334023047, www.freesbc.cn