WebRTC ICE 候选池的时序与隐私
区分 ICE 预收集、候选交付和重启阶段;页面事件缺失不能证明此前没有网络活动。
BotBrowser Team
ICE 候选池会改变部分工作的发生时点,但不会把候选收集、页面事件、本地描述和后续重启变成同一个观察。事件日志安静,不能证明此前没有服务器活动。BotBrowser 文档说明 WebRTC 地址策略,但不保证控制候选池大小或撤销既有网络活动。
摘要
- 非零
iceCandidatePoolSize会请求预收集,但不证明候选池已经就绪。 setLocalDescription()是交付候选的阶段,不是网络首次活动的时间戳。- 初始候选池用于首次交换;
restartIce()请求后续协商,不会倒回或撤销既有活动。
观看视频
这段视频为英语讲解,沿文章同一问题展开:一条 WebRTC 记录包含多个时点,因此配置的候选池大小、页面候选事件、本地描述与服务器日志不能被替换成一个“ICE 开始”的时刻。视频在 Ubuntu Chrome 的受控本地夹具上运行:先以非零候选池大小建立 peer connection,之后修改该值返回 InvalidModificationError;另一个探针在固定窗口内统计交付给页面的候选事件并读取收集状态。该计数仍是页面观察,不是服务器端记录;视频不声称测量了任何真实站点,也不声称 BotBrowser 控制候选池大小。音频为英语。
目录
BotBrowser 文档说明受控会话中的 WebRTC 地址策略,但不保证控制候选池大小或撤销此前活动。WebRTC 防泄漏指南介绍候选类型与地址策略,mDNS 候选指南说明主机候选的表示。此处聚焦 ICE 各阶段何时可被观察。
时序会改变隐私问题
WebRTC 记录可能包含多个时点:创建 peer connection、可选的候选预收集、本地描述调用、交付给页面的候选事件、offer/answer 交换,以及之后的 ICE 重启。把它们统称为一个“ICE 开始”时刻,会掩盖解读隐私报告时需要的区别。WebRTC 推荐标准把 iceCandidatePoolSize 定义为预取候选池的大小,默认值为零。非零值会请求一个池,但不是记录页面实际收到哪些候选的时间戳日志。
| API 边界 | 记录 | 不应推断 |
|---|---|---|
RTCPeerConnection(configuration) | 请求的 iceCandidatePoolSize 与采集开始时间 | 预收集已经完成 |
setLocalDescription() | 调用时间与交付的候选事件 | 网络恰在调用时开始活动 |
restartIce() | 重启请求与后续协商 | 初始候选池已经恢复 |
页面事件描述的是应用可见的交付点,不会标记此前可能发生的每一次 DNS、STUN 或 TURN 操作。反过来也一样:应用日志里没有候选事件,单凭这一点无法证明此前没有收集或服务器侧活动。采集可能开始得太晚,应用可能过滤了自身日志,观察也可能来自其他层面。这些情况要求明确证据边界,而不是补造缺失事件。
在经授权的兼容性或隐私检查中,应把以下四类观察分开:
- 配置的候选池大小,以及采集该配置的时间。
- 独立且获准的服务器侧记录观察到的网络活动(如果存在)。
- 交付给应用的候选事件,并注明采集窗口。
- 应用记录的本地描述及 offer/answer 阶段。
这些记录回答不同问题。配置说明应用请求了什么;页面事件说明交付给脚本什么;本地描述说明应用传给协商 API 什么;服务器日志说明特定服务器观察到什么。它们都不能单独证明地址策略先于所有相关网络操作生效。比较时间戳之前,应先确认每个来源使用的时钟和观察位置。
候选池也不是页面可见的候选数量。配置大小不能证明实现已在后续调用前收集到同样数量的可用候选。RFC 9429 明确考虑了候选池耗尽的情况:可能因为需要更多 ICE 组件,也可能因为预收集时间不足。此时剩余候选会按常规方式收集。因此,配置值、页面事件数量和本地描述中的候选不是可互换的测量结果。
这种区分可以避免两种过度断言。“本地描述前没有记录候选事件”描述的是特定页面日志及其覆盖范围。“本地描述前没有网络活动”则是更广的说法,需要来自相关网络观察位置的证据。非零配置允许预收集,但本身不能证明某个服务器在某个具体时间收到请求。应记录各来源实际显示的内容,其余部分保留为未知。
候选池的生命周期有限
候选池在协商中承担的作用有明确边界,并不是每次后续交换都能复用的缓存。RFC 9429 第 3.5.4 节描述了这个顺序:setLocalDescription 开始首次 offer/answer 交换所需的收集时,实现应先检查池中是否已有候选。可用候选应通过候选事件交给应用。如果池因组件需求更多或收集时间不足而耗尽,其余候选会按常规方式收集。RFC 说明这种候选池行为只适用于首次 offer/answer 交换;之后池会清空并停止使用。
RFC 使用“SHOULD”描述可用池候选的交付。应保留这一规范语气:它是标准建议,不代表某个池一定在特定时刻前完成。该节还指出,保持预收集候选可用会消耗已配置 STUN 或 TURN 服务器的资源。这是 RFC 提到的资源影响,不是测得的流量,也不是对某个浏览器版本保留时长的承诺。
配置时机也有限制。WebRTC set-configuration 算法会拒绝在已调用 setLocalDescription 后更改 iceCandidatePoolSize。对此类受限更改,方法会报告 InvalidModificationError,但异常名称本身不能诊断原因:其他配置变更也可能触发它。有用的故障记录应包含变更前后的配置、调用顺序和异常上下文。缺少这些信息时,把错误归因于候选池大小只是一种猜测。
| 阶段 | 候选池的含义 | 应用可见证据 | 记录无法证明什么 |
|---|---|---|---|
| 以大小 0 创建 | 此设置没有请求预收集 | 初始配置快照 | 后续不会发生收集 |
| 以非零大小创建 | 允许在准备候选时预收集 | 请求值;如有则另记服务器侧观察 | 候选池已填满或页面事件已发生 |
| 本地描述之前 | 可按 API 规则选择或更改大小 | 配置快照与调用顺序 | 请求的候选池已就绪 |
| 首次本地描述收集 | 应交付可用池候选;不足部分常规收集 | 候选事件和本地描述生命周期 | 仅凭页面事件还原此前完整网络时间线 |
| 首次交换之后 | 初始池已清空且不再使用 | 协商历史 | 后续重启恢复了预收集池 |
| 后续 ICE 重启 | 通过协商请求新的 ICE generation | 重启调用与后续信令 | 此前活动已被抹除 |
该矩阵用于解读自有应用的记录,记录阶段标签和大致顺序,而不是候选内容。要区分“请求了配置”和“操作已经完成”。如果没有独立的网络观察,应明确说明;不要把页面事件流当作服务器活动的替代记录。
ICE 重启是后续协商动作,不是回退按钮。restartIce() 会请求在后续协商中执行 ICE 重启。它不会追溯删除之前的事件、清除服务器日志,也不会在首次 offer/answer 交换消耗初始池之后将其恢复。如果报告跨越多个 generation,应标出每条记录所属阶段。否则,重启后观察到的候选可能被错误关联到初始交换的证据。
分开记录不同观察层
证据集合应说明每项观察来自哪个层面,以及覆盖哪个时间区间。配置快照记录应用请求的值;页面事件记录交付给脚本的内容;本地描述记录应用在 API 边界提交的内容;系统所有者获准查看的服务器日志,则记录特定服务器观察到的内容。时钟、保留时间和覆盖范围可能不同。忽略这些差异的比较,可能显得比原始记录更确定。
| 观察 | 可以支持 | 单独不能支持 |
|---|---|---|
协商前 iceCandidatePoolSize: 0 | 该设置没有请求预收集 | 后续绝不会收集候选 |
| 已配置非零候选池 | 应用请求了预收集 | 收集已完成或候选池仍可用 |
| 候选事件时间戳 | 页面在记录时收到该事件 | 首次网络活动发生于此时 |
setLocalDescription 调用 | 应用进入本地描述阶段 | 所有候选此前都已收集 |
restartIce() 调用 | 应用请求后续协商执行 ICE 重启 | 初始池已恢复或历史已抹除 |
| 部分记录中没有事件 | 该记录不含此事件 | 采集窗口之外没有活动 |
实用记录应保留采集开始时间、按序排列的 API 阶段标记、浏览器版本,以及日志是否完整。目的是解释获准的诊断,不是收集超出需要的数据。上述生命周期区别不需要原始候选字符串、地址、SDP 内容、服务器凭据或逐用户标识符。如果其他获准事件确需敏感数据,应按该系统的访问与保留政策处理,而不要复制进通用报告。
没有独立网络观察时,不要把页面事件时间与假定的 STUN 请求时间拼接。这可以作为待验证假设,但不是观察到的事实。导出流程删除字段时也要谨慎:字段缺失可能是导出器省略,而非浏览器从未生成。应保留每条记录的来源层、采集区间、转换步骤和最终保留位置。
为了可重复地比较两次运行,应使用相同的观察定义。如果一次从连接创建起记录 API 调用,另一次从首个候选回调才开始,两条表面上的时间线并未对齐。比较前先整理一张小表,列出事件定义、时钟来源、采集起点和缺失数据规则。这是检查自有测试系统的方法,不需要公开候选值或探测第三方端点。
排查不完整的时间线
报告把某个地址称为“较晚”时,先定义“较晚”指什么:页面代码看到的首个候选事件、本地描述中的首个候选、导出过程保留的首个事件,还是获准服务器日志中的首次观察。这些时间戳不能互换。先检查采集是否早于连接创建,以及是否记录了初始配置;再检查尝试更改候选池大小时,是否已经调用 setLocalDescription。
| 现象 | 首先检查 | 避免推断 |
|---|---|---|
| 本地描述前没有候选事件 | 是否请求收集,以及采集是否覆盖更早活动 | 没有预收集或服务器联系 |
| 本地描述后事件少于预期 | 候选池是否有足够容量或准备时间;常规收集可能继续 | 连接仅因池较小而失败 |
配置时出现 InvalidModificationError | 哪个字段变化,以及是否已调用 setLocalDescription | 必然是候选池大小被拒绝 |
restartIce() 后出现事件 | 重启调用、重新协商顺序和采集完整性 | 旧 generation 或其活动已消失 |
| 服务器与页面记录不一致 | 时钟来源、采集边界、过滤步骤和观察位置 | 一份记录代表另一层 |
按此顺序检查,可让诊断紧贴证据。先看调用顺序和采集范围;比较本地描述前请求的值与实际记录的值;区分配置异常和事件交付。只有在两类记录都是获准采集时,再比较页面与服务器记录。若有项目缺失,应记录缺口,而不是填入假定的默认值或虚构事件时间。
拿不到初始配置或采集区间时,应把时间线标为不完整。不要从最终候选列表还原初始意图。最终列表是保留的结果,不是产生结果的完整过程。若导出缺少来源信息,应说明转换历史未知。这样的报告结论范围较窄,但其他工程师能看清缺少哪个事实会改变解读。
比较两次运行时,差异可能来自采集覆盖、网络响应时间、应用调用顺序或服务器日志边界,而不只是候选池大小。应把这些条件一并记录。除非证据隔离了池大小变化,且适用来源支持提出的机制,否则不要断言该变化导致差异。引用的标准说明 API 行为与生命周期,不提供未执行的浏览器配置测量结果。
配置更改被拒绝时也可采用同一方法。去除秘密后保存变更前后配置,记录是否已经调用本地描述,并保留异常类型和发生阶段。InvalidModificationError 说明该更改未按 API 规则被接受;没有周围记录时,异常本身不能指出责任字段。
BotBrowser 策略边界
BotBrowser 的 WebRTC 文档说明了受控会话的地址策略选择与配置,是这些产品控制项的来源。候选池默认值、首次交换生命周期和本地描述处理时序来自 WebRTC 推荐标准与 JSEP。产品公开文档没有承诺专用的候选池大小控制、所有候选都会被预收集,或后续更改策略会消除此前的服务器侧活动。
受控比较时,应分别记录所选产品策略和 API 阶段证据,并为每项主张注明来源。不要把配置文档当成网络侧证明,也不要把页面事件缺失当成主机从未联系配置服务的证据。WebRTC 防泄漏指南仍是地址策略设置的操作参考;这里的生命周期表补充时序和证据范围的解读。没有声称未执行的浏览器版本、网络路径或部署结果。
常见问题
非零候选池是否保证协商前候选已就绪?
不保证。它只请求预收集;RFC 9429 说明候选池可能耗尽,也可能因时间不足而未完成。开始收集时应交付可用候选,但配置值不是完成回执。
本地描述之后修改候选池大小能修正先前的记录吗?
不能。W3C 的 set-configuration 算法限制在调用 setLocalDescription() 后修改该值。之后的配置不会重写之前的页面日志或服务器观察。
restartIce() 会恢复初始候选池吗?
不会。它请求在后续协商中进行 ICE 重启,有自己的证据时间窗,也不会抹去先前网络活动。
来源
- W3C WebRTC 推荐标准:RTCConfiguration 与 set-configuration 算法
- RFC 9429,第 3.5.4 节:ICE Candidate Pool
- MDN:RTCPeerConnection.setConfiguration()
- MDN:RTCPeerConnection.restartIce()
- BotBrowser WebRTC 防泄漏文档
实践结论
将配置、页面事件和服务器观察保留为不同证据。记录采集窗口与调用顺序;缺少其中任何一项时,应标记时间线不完整,而不是推断此前没有网络活动。
本文属于指南: 什么是 WebRTC 泄露?IP 泄露防护与浏览器隐私