WebRTC 中两个或多个主机进行 P2P 连接是通过 STUN、TURN、ICE 等技术实现的。主机往往都是在 NAT 之后,且不同的 NAT 导致外部主机向内网主机发送数据的可见性不同。内网主机通过 STUN 协议可以获得 NAT 分配的外部地址。ICE 是主机之间发现 P2P 传输路径机制,ICE 中使用了 STUN 协议进行连通检测、传输路径的指定和保活。本文将对 STUN 和 ICE 协议进行分析和解读,希望能为开发者们带来一些启发和帮助。
1. NAT 类型
网络地址转换, 简称 NAT,节省了 IPv4 地址空间的使用并且隔离了内网和外网。NAT 对待 UDP 的实现方式有 4 种,分别如下:
1.1 完全圆锥型
一个内网地址(iAddr:iPort)被映射到一个外网地址(eAddr:ePort)。这个内网(iAddr:iPort)地址发送的数据包都会通过这个外网地址(eAddr:ePort)。外部主机可以通过这个外网地址(eAddr:ePort)向这个内网地址(iAddr:iPort)发送数据包。
1.2 地址受限锥型
一个内网地址(iAddr:iPort)被映射到一个外网地址(eAddr:ePort)。这个内网(iAddr:iPort)地址发送的数据包都会通过这个外网地址(eAddr:ePort)。外部主机 (hAddr:any) 只有接收过从内网(iAddr:iPort)发送来的数据包后,才能通过外部地址 (eAddr:ePort)发送数据包给内网地址(iAddr:iPort)。其中外部主机的端口可以是任意的。
1.3 端口受限锥型
一个内网地址(iAddr:iPort)被映射到一个外网地址(eAddr:ePort)。这个内网(iAddr:iPort)地址发送的数据包都会通过这个外网地址(eAddr:ePort)。外部主机 (hAddr:hPort) 只有接收过从内网(iAddr:iPort)发送来的数据包后,才能通过外部地址 (eAddr:ePort)发送数据包给内网地址(iAddr:iPort)。其中外部主机的端口 hPort 是受限的。
1.4 对称型
一个内网地址(iAddr:iPort)向外网地址(sAddr1:sPort1)发送的多次请求时,NAT 会分配同一个外网地址(eAddr1:ePort1)。若向不同的外网地址如(sAddr2:sPort2)发送数据包时,NAT 分配另外一个外网地址(eAddr2:ePort2)。外网地址 (sAddr1:sPort1)只有接收过从内网(iAddr:iPort)发送来的数据后,才能通过已经在 NAT 上开辟的(eAddr1:ePort1)发送数据包给内网.
2. STUN 简介
STUN 是 Session Traversal Utilities for NAT 简写,RFC5389 规定了具体内容。STUN 协议是用来获取内网地址对应在 NAT 上的外网地址,NAT 穿越。STUN 是 C/S 模式的协议,由客户端发送 STUN 请求;STUN 服务响应,告知由 NAT 分配给主机的 IP 地址和端口号。
2.1 STUN 消息结构
STUN 消息头为 20 字节,后面紧跟 0 或多个属性。STUN 头部包含一 STUN 消息类型、magic cookie、事务 ID 和消息长度。
(图)STUN 消息结构
每个 STUN 消息的最高位前 2 位必须为 0。当多个协议复用同一个端口的时候,这个可以用于与其他协议区分 STUN 数据包。消息类型确定消息的类别(如请求、成功回应、失败回应、指示 indication)。虽然这里有四种消息类型,但可以分为 2 类事务:请求/响应事务、指示事务。
magic cookie 为固定值 0x2112A442。
Transaction ID 标识同一个事务的请求和响应。当客户端发送多个 STUN 请求,通过 Transaction ID 识别对应的 STUN 响应。
2.2 STUN 消息类型
(图)STUN 消息类型
C0 和 C1 位置的 bit 指明了消息的分类。其余的 12 位置标示不同的请求类型,比如绑定请求。
2.3 STUN 属性
STUN 头之后是 0 或多个属性。每个属性都采用 TLV 编码,type 为 16 位的类型、lenght 为 16 位的长度、value 为属性值。每个 STUN 属性必须是 4 字节对齐。
(图)STUN 属性
STUN 属性格式:
STUN 服务器请求和响应都包含消息属性。一些属性不是强制性的,其中一些只能出现在绑定请求中,而其他一些只能出现在绑定响应中。 属性空间被划分为 2 个范围。强制理解属性 STUN 代理必须处理,否则 STUN 代理将无法正常处理该属性的消息;STUN 代理不能理解可选理解属性的话,这些属性可以被忽略。
强制理解属性 (0x0000-0x7FFF)
可选理解属性 (0x8000-0xFFFF)
具体说明如下:
MAPPED-ADDRESS 属性标识了 NAT 映射后的地址。
XOR-MAPPED-ADDRESS 属性与 MAPPED-ADDRESS 属性一致,映射后的地址要做异或处理。
USERNAME 属性用于消息完整性。用户名和密码包含在消息完整性中。
MESSAGE-INTEGRITY 属性是 STUN 消息的 HMAC-SHA1 值,长度 20 字节。MESSAGE-INTEGRITY 属性可以出现在任何类型的 STUN 消息中。用作 HMAC 输入的文本是 STUN 消息,包括头部,直到且包括 MESSAGE-INTEGRITY 属性前面的属性。FINGERPRINT 属性出现 MESSAGE-INTEGRITY 后。所以 FINGERPRINT 属性外,STUN 代理忽略其他出现在 MESSAGE-INTEGRITY 属性后的任何属性。
FINGERPRINT 属性可以出现在所有的 STUN 消息中,该属性用于区分 STUN 数据包与其他协议的包。属性的值为采用 CRC32 方式计算 STUN 消息直到但不包括 FINGERPRINT 属性的的结果,并与 32 位的值 0x5354554e 异或。
ERROR-CODE 属性被用于错误响应消息中。它包含一个在 300 至 699 范围内的错误响应号。
REALM 属性出现在请求中,表示认证时要用长期资格。出现在响应中,表示服务器希望客户端使用长期资格进行认证。
NONCE 属性是出现在请求和响应消息中的一段字符串。
UNKNOWN-ATTRIBUTES 属性出现在错误代码为 420 的的错误响应,表示服务器端无法理解的属性。
SOFTWARE 属性用于代理发送消息时所使用的软件的描述。
ALTERNATE-SERVER 属性表示 STUN 客户可以尝试的不同的 STUN 服务器地址。属性格式与 MAPPED-ADDRESS 相同。
2.4 STUN 示例
下面是 Wireshark 抓取的一对 STUN 绑定请求和响应。STUN 绑定请求,源地址 192.168.2.36:47798,目标地址 180.76.137.157:30001。
STUN 绑定响应,源地址 180.76.137.157:30001,目标地址 192.168.2.36:47798
其中 ICE-CONTROLLING、PRIORITY 属性是下面提到的 ICE 中扩充的属性。
3. ICE 简介
ICE 两端并不知道所处的网络的位置和 NAT 类型,通过 ICE 能够动态的发现最优的传输路径。如下图 L 和 R 是 ICE 代理,下面简称 L 和 R。L 和 R 有各自的传输地址,包括主机的网卡地址、NAT 上的外网地址、 TURN 服务地址。ICE 就是要从这些地址中,找到 L 和 R 的候选地址对,实现两端高效连通。
(图)ICE 部署图举例
ICE 两端可以通过信令服务器交换 SDP 信息。ICE 使用 STUN,TURN 等协议来建立会话。
3.1 收集候选地址
ICE 端收集本地地址。通过 STUN 服务收集 NAT 外网地址;通过 TURN 收集中继地址。
所以有四种候选地址:
如下图: 主机候选 X:x 服务器反射候选 X1′:x1′ 中继候选 Y:y 这里称主机候选地址是服务器候选地址的 BASE。
(图)ICE 端口
3.2 连通检测
L 收集了所有的候选地址后,按优先级从高到低排序,通过信令服务器发送 SDP offer 给 R。R 收到 offer 后,收集获选地址,并将自己候选地址放入 SDP answer 发送给 L。此时两端都有了对端的和本端的候选地址。然后配对,生成 candidate pair。为了确保 candidate pair 的有效性,两端都要做连通检测。根据 candidate pair,从本地 candidate 发送 STUN 请求到远端 candidate;接收端返回 STUN 响应给发送端。如下图:
(图)ICE 基本连通检测
两端都按照各自 checklist 分别进行检查。当 R 收到 L 的检测时,R 发送向 L 的检测被称为 Triggered 检测。
3.3 Candidates pair 排序
将连通性检查成功的 candidate pair 按优先级排序加入 check list。两端定期遍历这个 check list, 发送 STUN 请求给对端称为 Ordinary 检测。优先级的计算根据以下原则:每端给自己的 candidate 一个优先级数值。本端优先级和远端优先级结合,得到 candidate pair 的优先级优先级。
公式 priority = (2^24)*(type preference) + (2^8)*(local preference) + (2^0)*(256 – component ID)
再根据 candidate 的优先级计算 candidate pair 的优先级。
priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
G:controlling candidate 优先级。
D:controlled candidate 优先级。
3.4 提名 Candidates
ICE 中有两种角色, controlling 角色可以选取最终的 candidate pair;controlled 角色会等待 controlling 角色选取的 candidate pair。ICE 指定一个 ICE 代理为 controlling 角色,其他 ICE 代理为 controlled 角色。ICE 优先检测优先级高的 candidate pair。
Controlling 角色有两种提名方案:REGULAR 提名:当得到至少一对有效的 pair 的时候,Controlling 角色就会选择其中的一个 pair 作为候选,此次连通检测发送一个带 flag 的请求,告诉对端这个就是被选中的 pair。
(图)REGULAR 提名
AGGRESSIVE 提名:Controlling 角色会在每个 STUN 请求之中添加 flag 标志,最先成功的那个被选为媒体传输通道。
(图)AGGRESSIVE 提名
3.5 ICE 示例
下面是例子中,L 和 R 都是 full 模式 ICE 代理,采用 aggressive 提名,传输媒体为 RTP。full 模式为双方都要进行连通性检查,都要走一遍流程;lite 模式为,full 模式 ICE 一方进行连通性检查,lite 一方只需回应 response 消息。
(图)ICE 举例
便于理解,采用”主机类型-网络类型-序号”的格式表示传输的地址。地址有两个分量,分别是 IP 和 PORT。L,R,STUN,NAT 代表不同的主机类型;PUB 代表外网,PRV 代表内网;L 处在内网中,内网地址是 10.0.1.1,R 处在外网,外网地址是 192.0.2.1。L 和 R 都配置了 STUN 服务,地址是 192.0.2.2,端口是 3478。L 在 NAT 后面,NAT 外网地址是 192.0.2.3。序号表示不同的媒体类型,这里只有 RTP 所以序号为 1。”S=”表示 STUN 消息的发送地址、”D=” 表示 STUN 消息的接收地址。”MA=” 表示 STUN 绑定响应的中 mapped address。”USE-CAND” 表示带有”USE-CANDIDATE” STUN 消息。
L 收集本地候选地址,并发起 STUN 绑定请求给 STUN 服务器,L 得到 NAT-PUB-1 作为服务器反射候选地址。L 计算候选的优先级,主机候选地址 type preference 为 126;服务器反射候选地址 type preference 为 100。local preference为65535。component ID 为 1 套用公式 priority = (2^24)*(type preference) + (2^8)*(local preference) + (2^0)*(256 – component ID) 的主机候选地址的优先级为 2130706431,服务器反射候选地址的优先级为 1694498815。L 设置主机候选地址的 foundation 为 1,服务器反射候选地址 foundation 为 2。 L 将服务器反射候选地址作为 default 候选地址。对应的 offer sdp 为
替换地址后
因为 L 和 R 都采用的是 full-mode,这种情况下 ICE 协议规定发送 offer为controlling 端,L 为 controlling 端。L 和 R 生成 candidate pair,两端都有 2 个 candidate pair。L 会裁减掉包含服务映射候选地址,保留 candidate pair 为本端 $L_PRIV_1、远端 $R_PUB_1。
消息 9 表示 R 做连通检测,因为 R 是 controlled 角色,所以无需设置 USE-CANDIDATE。L 处于 NAT 后面,且没有向 R 发送过请求,所以此次连通检测会失败。
当 L 收到 answer sdp 后,开始连通检测(消息 10-13)。L 采用的是 aggressive 提名,所以每个请求都会有 USE-CANDIDATE。L 使用 candidate pair 为 $L_PRIV_1/$R_PUB_1 发起的连通检测成功后,L 创建一个新的 candidate pair,本端为 NAT-PUB-1(消息 13 中得到) 、远端为 R-PUB-1(消息 10 中得到),加入 valid list 中。这个连通检测中设置了 USE-CANDIDA 属性,该 candidate pair 为选中的候选。L 的 RTP 流在 valid list 中有选中的 candidate pair,所以 L 进入完成状态。
R 收到 L 的 STUN 绑定请求(消息 11)后,R 发起消息 11 对应的 Triggered 检测,其 candidate pair 的本端为 R-PUB-1、远端为 NAT-PUB-1。检测成功后,R 创建本端为 R-PUB-1、远端为 NAT-PUB-1 的 candidate pair,加入 valid list。因为消息 11 中包含了 USE-CANDIDATE,所以这个 candidate pair 就被选中为这个 RTP 流的传输通道。R 进入完成状态。
4. 总结
本文介绍了 NAT、STUN、ICE 等基本概念。STUN 部分介绍了 STUN 的消息结构、消息类型和消息属性,ICE 协议中 STUN 消息要遵循 STUN 协议。ICE 部分介绍了 ICE 代理之间是如何根据各自的网络地址建立连接的,步骤有收集候选地址、连通检测、Candidates pair 生成与排序、提名 Candidates。详细内容还需查看 ICE 协议 RTC5245 以及 WebRTC 的 P2P 部分的具体实现。