privacy

三种 WebRTC 模式,网站分别能看到什么

Agilelogin 三种 WebRTC 模式决定 Profile 暴露哪些 ICE 候选:各模式显示什么、为何 privacy 默认,以及不覆盖什么。

更新于 7 分钟阅读 Agilelogin product team

要点

  • 网页无需请求摄像头或麦克风权限,就能发起一次 WebRTC 连接尝试,而它收集到的候选地址列表,即使浏览器已经走了代理,也可能包含真实公网 IP。
  • Agilelogin 提供一个逐 Profile 生效的设置 webrtc.mode。编辑器提供三种模式:privacy、replace 和 disable。内核为已有 Profile 和调试保留两个旧值 forward_stun 和 allow;新建的 Profile 无法选择它们。
  • 自 2026-07-27 起,privacy 一直是产品默认值:不会泄露 host 候选或 server-reflexive 候选,这与 Chrome 自身企业策略中的 disable_non_proxied_udp 效果一致。
  • 编辑器会拒绝在轮换代理上使用 replace,旧值 forward_stun 也不能用在已配置代理的 Profile 上,因为这两种组合给出的候选地址列表都撑不住背后的网络分配。
  • 该模式在启动时读取一次,整个会话期间保持不变;它并不覆盖网页可以使用的每一条 WebRTC 代码路径,对于自己运营 STUN 服务器的网站,没有任何一种模式能隐藏真实地址。

网页在你允许之前就能了解到什么

WebRTC 最初是为了让两个浏览器之间直接交换音频、视频或数据而设计的,不必让每个数据包都绕经服务器。为了让这种直连成为可能,双方需要告诉对方如何联系到自己,而这套协商——即互动连通性建立(ICE)——会在网页打开一个 RTCPeerConnection 的那一刻就开始运行,早于任何摄像头或麦克风权限提示出现之前。哪怕只是一个只带数据通道的 RTCPeerConnection,也足以触发它,网站根本不需要用户同意任何事情。

ICE 会收集所谓的候选地址,监听 icecandidate 事件的网页可以把它们全部收集起来。host 候选描述的是本机网络接口上的一个地址;现代 Chromium 会用一个随机生成的 .local mDNS 名称替代直接暴露局域网 IP。server-reflexive(srflx)候选,是 STUN 服务器告诉浏览器”从外部看你的地址是什么”——通常就是真实公网 IP,无论浏览器其他流量是否走了代理都会被发现,因为 STUN 流量不必像网页请求那样经过 HTTP 代理。而 relay 候选,在涉及 TURN 时,是流量实际经过的中继服务器上的一个地址。合起来看,这正是众所周知的”WebRTC 泄漏”路径:浏览器的 HTTP 流量可能正走着代理,而它的 ICE 候选地址却仍然把底层的公网 IP 交给了网站。

三种模式与两个旧值并排对比

Agilelogin 只提供一个设置来管理这一切,即逐 Profile 生效的 webrtc.mode。编辑器提供其中三个取值:Privacy(privacy)、Replace IP(replace)和 Block connections(disable)。另外两个 allow 和 forward_stun 是内核为已有 Profile 和调试保留的旧值:编辑器只在 Profile 当前已是该值时才显示它,所以新建的 Profile 无法选择。下表列出内核的全部五个取值:

模式Host / mDNSServer-reflexiveRelay (TURN)网站看到什么
privacy(默认)无无仅当网站提供 TCP/TLS TURN 时才可能出现代理出口 IP,或什么都没有
replace—改写为代理出口 IP—代理出口 IP
disable无无无,完全不建立 TURN 连接没有任何候选地址——API 本身仍然存在
allow(旧值)mDNS 候选真实公网 IP(若网站的 STUN 服务器可达)—真实 IP,经由 srflx
forward_stun(旧值)—真实公网 IP—真实 IP,被强制暴露

allow 就是未经改动的 Chromium 原生行为,候选地址收集的任何环节都不会被触碰。disable 走向另一个极端——没有任何类型的候选地址会到达网页,也不会建立任何 TURN 连接,尽管 RTCPeerConnection 对象本身仍然存在,脚本照样可以构造它;网页无法从调用方式上看出这个 API 正被有意压制。forward_stun 和 replace 都专门针对 srflx 候选下手:前者放行真实地址,后者会在网页看到之前,把地址改写成该 Profile 的代理出口 IP。

