Chrome WebRTC 防泄露测试:用本地 STUN 验证网络策略
验证 WebRTC 网络策略,需要让页面具备创建连接的能力,再同时观察 STUN 服务端收到的数据报与页面产生的 ICE 候选。 如果先把 RTCPeerConnection 禁用了,之后没有数据包,只能说明没有发生这次连接,不能据此证明底层网络策略生效。
本文给出只在本机运行的教学实验,通过“关闭—开启—再次关闭”建立对照。2026年11月6日,在 Windows、Playwright 1.60.0、完整 Chrome 155.0.8059.85 的无头模式下,三次实验的 STUN Binding 请求数为 1、0、1,测试映射候选也依次出现、消失、恢复。
这些结果只支持该浏览器版本、本地 STUN 路径和观察窗口的结论,不是匿名性认证,也不证明所有站点、企业策略或执行环境中的地址获取方式均被覆盖。旧文WebRTC 泄漏机制与排查已经解释了基础概念,本篇集中讨论怎样取得可核验的实验依据。
文中的 dyDM 案例对应固定开发源码,不表示正式安装包已经交付相同能力。下方独立实验直接启动指定 Chrome,并未通过 dyDM 的完整业务启动链路;本地实验结果与私有项目整体验收分别记录。
网络策略与页面接口限制应分别验收
网络策略影响浏览器尝试使用哪些连接路径;页面接口限制影响页面是否能创建相关对象。两个控制层可以配合,但不应互相冒充验证证据。
| 测试目的 | 保持什么条件 | 主要观察 |
|---|---|---|
| 网络策略是否改变测试路径 | 页面 API 可用,仅切换网络策略 | STUN 收包、候选类型与收集结束 |
| 页面 API 限制是否生效 | 独立配置初始化脚本 | 页面第一个脚本看到的对象 |
| 组合运行与兼容性 | 明确记录两层配置 | 目标任务是否完成、哪些功能受限 |
RFC 8828讨论 WebRTC 的 IP 地址处理要求,可用于理解策略的目标;浏览器具体接口和行为仍需结合实现验证。Chrome 的隐私 API 文档列有 disable_non_proxied_udp 这一策略值,但不能由某个参数字符串出现在启动列表,就推断所有 Chromium 产品均采用了它。Chrome privacy 文档

图1:原创分层验证示意,未表示所有页面环境都已覆盖。
STUN 测试映射地址不是真实公网 IP
ICE 收集候选并检查可用路径,STUN 是其使用的工具之一。通过 STUN 获得的 server-reflexive 候选,通常简称 srflx,与本地接口相关的 host 候选、中继候选不同。RFC 8445、RFC 8489
为了让实验不依赖公网服务,下面的响应器绑定 127.0.0.1,收到 Binding 请求后,返回固定的 203.0.113.42 作为测试映射地址。它属于RFC 5737的文档地址段,不是本次测出的公网出口,也不代表这条候选能够建立真实公网通信。
响应器只实现本实验需要的回复,不是完整 STUN 服务端。把一个可辨识结果返回给浏览器,目的是检查请求与候选生成这条链路是否发生。
STUN 成功响应需要沿用请求的事务 ID,并按 XOR-MAPPED-ADDRESS 的格式编码地址和端口。随便返回一个 IP 文本,不能用来判断浏览器是否正确收到了 STUN 响应。示例实现依据 RFC 8489 的报文与属性格式,没有将它宣称为协议一致性测试套件。
用关—开—关排除无效对照
只有“开启后没收到包”,证据不充分:浏览器可能未创建连接、响应器可能没启动、采集过程也可能提前结束。
三次测试都使用新的浏览器进程和临时 Context,API 屏蔽始终关闭,只切换网络启动参数:
| 阶段 | 网络策略 | 预期检查 |
|---|---|---|
| 第一次 | 关闭 | 收到请求,产生固定测试映射的 srflx 候选 |
| 第二次 | 开启 | 在观察窗口内没有新增请求,也没有测试映射候选 |
| 第三次 | 再次关闭 | 请求与候选恢复,确认测试链路仍然正常 |
等待以 ICE 收集结束或最长四秒为边界。四秒不是网络世界的通用安全阈值,只是这个本地、可立即响应的夹具设置。实验若不能建立正向对照,应先报告对照失败,而不是把全程零包当作防护成功。

