Transport Address:包含IP、port和传输协议。
Candidate:除了Transport Address 外还包括类型、优先级、foundation还有Base。
Base:Host candidate 关联一个 Server reflexive candidate 。
ICE实现NAT穿透的所要完成的核心处理包括候选地址信息的收集,之后对收集到的地址进行排序、配对,然后执行连通性检查。一个终端有多种候选传输地址(ip地址和端口用于特定传输协议)用以与其他终端进行通信, 它可能包含:
终端必须确定所有的候选的地址。这些地址包括本地网络接口的地址和由它派生的其他所有地址。本地网络地址包括本地网卡地址、VPN 网络地址、MIP 网络地址等。派生地址指的是通过本地地址向 STUN 服务器发送 STUN 请求获得的网络地址,这些地址分为两类,一类是通过 STUN 的绑定得到的地址,称为服务器反向候选地址(Server Reflexive Candidates)或服务器反向地址。另一类是通过 turn 服务中继得到的,称为中继候选地址(Relay Candidates)。
终端通过各个主机候选地址向 STUN server 发送一个 Bind Request 和向 Turn server 发送 Allocate 消息获取额外的服务器反向地址和服务器中继地址。下图描述的是通过 turn 服务同时发现 Server Reflexive Candidates 和Relay Candidates 的过程:
当终端发送 turn allocate 消息从IP为IP端口为 Port(主机候选地址)经过NAT,NAT将绑定一个IP为IP1和端口为Port1的地址,映射该候选服务器反向地址到主机候选地址。数据包从主机候选地址出来后,被NAT转换成服务器反向地址;最后发往目标地址。发往服务器反向候选地址的数据包,被NAT转换为主机候选地址,并转发到终端。
当终端和 STUN 服务器之间存在多重 NAT,那么 STUN 请求将会针对每一个NAT创建一个绑定,但是,只有最外部的服务器反向地址会被终端发现。如果终端不在任何NAT之后,那么,base候选传输地址将与服务器反向地址相同,服务器反向地址可以忽略。
当 turn allocate 到达 turn server 后,turn 服务器为该请求分配一个IP为IPy端口为 Porty 的地址,并加此信息到产生的应答消息中,同时把服务器反向地址IP1及 Port1 也加到应答消息中发回终端。turn server 提供中继,把将要加入会话的终端携带的信息转发给对端,这样终端不但有了自己的本地候选地址信息,还有对端的候选地址信息,为后续的建连提供了地址信息基础。
终端1收集到所有的候选地址后,就将它们按优先级高低进行排序,再通过信令信道发送给终端2。这些候选地址作为 SDP 请求的属性被传输。当终端2收到请求,它执行相同的地址收集过程,并且把它自己的候选地址作为响应消息发给请求者。这样,每个终端都将有一个完整的包含了双方候选地址的列表,然后准备执行连通性检查。连通性检查的基本原理是:
终端将本地地址集和远程地址集进行配对,如本地有2个地址,对端有3个地址,那么配成2*3=6对地址对。终端A选择本地的一个候选地址向终端B的的服务器反向地址发送一个 STUN 请求,并收到了 Stun 的 response,称该地址对是可接收的。当终端A地址对中的本地地址收到地址对中远程地址的一个STUN请求,并成功地响应,则称该地址对为可发送的。若一个地址对是可接收的,同时又是可发送的,则称该地址对是有效的,即这个地址对通过连通性检查。以上描述的是一个地址对的筛选过程,多个地址对同时进行多次这样的往返交互终端A和终端B就可以筛查出所有的有效地址对。
由于收集候选地址时,收集的是所有的候选地址,为了能够更快更好的找到能够正常工作的候选地址对,对所有组合进行排序是势在必行的。在此说明进行排序的两个基本原则:
每个候选者都和一个叫 FOUNDATION 属性相关,两个候选者的 foundation 是“相同”的--是相同的主机候选者,且用了相同的 stun 服务协议。否则它们的 foundation 是不一样的 。候选地址对也有 foundation,只是和两个候选地址相关联。初始阶段,仅有唯一 foundation 的地址对进行检测,其他的对候选地址对都是处于“冻结”状态。当可连接检测成功后,在解冻和当前 foundation 一样的候选地址对,这样可以避免重复检查表面上看起来更可能连接成功,但实际上会失败的候选地址对。
Foundations计算,两个候选地址要有相同的 foundation ID:
o 一样的类型(主机、服务器映射、中继、对端映射)。
o Base 有相同的IP(端口可以不一样)。
o 用的相同的传输协议。
o 服务器反向和中继地址,当 stun 或则 turn server 获取到它的IP是一样的。
否则就要用不同的ID。
候选地址 priority 的计算公式:
priority = (2^24)*(type preference) + (2^8)*(local preference) + (2^0)*(256 - component ID)
类型首选项必须是0到126(含0和126)之间的整数,0为最低优先级,126为最大优先级。
本地首选项必须是0到65535(含0和65535)之间的整数。0为最低优先级,65535为最高优先级。
The component ID必须是0到256(含0和256)之间的整数。
对于ICE流程中的每个会话,每个终端都扮演一个角色。有两个角色--控制和被控制。被控制终端负责最后一对候选配对的选择,用于通信。这意味着提名候选地址对,用于每个媒体流。控制终端被告知哪些候选对用于每个媒体流。决定角色的规则如下:
在一些特殊的流程下,可能会导致会话的双方都认为自己是控制者或者被控制者。为了解决角色冲突,在 connectivity check 的阶段,发送的 bind request 要求携带 role 相关的 STUN 属性,ICE-CONTROLLED 或者是 ICE-CONTROLLING,这两个属性都会携带一个 Tie breaker(取值0- 2^64 - 1)这样的字段,其中包含 一个本机产生的随机值。收到该 bind request 的一方会检查这两个字段,如果和当前本机的 role 冲突,则检查本机的 tie breaker 值和消息中携带的 tie breaker 值进行判定本机合适的role。判定的方法为 Tie breaker 值大的一方为 controlling。如果判定本端变更角色,就会直接修改;如果判定对端变更角色,则对此 bind request 发送487错误响应,收到此错误响应的一端改变角色就可以了。(这里就用到了STUN 请求新增的属性 0x8029 ICE-CONTROLLED 0x802A ICE-CONTROLLING 以及STUN错误反馈487 Role Conflict: 终端指定的角色和 server 指定的角色冲突)。
一旦候选地址对确定好后,就要给它们计算优先级。G为控制端地址的优先级,D为被控制端的地址优先级。候选地址对的优先级计算公式:
pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
一旦分配了候选对优先级,就会候选对按优先级降序配对。如果两对具有相同的优先级,它们之间的顺序是任意的。
候选地址对状态机
此流程只有全量实现才有,又分为平常检查和触发检查,两者都是由定时驱动。终端持有一个先进先出队列,称为触发队列,里面放着下次有机会触发的candidate pair。当定时器触发后,终端从触发队列里拿出最上面的candidate pair,执行连接检查,设置该candidate pair状态为In Progress。如果触发队列为空,就执行平常检查。一旦终端完成组织好candidate pairs后,就给个这些活动的检查列表设置定时器,有N个就设置N个定时器,时间间隔为Ta*N秒。当定时器触发后但没有触发检查可发送,终端必须切换到平常检查流程如下:
终端可能为媒体流重启ICE,那么原来的又变回到了新的状态。和新的会话不同的点就是在重启的过程中,媒体依然可以从前面有效的地址对发送。一个终端必须重启ICE,当列情况出现的时候:
我们都知道ICE协议只负责收集通信地址对它们进行排序,要让通信双方都知道自己处在的网络环境和通信地址,还要通过服务器中继,多媒体通信一般都通过SDP[ RFC 4566]协议进行封装。下面是用于ICE协议的attribute extensions。
a=ice-pwd:asd88fgpdd777uzjYhagZg a=ice-ufrag:8hhY
sdp中传递ice-ufrag和ice-pwd用于stun信息的安全有效校验。candidate属性携带有效的通信地址信息,依次是从1开始递增的component-id、通信协议(tcp或udp)、foundation、IP、port、type、relate_address、network-id和network-cost。
STUN:https://datatracker.ietf.org/doc/rfc5389/
SDP:https://datatracker.ietf.org/doc/rfc4566/
ICE:https://datatracker.ietf.org/doc/rfc8445/