为什么 privacy 是默认值

privacy 之所以处在两个极端之间,是有意为之——自 2026-07-27 起,它就被明确写进了每一个 Profile 模板。它彻底压制 host 和 server-reflexive 两类候选——没有 mDNS 名称,也不会经由 STUN 暴露真实 IP——同时如果网站自己的 TURN 服务器恰好跑在 TCP 或 TLS 上,仍然允许出现一个 relay 候选,此时网页看到的地址是代理的出口 IP,而不是本机地址。这并非凭空发明的行为:它和 Chrome 企业策略中 WebRTCIPHandlingPolicy 设为 disable_non_proxied_udp 时的效果完全一致——Agilelogin 的默认值,对应的正是 Chrome 自己已经为受管理部署提供的一种模式,只是把它下放到了每个 Profile 而不是每台机器上。

把它设为默认值,考虑的是一致性,而不是隐匿。一个已经为其 HTTP 流量和浏览器指纹配置了代理的 Profile,理应在每一个会被检查身份的地方都向网站展示同一个网络身份,WebRTC 也不例外——同时运行多个 Profile 的操作者,希望每一个都呈现为一个连贯、自洽的环境,而不是地址栏显示一个国家、WebRTC 候选地址却显示另一个国家。当 Profile 由自动化工作流驱动而非由人工点击操作时,这种一致性同样重要,因为脚本没有办法自己察觉到某一个信号正悄悄地和其余信号不一致。

编辑器拒绝的两种组合

会触及 server-reflexive 候选的两个取值,各自都配有防护措施。forward_stun 是有意暴露真实公网 IP,新建的 Profile 无法选择它,Profile 编辑器也不允许已配置代理的 Profile 使用它;如果每一个其他信号都来自代理,却单单通过 WebRTC 强行放出真实 IP,那就重新制造了整套设置本该堵住的那个泄漏点。

replace 的防护措施范围更窄:它在使用轮换代理的 Profile 上会被拒绝。把 srflx 候选改写成”代理的出口 IP”,前提是这个出口 IP 得是一个固定、已知、整个会话都能锚定的值;轮换代理没有这样一个值。即便是在静态代理上,如果内核在 ICE 收集候选地址的那一刻还不知道该代理的出口 IP,replace 也会退化为 privacy 的行为,而不是给出一个过时或错误的地址——不改写的猜测,比完全没有候选地址还要糟糕。

这些模式都不覆盖什么

webrtc.mode 只在内核启动一个 Profile 时,通过一条私有的 stdin 通道读取一次——它无法在该浏览器实例运行期间被动态改变,所以需要不同模式的 Profile,需要的是重启,而不是实时切换。对于 replace 和 forward_stun,被改写或转发的值在网页可能去查询的每一处都保持一致——RTCPeerConnection.getConfiguration()、icecandidateerror.url,以及 getStats() 和 iceTransport 事件中暴露的候选地址 URL,都和实际发出去的一致。这堵住了网页原本可以用来交叉核对的明显手段,但它有一条硬性限制:一个自己运营 STUN 服务器的网站,能看到每个请求实际来自哪个地址,并可以把它与网页拿到的候选地址进行比对。replace 无法改变那台服务器观察到的内容。它是针对网页侧视图的一项隐私与一致性控制,仅此而已。

还有两个限制值得直说,而不是含糊带过。任何一个 webrtc.mode 取值下 IPv6 候选地址的行为都尚未单独验证过,因此本文对此不作任何断言。而且这些模式并不管辖 Chromium 暴露的每一个 WebRTC 相关接口——内核中存在一个更新的 RtcTransport API,它完全在 webrtc.mode 的管辖范围之外,不过它目前尚未在正式发布版本中开放,所以现阶段网页还接触不到这条路径。以上都是 Polychrome 内核中已有文档记录的行为,测量基于 M150 构建;目前还没有可供下载的桌面应用正式版本,所以请把本文当作内核当下的实际行为来看待,而不是当作一个可以在已发布版本中尝试的功能。