跳到主要内容

2. ICE 概览

在典型的 ICE 部署中, 有两个希望通信的端点 (ICE agent). 注意, ICE 并不用于信令协议的 NAT 穿越, 后者假定由其他机制提供. ICE 假定 agent 能够彼此建立信令连接.

最初, agent 并不了解自身所在的拓扑. 特别是, agent 可能位于 NAT (或多层 NAT) 之后, 也可能不是. ICE 允许 agent 发现关于自身拓扑的足够信息, 从而有可能找到一条或多条可用于建立数据会话的路径.

图 1 展示了一个典型的 ICE 部署. Agent 标记为 L 和 R. L 与 R 都位于各自的 NAT 之后, 尽管它们可能并不知道这一点. NAT 的类型及其属性也是未知的. L 和 R 能够参与 candidate 交换过程, 其目的是在 L 和 R 之间建立数据会话. 通常, 该交换会通过信令服务器进行 (例如 SIP proxy).

除 agent, 信令服务器和 NAT 之外, ICE 通常还会与网络中的 STUN 或 TURN 服务器配合使用. 每个 agent 可以拥有自己的 STUN 或 TURN 服务器, 也可以使用同一个服务器.

                           +---------+
+--------+ |Signaling| +--------+
| STUN | |Server | | STUN |
| Server | +---------+ | Server |
+--------+ / \ +--------+
/ \
/ \
/ <- Signaling -> \
/ \
+--------+ +--------+
| NAT | | NAT |
+--------+ +--------+
/ \
/ \
+-------+ +-------+
| Agent | | Agent |
| L | | R |
+-------+ +-------+

图 1: ICE 部署场景

ICE 背后的基本思想如下: 每个 agent 都有若干个可用于与另一个 agent 通信的候选传输地址 (针对特定传输协议的 IP 地址和端口组合, 在本规范中始终为 UDP). 这些地址可能包括:

  • 直接连接的网络接口上的传输地址
  • NAT 公共侧的转换后传输地址 (一个 "server-reflexive" 地址)
  • 从 TURN 服务器分配的传输地址 (一个 "relayed address")

理论上, L 的任意候选传输地址都可以用于与 R 的任意候选传输地址通信. 但在实践中, 许多组合无法工作. 例如, 如果 L 和 R 都位于 NAT 之后, 它们直接连接接口的地址不太可能直接通信 (这正是需要 ICE 的原因!). ICE 的目的就是发现哪些地址对可以工作. ICE 的做法是按系统化方式尝试所有可能的 pair (以精心排序的顺序), 直到找到一个或多个可工作的 pair.

2.1. 收集 Candidate

为了执行 ICE, ICE agent 会识别并收集一个或多个地址 candidate. Candidate 具有传输地址 -- 针对特定传输协议的 IP 地址和端口组合 (此处仅规定 UDP). Candidate 有不同类型; 有些来自物理或逻辑网络接口, 另一些可通过 STUN 和 TURN 发现.

第一类 candidate 是传输地址直接从本地接口获得的 candidate. 这样的 candidate 称为 "host candidate". 本地接口可以是 Ethernet 或 Wi-Fi, 也可以是通过隧道机制获得的接口, 例如 Virtual Private Network (VPN) 或 Mobile IP (MIP). 在所有情况下, 这样的网络接口对 agent 来说都表现为一个本地接口, 可以从中分配端口 (从而分配 candidate).

接下来, agent 使用 STUN 或 TURN 获得额外 candidate. 这些 candidate 有两种: NAT 公共侧的转换后地址 (server-reflexive candidate), 以及 TURN 服务器上的地址 (relayed candidate). 使用 TURN 服务器时, 这两种 candidate 都从 TURN 服务器获得. 如果只使用 STUN 服务器, 则只能从中获得 server-reflexive candidate. 这些 candidate 与 host candidate 的关系如图 2 所示. 在该图中, 两种 candidate 都使用 TURN 发现. 图中的记号 X:x 表示 IP 地址 X 和 UDP 端口 x.

                  To Internet

|
|
| /------------ Relayed
Y:y | / Address
+--------+
| |
| TURN |
| Server |
| |
+--------+
|
|
| /------------ Server
X1':x1'|/ Reflexive
+------------+ Address
| NAT |
+------------+
|
| /------------ Local
X:x |/ Address
+--------+
| |
| Agent |
| |
+--------+

图 2: Candidate 关系

当 agent 从 IP 地址和端口 X:x 发送 TURN Allocate request 时, NAT (假设存在 NAT) 将创建绑定 X1':x1', 将该 server-reflexive candidate 映射到 host candidate X:x. 从 host candidate 发出的出站分组会被 NAT 转换为 server-reflexive candidate. 发送到 server-reflexive candidate 的入站分组会被 NAT 转换到 host candidate, 并转发给 agent. 与给定 server-reflexive candidate 关联的 host candidate 称为 "base".

注意: "Base" 指 agent 针对特定 candidate 发送数据时所使用的源地址. 因此, 作为一种退化情况, host candidate 也有 base, 但它与 host candidate 本身相同.

当 agent 与 TURN 服务器之间存在多个 NAT 时, TURN request 会在每个 NAT 上创建绑定, 但 agent 只能发现最外层的 server-reflexive candidate (最靠近 TURN 服务器的那个). 如果 agent 不在 NAT 之后, 则 base candidate 将与 server-reflexive candidate 相同, 该 server-reflexive candidate 是冗余的并会被消除.

