WebRTC 中的 ICE 实现
WebRTC 中的 ICE 实现Candidate 种类与优先级ICE 策略P2P 连接完全锥型 NATIP 限制锥型 NAT端口限制锥型 NAT对称型 NATNAT 类型检测如何进行 NAT 穿越 网络中继TURN 协议中转数据WebRTC 使用 TURN 协议STUN/TURN 服务器的安装与部署
WebRTC 中的 ICE 实现
Candidate 种类与优先级
ICE中使用的Candidate具有优先级次序,由高到低分别为host、srflx、prflx、relay。WebRTC进行一对一音视频通信时,就是按照这个次序尝试建立连接的。
WebRTC通信时会按照内网、P2P、relay这样的次序尝试连接:
内网连接对应host连接策略。host连接表示直接在本地设备上建立的连接,也就是在同一局域网内的设备之间的直接连接。不在同一局域网内,双方尝试host型Candidate连接时会失败。不过,在双方尝试连接时,双方Candidate的收集工作并未停止。Candidate收集线程还在收集其他类型的Candidate,如从STUN/TURN服务器收集srflx和relay类型的Candidate。P2P连接对应srflx和prflx连接策略。srflx(server reflexive)和prflx(peer reflexive)连接都是通过STUN服务器获取的公共IP地址,用于建立点对点连接。srflx连接是通过NAT后的公共IP地址建立的连接(当收集到srflx类型的Candidate时,ICE会尝试NAT打洞),而prflx连接是对等方自己的NAT映射地址。中继连接对应relay连接策略。当无法建立直接的P2P连接时,WebRTC会使用TURN服务器作为中继,建立relay连接。relay连接通过中继服务器传递数据,确保数据的可靠传输。总结:
WebRTC的ICE机制会选择最好的链路传输音视频数据,即如果通信的双方在同一网段内,则优先使用内网链路;如果通信的双方不在同一网段,则优先使用P2P;当以上方式都无法连通时,则使用relay服务进行中转。ICE的连通率几乎可以达到100%。在内网和P2P无法连通的情况下,它还可以通用中继的方式让彼此连通,从而大大提高了WebRTC的连通率。WebRTC中的ICE既考虑了数据传输的效率,又考虑了网络的连通率,实现起来也简单。
ICE 策略
RTCPeerConnection提供了一种在浏览器之间建立点对点连接的方式,而无需通过服务器进行中转。它使用了ICE(Interactive Connectivity Establishment,交互式连接创建)协议来处理网络地址和端口的自动配置,以确保连接的稳定性和可靠性。
构造RTCPeerConnection对象时,其输入参数的类型为RTCConfiguration。参数 configuration 是一个 JSON 对象,用于提供配置新连接的选项。
下面是 RTCConfiguration 各字段的含义:
iceServers是RTCIceServer类型的数组,可以包含一个或多个STUN和/或TURN服务器。
dictionary RTCIceServer {required (DOMString or sequence<DOMString>) urls;DOMString username;DOMString credential;RTCIceCredentialType credentialType = "password";};
各字段含义:
url:服务器地址;username:访问该服务器时使用的用户名;credential:访问该服务器时使用的密码;credentialType:指明授权方式为密码方式,也是目前唯一的授权方式。更详细的介绍:WebRTC 的核心:RTCPeerConnection
RTCConfiguration 实例:
RTCConfiguration pcConfig = { 'iceServers': [{ 'urls': 'turn:stun.learningrtc.cn:3478', 'username': "username1", 'credential': "password1", },{ 'urls': 'turn:stun.avdancedu.com:3478', 'username': "username2", 'credential': "password2", }], 'iceTransportPolicy': "all", 'bundlePolicy': "max-bundle", 'rtcpMuxPolicy': "require"};RTCPeerConnection pc = new RTCPeerConnection(pcConfig);
解释:
iceServers 包含两个 TURN 服务器,相当于为 WebRTC 增加了两个 relay 类型的 Candidate。当内网和P2P无法连通时,RTCPeerConnection 会尝试与这两台中继服务器连接。如果其中一台连接成功,就不会尝试另一台。iceTransportPolicy 为 “all”,表示所有的 ICE 候选都会被考虑,按 Candidate 的优先级次序尝试连接。bundlePolicy 为 “max-bundle”,所有媒体数据使用同一个 Candidate,这样更有利于端口资源的利用。rtcpMuxPolicy 为 “require”,表示 RTCP 和 RTP 数据共用一个 Candidate。P2P 连接
这里的 P2P 指的就是如何进行 NAT 穿越。NAT在真实的网络环境中随处可见,它的出现主要出于两个目的:
为了解决IPv4地址不够用的问题。当时IPv6短期内还无法替换IPv4,而IPv4的地址又特别紧缺,所以人们想到让多台主机共用一个公网IP地址,大大减缓了IPv4地址不够用的问题。为了解决安全问题。使用NAT后,主机隐藏在内网,这样黑客就很难访问到内网主机,从而达到保护内网主机的目的。NAT就是一种地址映射技术,它在内网地址与外网地址之间建立了映射关系。当内网主机向外网主机发送信息时,数据在经过NAT层时,NAT会将数据包头中的源IP地址和源端口号替换为映射后的外网IP地址和外网端口。相反,当接收数据时,NAT收到数据后会将目标地址映射为内网的IP地址和端口再转给内网主机。
RFC3489和RFC5389是最重要的两份NAT穿越的协议文档。在RFC3489协议中,将NAT分成4种类型,即完全锥型、IP限制锥型、端口限制锥型以及对称型。在这4种类型中,越往后的NAT类型穿越难度越大。
大多数情况下NAT穿越使用的是UDP,这是因为UDP是无连接协议的,打洞会更加方便。当然,也可以使用TCP打洞。
完全锥型 NAT
完全锥型NAT的特点:一旦打洞成功,所有知道该洞的主机都可以通过它与内网主机进行通信。
当 host 主机通过 NAT 访问外网主机 B 时,就会在 NAT 上打洞。如果主机 B 将该洞的信息分享给主机 A 和 C,那么它们也可以通过该洞给内网的 host 主机发送消息。
“洞”就是 NAT 上的一个内外网的映射表,其格式为:{内网IP,内网端口号,外网IP,外网端口号}。有了这个映射表,所有发向洞的数据都会被 NAT 中转到内网的 host 主机。在 host 主机上,所有侦听其内网端口的应用程序可以收到所有发向它的数据。
IP 限制锥型 NAT
IP限制锥型NAT要比完全锥型NAT严格得多。IP限制锥型NAT的主要特点:NAT打洞成功后,只有与之打洞成功的外网主机才能通过该洞与内网主机通信,而其他外网主机即使知道洞口也不能与之通信。
只有 host 主机访问过的外网主机才能穿越这个洞。
IP 限制锥型 NAT 的映射表是一个五元组,其格式为:{内网IP,内网端口号,外网IP,外网端口号,[被访问主机的IP,…]}。当外网主机通过 IP 限制锥型 NAT 向内网主机发送消息时,NAT 会检测数据包头的源 IP 地址是否在 NAT 映射表中有记录。如果有,说明是合法数据,可以进行数据转发;如果没有记录,说明非法,NAT 将该数据包直接丢弃。
端口限制锥型 NAT
端口限制锥型NAT的主要特点:除了像IP限制锥型NAT一样需要对IP地址进行检测外,还需要对端口进行检测。
端口限制锥型 NAT 的映射表是一个五元组,其格式为:{内网IP,内网端口号,外网IP,外网端口号,[{被访问主机的IP,被访问主机的端口},…]}。
对称型 NAT
对称型NAT是4种NAT类型中对数据包检测最严格的。对称型NAT的特点:内网主机每次访问不同的外网主机时,都会生成一个新洞,而不像前面3种NAT类型使用的是同一个洞。
对称型NAT的映射表是一个六元组,其格式为:{内网IP,内网端口号,外网IP,外网端口号,被访问主机的IP,被访问主机的端口}。
对称型NAT每次访问不同外网主机都生成新洞的这种特性,导致对称型NAT碰到对称型NAT或者对称型NAT遇到端口限制型NAT时,双方打洞的成功率非常低,即使可以互通,成本也非常高。WebRTC遇到上面这两种情况时,直接放弃打洞的尝试。
NAT 类型检测
RFC3489 协议中给出了标准的 NAT 类型检测流程:
内网主机进行 NAT 类型检测时,需要两台 STUN 服务器,每台 STUN 服务器又需要两块网卡,每块网卡需要配置公网 IP 地址。
检测过程:
检测是否在NAT之后或者UDP socket是否阻塞
向IP1:PORT1发送数据包,要求IP1:PORT1返回数据包源地址和端口号,同时设置socket timeout,重复一定次数后,如果一直未收到数据包回复,表明UDP通信被阻塞。如果收到数据包,和自身的IP:port对比,如果相同,则表明没有经过NAT,不同则表明一个是外网IP,一个是内网IP,且经过NAT设备。
检测是否为完全锥形NAT
向IP1:port1发送数据包,要求IP2:port2向目的主机发送数据包,如果目的主机可以接收,表明是完全锥形NAT.
检测是否为对称形NAT
向IP2:port2发送数据包,要求返回数据包的外网地址和端口号,将其与步骤2的外网地址和端口号对比,如果完全相同,则为限制锥形NAT或者端口限制NAT,否则为对称形NAT。
检测是否为限制锥形NAT或者端口限制NAT
向IP2:port2发送数据包,由IP2:port3向源地址返回数据,如果可以收到,表明为限制锥形,否则为端口和IP限制锥形。
如何进行 NAT 穿越
各 NAT 之间可穿越表:
NAT 类型 | NAT 类型 | 能否穿越 |
---|---|---|
完全锥型 NAT | 完全锥型 NAT | 可以 |
完全锥型 NAT | IP 限制锥型 NAT | 可以 |
完全锥型 NAT | 端口限制锥型 NAT | 可以 |
完全锥型 NAT | 对称型 NAT | 可以 |
IP 限制锥型 NAT | IP 限制锥型 NAT | 可以 |
IP 限制锥型 NAT | 端口限制锥型 NAT | 可以 |
IP 限制锥型 NAT | 对称型 NAT | 可以 |
端口限制锥型 NAT | 端口限制锥型 NAT | 可以 |
端口限制锥型 NAT | 对称型 NAT | 不可以 |
对称型 NAT | 对称型 NAT | 不可以 |
可以看出,完全锥型 NAT 和 IP 限制锥型 NAT 可以与其他任何类型的 NAT 互通。而最后两种情况因为互通成本太高,不必尝试 NAT 穿越。
网络中继
当遇到NAT之间无法打通的情况时,WebRTC会使用TURN协议通过中转的方式实现端与端之间的通信。
TURN 协议中转数据
TURN协议采用了典型的客户端/服务器模式,其服务器端称为TurnServer,客户端称为TurnClient。TurnClient与TurnServer之间通过信令控制数据流的发送。
TURN协议底层依赖于STUN协议。值得一提的是,TURN协议本身是STUN协议的一个拓展,因此绝大部分TURN报文都是STUN类型的,作为STUN的一个拓展,TURN增加了新的方法(method)和属性(attribute)。
下图就是一个 TURN 服务中转的实例,有个TURN client端和一个TURN Server端以及两个Peer对端。
在上图中,左边的 TurnClient 是位于 NAT 后面的一个客户端(内网地址是10.1.1.2:49721)。TurnClient 首先向 TurnServer 的 3478 端口发送 Allocate 指令。TurnServer 收到该消息后,在 TurnServer 分配一个与 TurnClient 对应的 Relay 地址(192.0.2.15:50000),任何发向 Relay 地址的数据都会被转发到 TurnClient 端。
主机 A 和 B 不是 TurnClient,它们被称为 Peer 端。Peer 端可以使用 UDP 向 TurnServer 的 Relay 地址发送数据,TurnServer 根据映射关系,将 Relay 地址收到的数据转给对应的 TurnClient。
注意,TurnClient 和 TurnServer 之间的传输协议可以是 UDP 或者 TCP。而在 TurnServer 上分配的 Relay 地址使用的都是 UDP。
TurnServer 上的每一个 allocation 都唯一对应一个 TurnClient,并且只有一个中继地址,因此当数据包到达某个中继地址时,服务器总是知道应该将其转发到什么地方。
但值得一提的是,一个 TurnClient 可能在同一时间在一个 TurnServer 上会有多个 allocation,这和上述规则是并不矛盾的。
TURN 协议对应端到端传输数据提供了 2 种方法:
Send/Data indication:指令 Send indication(XOR-PEER-ADDRESS、DATA) 用于 TurnClient 向某个 Peer 端发数据,其中 XOR-PEER-ADDRESS 属性用于指定向哪个主机转发数据,DATA 属性指明数据的具体内容。相反,当 Peer 端通过 TurnServer 向 TurnClient 发数据时,使用 Data indication 指令,指明向哪个 TurnClient 转发数据。tunnel机制:使用tunnel机制的好处是不用再像使用Send/Data indication指令一样,每次都要指定数据发往的地址,只需要在开始发送数据之前,发送ChannelBind指令将channel number与目标地址绑定一次即可,后面统一使用channel number就可以找到发往的目的地。WebRTC 使用 TURN 协议
WebRTC 收集到的 relay 类型 Candidate,指的就是通过 TURN 协议的 Allocation 指令分配的地址。
WebRTC通信的两端既是 TurnClient,因此都可以使用 TURN 协议与 TurnServer 建立联系;又是彼此的 Peer 端,因此可以向对端的 Relay 地址发送数据,从而让 TurnServer 将数据中转给对端。
3478 是一个多路复用的端口:
接收 Allocate 指令:TurnClient 首先向 TurnServer 的 3478 端口发送 Allocate 指令。TurnServer 收到该消息后,在 TurnServer 分配一个与 TurnClient 对应的 Relay 地址。TurnServer 从 Relay 地址收到数据后,将其打包成 TURN 消息,也要经过 3478 端口转发给对应 TurnClient。STUN/TURN 服务器的安装与部署
公网上的 STUN/TURN 服务器的安装与部署一般采用云主机。目前比较流行的是 Google 开源的 coturn 服务器。
基本步骤:
获取 coturn 源码:https://github.com/coturn/coturn。编译安装。配置 coturn:配置一下侦听的端口(默认为 3478)、指定云主机的公网 IP 地址、访问 STUN/TURN 服务的用户名和密码。启动 STUN/TURN 服务。测试 STUN/TURN 服务:打开trickle-ice 测试工具,按要求输入 STUN/TURN 地址、用户名和密码就可以探测 STUN/TURN 服务运行是否正常。STUN/TURN 部署好后,就可以使用它转发多媒体数据,不用担心通信双方因 NAT 或防火墙等原因而无法通信的问题。