图2:原创实验设计示意。实际结果见下方版本和观察表。
可独立运行的本地实验
在独立目录保存下方完整代码为 probe.py。需要 Python 3.10 及以上、Playwright,以及本机已安装的完整 Chrome:
1 | python -m pip install playwright==1.60.0 |
脚本使用 channel="chrome",不会自动下载或替换本机 Chrome。没有安装该浏览器时应先处理环境;直接把 channel 改成测试 Chromium,得到的是另一个环境的结果,需要单独记录。此脚本不读取账号目录、Cookie 或真实代理配置。
1 | """Original loopback-only STUN teaching experiment; not an Internet STUN server.""" |
程序将结果写入同目录的 verification-results.json。服务端只绑定回环地址,结束时停止线程并关闭 socket;输出仅保留候选类型与是否出现文档测试映射,不保存本地接口地址列表。
本次实际运行结果如下。三次均完成 ICE 收集,没有依靠超时把尚未结束的过程当作成功:
| Chrome 155.0.8059.85,无头模式 | STUN 请求数 | 候选类型 | 固定测试映射 |
|---|---|---|---|
| 策略关闭 | 1 | host、srflx | 出现 |
| 策略开启 | 0 | 无 | 未出现 |
| 策略再次关闭 | 1 | host、srflx | 恢复 |
表格是本次运行记录,不是建议值或模拟指标。响应器返回的是人工地址,因此不能把表格写成真实公网 IP 泄漏与修复测试。
页面 API 限制验证的是另一个结果
示例末尾还独立检查了页面首次脚本:通过 BrowserContext.add_init_script(),在文档脚本运行前限制 RTCPeerConnection,页面保存的首次观察结果为 undefined。
Playwright 初始化脚本文档说明了相应生命周期。注册初始化脚本与事后在开发者工具修改全局对象不同;已经运行的脚本可能保留旧引用。
这个附加用例只验证示例数据页面的首次脚本,不代表本次已测试跨源 iframe、popup、Worker、扩展脚本或任意 CSP。真实应用如果依赖这些路径,需要分别设置可观察断言;不能把方法调用成功当作覆盖清单已经完成。
对象限制也有兼容性成本。视频会议、设备采集、直播连麦等正常功能可能受影响,mediaDevices 等额外限制尤其需要按用途选择。策略关闭后,已经安装进旧页面的属性限制也不一定可撤销,重新创建浏览器是更清楚的生效边界。
完整 Chrome 与测试内核需要分别记录
个人项目的历史开发资料记录过一种反例:测试内核中的结果符合预期,完整 Chrome 的 STUN 验证却未通过。当前私有源码基线 56e25be7f9792a4245f343408bbb061df59bbf7e 保留了以下两个启动参数:
1 | --webrtc-ip-handling-policy=disable_non_proxied_udp |
本文本次实测也使用这一组合,但没有分别测量两个参数各自的贡献,不能从组合成功推导两个参数单独都必需或都有效。它们不是本文承诺所有浏览器长期支持的稳定公共接口;版本升级后应复测实际行为。
同样,disable_non_proxied_udp 不会把一个只支持 TCP 的本地代理变成 UDP 转发器。没有实现 UDP ASSOCIATE 或其他 UDP 传输,就不能宣传为“所有 WebRTC 流量已经通过当前 SOCKS5 代理”。
结果能支持哪些结论
本次实验支持的结论是:在记录的完整 Chrome 无头版本中,指定参数组合改变了本地 STUN 测试路径,关闭后能恢复;附加页面用例中,初始化脚本在首次脚本前限制了指定对象。
它没有检查有窗口模式、所有浏览器产品、真实上游代理、远端 STUN/TURN、企业管理策略、实际业务账号或全部 JavaScript 环境。也没有用一次运行证明长期稳定性。
当 IP 检测页仍显示非预期地址时,应继续核对页面实际调用的接口域名、代理规则和当前运行快照。网络检测接口直连、旧配置仍被使用、上游自身分流,都可能造成类似表象;“IP 不对”不是 WebRTC 问题的充分证据。
参考资料
资料核对与本地实验日期为2026年11月6日。私有项目历史反例与本次原创夹具结果分别叙述;本篇不复现其全部内部测试。
- RFC 8828:WebRTC IP Address Handling Requirements,地址处理目标和策略背景。
- RFC 8445:ICE,候选与连通性检查。
- RFC 8489:STUN,Binding报文和映射地址属性。
- Chrome:privacy API,IP handling策略值。
- Playwright:BrowserContext.add_init_script,初始化脚本时机。
- Playwright:Browsers,配套浏览器与品牌浏览器的运行选择。
- RFC 5737:文档示例IPv4地址,人工映射地址的性质。