CreepJS WebRTC 报告:追踪字段来源
将 CreepJS WebRTC 报告字段对应到固定 API、SDP 处理与部分结果分支,不把它们解读为身份。
BotBrowser Team
CreepJS 的 WebRTC 面板是由多种结果组成的报告,不是数据包捕获,也不是浏览器全部能力的清单。固定在提交 10aa6724cd33a1015db1574211890518cd04f0cc 的收集器组合了设备类型枚举、六项媒体解码查询、一份 offer 的 SDP,以及选定的 ICE 事件数据。BotBrowser 文档说明了授权工作流中的候选、代理和 ICE 服务器控制;这些控制不能证明 CreepJS 报告准确,也不能识别物理接口或个人。
TL;DR
- 收集器把
enumerateDevices()的记录映射为设备类型并排序;这个 helper 不读取设备标签。 - 它使用六种固定音视频配置调用
decodingInfo(),保留受支持的记录,并将每条结果缩减为流畅度和能效标签。 - SDP 扩展的数字 ID 会先被移除,剩余字符串再去重和排序。因此面板列表不是原始 SDP。
- 第一条非空事件字符串提供
iceCandidate和foundation;后续事件可能补全结果。 - 不存在
RTCPeerConnection时会立即返回null。另一个三秒计时器可能返回部分字段,所以null、部分结果和省略字段不是同一种结论。
目录
收集器边界与固定版本
源码链接固定到一个公开的 CreepJS 版本。以下说明针对该版本的代码,不代表运行了当前 CreepJS 网站、BotBrowser 或原版 Chrome。讨论范围比 WebRTC 配置指南更窄:将可见字段对应到生成它们的操作,并指出实现在哪里丢弃、汇总或因超时而停止处理数据。
相邻的 WebRTC codec 对比讨论 codec API 和兼容性;ICE 候选池与隐私时序讨论预收集及事件时序;WebRTC 泄漏防护则涉及代理和 ICE 配置。这里聚焦单个收集器如何构造报告,不涉及这些操作指南中的配置步骤。
字段来源映射
| 报告值或字段组 | 固定源码中的操作 | 转换或缺失状态 | 可以据此说明什么 |
|---|---|---|---|
mediaDevices | navigator.mediaDevices.enumerateDevices() | 每条记录映射为 kind 后排序;API 不可用时返回 null | 此 helper 收到的设备类型,不包括标签、物理设备或用户身份 |
codecsSdp | createOffer() 的 SDP 与媒体格式 ID | 汇总 MIME 类型、时钟频率、通道、格式参数和反馈 | 选定 offer 能力的解析视图,不是全部 SDP 行,也不证明播放成功 |
extensions | SDP a=extmap 行 | 移除数字 ID、去重并排序 | 该解析器留下的扩展字符串,不包括原始顺序或 ID |
iceCandidate 与 foundation | 第一条非空 icecandidate 事件 | 只解析一次 foundation;成功地址分支映射已知值,超时分支保留原值 | 第一条事件字符串及依赖分支的展示值,不是接口清单 |
address | connection.localDescription.sdp | 先解析 connection 行,再尝试形似候选的子串;排除 0.0.0.0 | 解析器返回的一个地址,不证明其他地址不存在 |
foundationProp 与 stunConnection | 到达有效地址分支的事件 | 与解析出的地址一同返回;它们可能来自保存 iceCandidate 之后的事件 | 完成该分支的事件字段,不一定来自 iceCandidate 对应的事件 |
| 面板哈希 | codecsSdp、extensions、foundation、foundationProp、address 和 mediaDevices | 只汇总选定子集;iceCandidate 和 stunConnection 不在输入对象中 | 该实现所选输入的相等性,不代表完整报告相同 |
这张表描述源码,不是测量结果。RTCPeerConnection API 和 W3C WebRTC 规范定义浏览器接口;表中的字段名和处理方式则属于这个固定版本的收集器。字段名称即使对应熟悉的 API,也可能是对原始输出有损的投影。
SDP 派生字段省略了什么
扩展 helper 提取 extmap 行,移除扩展的数字 ID,然后用 Set 去重并按字典序排序。因此,数字 ID 或原始顺序不同的两份 SDP offer,只要剩余字符串相同,就可能得到相同的面板列表。不能从这个列表还原原始行。
列表有助于回答“解析后留下了哪些扩展名称”,但无法回答“分配了哪个数字 ID”或“offer 按什么顺序列出扩展”。读者不应从排序后的字符串推断已删除的信息。
codec 输出也经过汇总。收集器从音视频媒体区段读取格式 ID,在 SDP 中查找匹配的 rtpmap、fmtp 和 rtcp-fb 行,再按 MIME 类型合并条目。结果只保留时钟频率、通道、格式参数和反馈支持等选定字段。这能帮助理解面板选择了哪些内容,但它不是未经修改的 SDP 存档,也不能证明远端对等方接受了每种 codec。
解析顺序会影响结果。代码先找出媒体区段中的格式 ID,再在整份 offer SDP 中查找包含各 ID 的行。rtpmap 行提供 MIME 类型和时钟频率;对于音频,第三个斜杠分隔部分用于表示通道数,如果它缺失或计算结果为零,默认值是一。rtcp-fb 行会加入反馈列表。其他匹配行可提供以分号分隔的格式参数。这些是从匹配行汇集而来的展示字段,不是标准符合性验证器。
收集器还限制重复的 codec 细节。RTX 条目走单独逻辑,计数器会阻止同一路径再添加第二个 RTX 描述。MIME 类型相同的条目会合并:时钟频率合并成唯一值;存在格式参数或反馈列表时,也会合并这些项目。如果媒体区段没有格式 ID 或匹配行,描述列表可能为空。这只说明解析器在该 offer 中找到了什么,不证明浏览器没有解码器,也不证明无法播放。
如果面板看起来比 SDP 短,这一区别尤其有用。较短的行可能来自分组、去重、选择性保留字段或缺失匹配行。这本身不能证明某种 codec 已从 offer 中移除。若问题针对 offer 内容,应另行检查获准且已脱敏的 SDP 捕获,不要从报告表格反向重建它。
独立的 decodingInfo() helper 构造六种固定配置:四种音频、两种视频,并固定尺寸、比特率、帧率、通道数和采样率。音频输入是 Ogg Vorbis、Ogg FLAC、MP4/AAC 和 MPEG audio;视频输入是 Ogg/Theora 和 MP4/AVC。固定视频对象为 1920×1080、每秒 120000 bit、每秒 60 帧;音频对象为双通道、每秒 300000 bit、采样率 5200。音频查询只传入音频对象,视频查询只传入视频对象。
只有 supported: true 的结果会被保留;对应布尔值为真时才会加入 smooth 和 efficient 标签。如果两项布尔值都为假,受支持的记录也可能没有标签。记录缺失可能是因为 reducer 丢弃了 supported: false 的响应;被拒绝的 Promise 会走日志分支,不会生成可供 reducer 汇总的受支持记录。这些状态本身都不能解释其他应用为什么不支持某种格式。这些输入只是特定查询,并未覆盖所有通道数、采样率、分辨率、codec profile 或帧率。
这不同于询问平台普遍支持哪些媒体格式。Media Capabilities 规范描述支持情况、流畅度和能效字段;固定版本收集器决定询问哪六种输入以及保留哪些结果。decodingInfo() 参考说明方法契约,不说明收集器的展示策略。如果应用使用每秒 30 帧,而报告查询每秒 60 帧,它们就是不同的查询;不能把一个结果直接套用到另一个查询。采样率和音频通道数同理:固定的双通道查询不枚举所有可能的音频配置。
候选字段与部分结果
回调保存第一条非空字符串并解析其 foundation,但会继续监听,直到 getIPAddress() 能从 connection 的本地描述 SDP 中解析出地址。在该成功分支中,knownInterface 会把 842163049 映射为 public interface,把 2268587630 映射为 WireGuard;三秒超时分支则返回原始解析值。因此展示值取决于结束分支,不能据此推断接口清单。foundationProp 与 stunConnection 来自满足地址条件的事件,可能晚于保存 iceCandidate 的事件。
有两种不同的停止条件。如果没有 window.RTCPeerConnection,收集器会在创建 connection 前返回 null。否则它会创建 pool size 为 1、包含两个 Google STUN URL 的 connection,创建 data channel,并带有音频和视频接收选项请求 offer。它调用 setLocalDescription(),随后立即读取 offer 对象中的 SDP 来汇总扩展和 codec;第一次解析时并未等待 setLocalDescription() 完成。
之后,收集器会启动三秒计时器并监听 icecandidate 事件。如果计时器到期时已有 offer SDP,结果可能包含 codec、扩展、foundation 和候选字段,但没有后续依赖地址的字段;如果没有 SDP,则返回 null。计时器会移除 listener 并关闭 connection。这是源码定义的时间窗口,不证明 ICE 收集已完成或最终集合完整。源码没有为这些情况统一标注原因。
getIPAddress() 先尝试解析 SDP connection 行,再回退到形似候选的子串。它排除字面值 0.0.0.0,但 undefined 只说明这段代码没有返回地址;它不能证明浏览器没有接口、没有候选,也不能说明远端服务接受或拒绝了会话。此解析器不是网络清单:它按 connection 行优先的规则返回一个匹配项。单独的 mDNS 地址隐私指南说明地址形式能透露什么、不能证明什么。
返回对象的字段关联还有一个细微边界。只要保存字符串仍为空,iceCandidate 和 foundation 就会被赋值,因此它们保留第一条非空事件字符串。地址解析读取的却是当前 connection.localDescription.sdp。当某个事件遇到可解析地址时,回调返回的是当前事件的 foundationProp 和 stunConnection。把所有返回字段都归到“第一个候选”名下,会混淆两种选择规则。
因此对象可能包含混合来源:较早事件保存的字符串和 foundation、从当前本地描述中找到的地址,以及完成地址分支的事件字段。这里的“混合来源”只描述哪个代码分支提供了哪个属性,不证明竞态、不一致或网络故障。解读报告时要保留来源差异,不要断言所有属性都描述同一个包或接口。
回调还会忽略没有候选对象的事件;如果当前本地描述文本没有可解析地址,就提前返回。它不会在每个事件后都结束收集。另一个终止路径是计时器,而它的部分结果属性少于找到地址的分支。比较两份报告前,先确认它们是否到达相同分支;否则对象形状差异可能来自时序或可用性,而非共同字段的值发生变化。
报告哈希代表什么
展示代码使用 codecsSdp、extensions、foundation、foundationProp、address 和 mediaDevices 构造哈希输入。它不包含所有返回值:iceCandidate 和 stunConnection 不在输入对象中。因此,如果所选字段未变,原始候选字符串变化可能不会反映在哈希比较中;反过来,某个所选字段改变也只会改变摘要,不会解释原因。
哈希相等只是这个实现中的比较辅助信息。它不能证明两个浏览器是同一设备、完整报告一致,或外部网站给出相同结果。如果摘要不同,源码最多说明按该哈希实现至少有一个所选输入不同;摘要本身不会指出是哪一个。如果摘要相同,未纳入的字段仍可能不同。这个摘要适合提示下一步检查哪些字段,不能替代字段本身。
生产端还会独立计算 mediaDevices。HTML 展示逻辑把列表转换成简短标签,哈希输入也把该列表和 SDP 派生值放在一起。枚举不可用或受权限过滤时,即使 offer 字段没变,哈希的这一部分也可能变化。反之,offer 变化可以改变某个选中的 SDP 字段,而设备类型列表保持不变。摘要合并了来自不同 API 和采集路径的数据类别。
仔细比较时,要分开三个层次:源操作、返回值和展示投影。例如,enumerateDevices() 返回设备记录;helper 只保留类型;界面再将类型映射成标签。原始列表、helper 输出和可见标签不能互换。类似地,decodingInfo() 返回支持情况对象,reducer 保留其中一部分,面板再格式化留下的字段。报告能支持什么结论,由代码路径决定,不只看最终文本。
后续 CreepJS 版本可能改变固定输入、保留更多属性、调整计时器,或向哈希对象添加字段。只比较可见标签会漏掉具体变化发生在哪个阶段。固定版本,比较生产端和消费端函数,并只更新来源映射中相关的一行。这是维护公开源码的做法,不等于浏览器构建测试。关于页面、网络和产品层的边界,参见公开检测套件覆盖情况。
精简的阅读记录
保存源码解读时,记录版本和问题所需的字段差异即可。如果已脱敏的状态足够,不要保留原始地址、设备标签、完整 SDP 或候选字符串。记录应指出某个值来自 API 调用、offer 描述、事件回调,还是 UI 投影;也要注明字段是缺失、被过滤,还是由超时分支返回。这些标签能让两份报告在不复制敏感内容的情况下进行比较。
{
"sourceCommit": "10aa6724cd33a1015db1574211890518cd04f0cc",
"field": "extensions",
"sourceOperation": "getExtensions(sdp)",
"steps": ["remove numeric extmap id", "deduplicate", "sort"],
"state": "parsed",
"interpretationLimit": "not original SDP order or extension IDs"
}
此示例记录的是代码路径,不是浏览器观测。如果单独的支持调查确实需要运行证据,应按获准测试计划采集,并在分享前脱敏;公开源码未提供此类运行记录。不要仅为证明解析器返回了一个字段,就保存完整 SDP 或未脱敏地址。addressPresent 这样的布尔状态、分支名称和源码版本可能已足以回答问题,同时减少留存数据。记录绝不能被当作身份或远端决策的证据。
FAQ
CreepJS WebRTC 哈希能识别浏览器吗?
不能。固定版本代码只对选定报告输入计算哈希。它既不包含每个返回字段,也不证明物理设备、个人身份或外部服务决策。
地址缺失能证明 WebRTC 已禁用吗?
不能。解析器可能没有返回地址,但收集器仍有其他输出;计时器也可能返回部分数据。RTCPeerConnection 不可用时会走另一个立即返回 null 的分支。
扩展列表是原始 SDP 吗?
不是。数字 ID 已移除,重复项已删除,值经过排序。该列表不能还原原始顺序或 ID。
decodingInfo() 会显示所有受支持媒体格式吗?
不会。此收集器只查询六种固定配置,并保留受支持响应中的部分标签。更换配置、浏览器版本或设备上下文都可能得到不同结果。
来源
- 固定版本的 CreepJS WebRTC 收集器
- W3C WebRTC
- RFC 8866:SDP
- W3C Media Capabilities
- MDN:RTCPeerConnection
- MDN:MediaCapabilities.decodingInfo()
- BotBrowser WebRTC 泄漏防护
实践结论
应把 CreepJS WebRTC 面板视为来源和处理方式各不相同的一组字段。记录源码版本,区分 API 不存在、超时和过滤结果;不要把部分报告或选定字段的哈希解释成设备、身份或判定结论。
本文属于指南: 什么是 WebRTC 泄露?IP 泄露防护与浏览器隐私