随后 Allocate request 到达 TURN 服务器. TURN 服务器从其本地 IP 地址 Y 分配端口 y, 并生成 Allocate response, 将该 relayed candidate 告知 agent. TURN 服务器还通过把 Allocate request 的源传输地址复制到 Allocate response 中, 将 server-reflexive candidate X1':x1' 告知 agent. TURN 服务器充当分组中继, 在 L 和 R 之间转发流量. 为了向 L 发送流量, R 将流量发送到 TURN 服务器的 Y:y, TURN 服务器再将其转发到 X1':x1', 该流量穿过 NAT 后被映射到 X:x 并递送给 L.

当只使用 STUN 服务器时, agent 向其 STUN 服务器发送 STUN Binding request [RFC5389]. STUN 服务器通过把 Binding request 的源传输地址复制到 Binding response 中, 将 server-reflexive candidate X1':x1' 告知 agent.

2.2. 连接性检查

一旦 L 收集完自己的所有 candidate, 它会按 priority 从高到低对它们排序, 并通过信令信道发送给 R. 当 R 收到来自 L 的 candidate 后, 它执行相同的收集过程, 并以自己的 candidate 列表作出响应. 在该过程结束时, 每个 ICE agent 都拥有自己的 candidate 和对等方 candidate 的完整列表. 它将这些 candidate 配对, 形成 candidate pair. 为了确定哪些 pair 可以工作, 每个 agent 会调度一系列 connectivity check. 每个检查都是一次 STUN request/response transaction, 客户端会针对特定 candidate pair 执行该 transaction, 即从 local candidate 向 remote candidate 发送 STUN request.

Connectivity check 的基本原则很简单:

  1. 按 priority 顺序排序 candidate pair.
  2. 按 priority 顺序在每个 candidate pair 上发送检查.
  3. 确认从另一个 agent 收到的检查.

With both agents performing a check on a candidate pair, the result is a 4-way handshake:

              L                        R
- -
STUN request -> \ L's
<- STUN response / check

<- STUN request \ R's
STUN response -> / check

图 3: 基本连接性检查

需要注意, STUN request 的源和目的 IP 地址与端口, 与将用于数据 (例如 RTP, RTCP, 或其他协议) 的地址和端口完全相同. 因此, agent 使用分组内容而不是接收分组的端口来解复用 STUN 和数据.

由于 connectivity check 使用 STUN Binding request, STUN Binding response 将包含 agent 在其与对等方之间任意 NAT 公共侧的转换后传输地址. 如果该传输地址不同于 agent 已获知的其他 candidate 地址, 它就表示一个新的 candidate (peer-reflexive candidate), 随后 ICE 会像测试任何其他 candidate 一样测试它.

由于上述算法搜索所有 candidate pair, 如果存在可工作的 pair, 无论 candidate 的尝试顺序如何, 该算法最终都会找到它. 为了更快地产生 (也更好地产生) 结果, candidate 会按指定顺序排序. 由此得到的已排序 candidate pair 列表称为 "checklist".

Agent 通过周期性地为 checklist 中下一个 candidate pair 发送 STUN request 来推进 checklist. 这些检查称为 "ordinary check". 当 STUN transaction 成功时, 一个或多个 candidate pair 会成为所谓的 "valid pair", 并被加入名为 "valid list" 的 candidate-pair 列表.

作为优化, R 一旦收到 L 的检查消息, 就会调度一条 connectivity-check 消息, 在同一个 candidate pair 上发送给 L. 这称为 "triggered check", 它会加速寻找 valid pair 的过程.

在该握手结束时, L 和 R 都知道它们可以在两个方向上端到端地发送 (并接收) 消息.

一般而言, priority 算法的设计目标是让相似类型的 candidate 获得相似的 priority, 从而使更直接的路由 (即没有数据中继或 NAT 的路由) 优先于间接路由 (带有数据中继或 NAT 的路由). 不过, 在这些指导原则内, agent 对如何调优其算法拥有相当大的自由度.

一个数据流可能由多个 component 组成 (数据流中需要自己的一组 candidate 的部分, 例如 RTP 和 RTCP).

2.3. Nominating Candidate Pair 并结束 ICE

ICE 将其中一个 ICE agent 指派为 controlling agent 角色, 另一个指派为 controlled agent 角色. 对于数据流的每个 component, controlling agent 从 valid list 中 nominate 一个 valid pair 用于数据. Nomination 的确切时机基于本地策略.

进行 nomination 时, controlling agent 会让检查继续进行, 直到为数据流的每个 component 至少找到一个 valid pair, 然后它选择一个 valid pair, 并在该 pair 上发送 STUN request, 使用一个属性向 controlled peer 指示该 pair 已被 nominated. 如图 4 所示.

         L                        R
- -
STUN request -> \ L's
<- STUN response / check

<- STUN request \ R's
STUN response -> / check

STUN request + attribute -> \ L's
<- STUN response / check

图 4: Nomination

一旦 controlled agent 收到带有该属性的 STUN request, 它将检查同一个 pair (除非该检查已经完成). 如果上述 transaction 成功, agent 将为这些 pair 设置 nominated 标志, 并取消该数据流 component 的任何后续检查. 一旦 agent 为数据流的每个 component 都设置了 nominated 标志, 这些 pair 就成为 selected pair. 此后, 只有 selected pair 会用于发送和接收与该数据流关联的数据.

2.4. ICE Restart

ICE 结束后, 任一 ICE agent 都可以随时对一个或全部数据流重启 ICE. 这是通过发送指示 restart 的更新后 candidate 信息完成的.

2.5. Lite 实现

某些 ICE agent 将始终连接到公共 Internet, 并拥有可从任意通信对端接收分组的公共 IP 地址. 为了让这些设备更容易支持 ICE, ICE 定义了一种称为 "lite" 的特殊实现类型 (相对于普通的完整实现). Lite agent 只使用 host candidate, 不生成 connectivity check, 也不运行状态机, 但它们需要能够响应 connectivity check.