技术实践丨WebRTC P2P 连接技术之 STUN 和 ICE 协议解密
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的有效性,两端都要做连通检测。根据 candidatepair,从本地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 – componentID)
再根据 candidate 的优先级计算 candidate pair 的优先级。priority = 2^32*MIN(G,D) +2*MAX(G,D) + (G>D?1:0)G:controlling candidate 优先级 D:controlledcandidate 优先级3.4 提名 CandidatesICE 中有两种角色, controlling 角色可以选取最终的 candidatepair;controlled 角色会等待 controlling 角色选取的 candidate pair。ICE 指定一个 ICE 代理为 controlling 角色,其他 ICE 代理为 controlled 角色。ICE 优先检测优先级高的 candidatepair。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 计算候选的优先级,主机候选地址 typepreference 为 126;服务器反射候选地址 type preference 为 100。local preference 为 65535。component ID 为 1 套用公式 priority = (2^24)*(type preference) + (2^8)*(localpreference) + (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 检测,其 candidatepair 的本端为 R-PUB-1、远端为 NAT-PUB-1。检测成功后,R 创建本端为 R-PUB-1、远端为 NAT-PUB-1 的 candidate pair,加入 validlist。因为消息 11 中包含了 USE-CANDIDATE,所以这个 candidate pair就被选中为这个 RTP 流的传输通道。R 进入完成状态。
4. 总结
本文介绍了 NAT、STUN、ICE 等基本概念。STUN 部分介绍了 STUN 的消息结构、消息类型和消息属性,ICE 协议中 STUN 消息要遵循 STUN 协议。ICE 部分介绍了 ICE 代理之间是如何根据各自的网络地址建立连接的,步骤有收集候选地址、连通检测、Candidates pair 生成与排序、提名 Candidates。详细内容还需查看 ICE 协议 rfc5245 以及 webrtc 的 p2p 部分的具体实现。