Network Information API 指纹:策略复核
navigator.connection 的取值如何叠加到浏览器指纹中,代理出口为何可能与之矛盾,以及如何复核网络信息策略。
BotBrowser Team
Network Information API 通过 navigator.connection 向页面提供几项连接估计值。它的设计初衷是让网站在慢速链路上选择更轻量的资源,但页面上的任何脚本都能读到这些值,所以它们也可能叠加到浏览器指纹中。下面的各节依次说明每个属性报告什么、代理出口为什么可能与浏览器实测值矛盾、如何在原生策略、基于配置文件的策略和字段级策略之间做选择,以及如何不靠猜测来复核结果。BotBrowser 可以在受控上下文中让同一份配置的网络信息值保持一致,但不能改变实际的网络连接,也不能改变站点如何使用这些值。
TL;DR
navigator.connection无需权限提示即可读出effectiveType、以毫秒计的rtt、以兆比特每秒计的downlink、type和saveData,页面上的任何脚本都能看到这五个字段。--bot-network-info-override=false保留宿主的实测值,只写profile时套用已加载配置文件定义的全部取值,JSON 策略只设置它点名的字段。- JSON 语法错误、未知字段、无效取值,或者指向配置文件中不存在的字段的
profile选择器,会让整条策略被丢弃,实测原生值保持不变。 - 解析后的策略属于单个浏览器上下文,同时决定
RTT、Downlink、ECT和Save-Data请求头,所以要把页面取值与你自己的服务器收到的请求头对照。 - 原生运行与覆盖运行要在同一主机、同一路由上连续做,因为代理改变的是路径,而不是页面读到的取值。
目录
navigator.connection 暴露了什么
NetworkInformation 对象报告浏览器当前所用连接的粗略提示。页面用它来决定是否推迟可选视频、选择更小的图片候选项,或延后预取。读取它不需要权限提示,因此页面中运行的任何脚本都能读到,当浏览器的估计发生变化时还会触发 change 事件。
effectiveType把观察到的状况划分为slow-2g、2g、3g或4g。它标注的是浏览器见到的质量,而不是无线电技术。rtt是以毫秒为单位的往返时间估计值。浏览器会对它取整,所以它按档位变化。downlink是以兆比特每秒为单位的下行带宽估计值,同样会取整。type给出连接技术的名称,例如wifi、cellular、ethernet或unknown,前提是浏览器实现了它。saveData是布尔值,表示用户是否要求减少数据用量。
这些值是浏览器给出的估计,而不是页面可以精确到末位的测量结果。规范允许浏览器对它们取整并分档,间隔几秒的两次读取也可能不同。支持情况还因浏览器家族而异。基于 Chromium 的浏览器提供该对象,Firefox 只提供有限的部分,Safari 则不提供,因此复核要从部署所针对的浏览器品牌出发。在通常会提供该对象的品牌上缺失该对象,本身就是一个不寻常的现象。关于这个 API 在应用侧的用法,包括如何处理缺失或变化的提示,请参阅面向自适应内容的 Network Information API。规范还描述了 downlinkMax,即底层连接技术的上限,只有部分浏览器实现了它,所以不要假设策略中可以设置的属性在每个目标上都存在。
连接取值如何叠加到指纹中
没有哪个单独的属性能识别一个人。effectiveType 可取的值很少,type 常常是 unknown 或缺失,大多数访客的 saveData 是关闭的。它的贡献是统计意义上的。某个具体的 effectiveType、取整后的 rtt、取整后的 downlink 和一个 saveData 设置,合起来缩小了可能产生这次访问的浏览器范围,而这种缩小会叠加到页面收集的其他信号上。若声称这些值单独就能识别用户,或能达到某个固定的识别率,就超出了公开规范和文档所能支持的范围。
这些值还带有浏览器所处环境的上下文。光纤台式机、蜂窝网络手机和共享的办公室链路,往往报告不同的组合。saveData 是用户偏好,所以开启它的访客比保持关闭的访客更显眼。由于该对象无需提示就能读取,脚本可以在加载时记录这些值,并与同一次访问中后来的读数比较。
取整是浏览器自身的隐私措施,它限制了单次读取所能透露的信息。剩下的信号主要关乎稳定性和合理性。一个上下文在整个会话中只报告一种连接类别,且 IP 地址、时区和语言与之相符,看起来就是一次普通访问。如果一个上下文的网络值以访问其余部分无法解释的方式变化,或与它的其他信号相矛盾,脚本就有了可比较的依据。这就是复核者要把整组值放在一起看,而不是孤立地调整某一个数字的原因。
最实际的问题出现在代理上。把流量送往另一个地区出口的浏览器,仍然在测量它实际使用的路径,所以 rtt 和 downlink 描述的是这条真实路径,而不是出口地区的典型连接。当 IP 地址、时区和语言都指向同一个地方,而连接估计值描述的却是另一种链路时,这个组合就显得不寻常。代理和配置文件都不能单独解决这个问题,因为代理改变的是路由,而不是页面读取的值。代理配置指南讲的是路由一侧,网络信息策略则涵盖页面可见的取值。
值可能出现分歧的第二个地方是请求本身。同样的估计值可以通过 Client Hints 到达选择接收它们的服务器,对应的请求头是 RTT、Downlink、ECT 和 Save-Data。如果 JavaScript 报告一种连接类别,而请求头报告另一种,这种不一致比任何单个值都更容易被注意到。合理的策略把 JavaScript API 和与网络相关的 Client Hints 视为同一个决定。Client Hints 指纹指南介绍了更广泛的提示家族。
选择网络信息策略
--bot-network-info-override 选项只接受一个策略,有三种有用的选择。选能回答你眼前问题的最小一种。
- 不覆盖,写作
false:页面看到浏览器的原生估计值及其常规取整。这是任何比较的正确基线。 - 配置文件基线,写作不带值的选项、
true或profile:使用已加载配置文件定义的所有网络值。适合配置文件已经描述了预期连接类别的部署。 - 字段级 JSON:只设置列出的字段,省略的字段保持原生。只列出
rtt的策略会让其他所有字段保持实测值。
想测量宿主连接实际报告什么时,使用原生策略,例如调试时,或部署没有理由呈现其他内容时。当配置文件正是为描述你想让页面看到的设备和连接而选的,就使用配置文件基线,因为一个选项即可带来所有可用的网络值,并让它们彼此保持一致。当场景只需要一两个具体的值,例如为较远的出口设置不同的 rtt,而其余字段应保持原样时,使用字段级 JSON。
# 原生值
--bot-network-info-override=false
# 已加载配置文件中的所有可用值
--bot-network-info-override=profile
# 仅所选字段;省略的字段保持原生
--bot-network-info-override='{"effectiveType":"3g","rtt":180,"downlink":1.8,"saveData":false}'
# 按字段混合来源
--bot-network-info-override='{"type":"profile","rtt":"host","downlink":2.5,"saveData":"profile"}'
JSON 策略接受 type、effectiveType、rtt、downlink、downlinkMax 和 saveData。每个字段可以取一个受支持的值,或一个来源选择器 profile 或 host。选择器 profile 从已加载的配置文件中取该字段,host 则有意保留实测值。受支持的取值遵循平台:type 是 wifi、cellular、ethernet、bluetooth、wimax、other、none 或 unknown 之一;effectiveType 是 slow-2g、2g、3g 或 4g 之一;rtt 是受支持范围内的非负整数;downlink 和 downlinkMax 是受支持范围内的非负数;saveData 是 true 或 false。自定义策略或使用 host 的策略可以在没有配置文件时运行,而 profile 选择器要求配置文件定义了该字段。
把每个值都当作基线,而不是常量。浏览器仍然会应用自己的限制、取整和按主机的差异,所以两个来源可能从同一基线得到略有不同的数字,两次浏览器启动也不一定返回完全相同的 rtt。没有任何策略承诺在所有来源上给出同一个固定数字,而且每个站点上都出现一个恒定的、未取整的值,本身就会显得不寻常。
无效输入不会导致致命错误,也不会只生效一部分。格式错误的 JSON、未知字段、无效取值,或指向配置文件中不存在的字段的 profile 选择器,都会让整个策略被忽略,保留实测的原生值。配置文件的其他设置继续有效。对复核者来说,结论很简单:看起来像原生的数字并不能证明存在自定义覆盖。先检查策略文本,再重新读取这些值。
解析后的策略属于一个浏览器上下文。该上下文内的页面、worker、导航以及受支持的 Client Hints 请求使用同一个结果,另一个上下文可以使用不同的策略。要在上下文中创建第一个页面之前设置策略。通过命令行传入的策略优先于配置文件中的 networkInfoOverride 设置,所以当结果与配置文件所暗示的不一致时,请两处都检查。
复核者可以发现的不一致
网络值的大多数问题,是字段之间或字段与场景其余部分之间的不一致,而不是某一个错误的数字。部署上线前,值得检查这些情形。
设备类别与连接类型不匹配。描述桌面浏览器的配置文件通常报告 ethernet、wifi 或 unknown,而手机配置文件更可能报告 cellular。如果你手动设置 type,要确保它仍与配置文件所描述的设备相符。
彼此不相符的字段。规范根据往返时间和下行速率来定义 effectiveType,所以手写的 4g 与以秒计的往返时间估计值并列,描述的是一种很少存在的链路。列出多个字段时,要选择能描述同一条合理连接的值,或者让配置文件提供整组取值。
没有人想要的节省数据标志。大多数访客让 saveData 保持关闭,所以把它设为 true 会让浏览器更罕见。只有当场景确实代表一位要求减少数据用量的用户时才开启它,否则让配置文件或原生值来决定。
代理场景中的 host 选择器。host 选择器保留实测值,这意味着读数再次反映真实路径,包括到代理的那一跳。当你想要实测行为时这是正确的选择,而当你期望页面看起来像出口地区的典型连接时,它就是错误的选择。
报告结果不同的 worker。解析后的策略应当同样适用于上下文中的页面和 worker。如果应用使用的专用 worker 或共享 worker 报告的值与页面不同,请把它当作一个发现,并记录是哪个策略和哪个上下文产生了它。
在另一条连接上取得的原生基线。原生值取决于运行时宿主的连接,所以在不同网络上,或相隔数小时的两次原生运行,不能直接比较。请在同一宿主、同一路由上连续进行原生运行和覆盖运行。
修改得太晚的上下文。由于解析后的策略对上下文是固定的,在应用策略之前就已存在的页面中读到的值,可能与在新上下文中读到的值不同。当两次读数不一致时,请在各自用同一策略创建的独立上下文中比较。
复核原生运行与覆盖运行
复核是一次比较,而不是单次读取。用原生策略运行一次页面,再用待测策略运行一次。保持浏览器版本、配置文件、代理路由、视口和应用状态不变,只改变策略值。每次运行都使用新的上下文,这样新策略就不会与已经建立的页面混在一起。
- 记录每次运行的完整策略文本,包括字段名称。
- 打开一个只显示
effectiveType、rtt、downlink、type和saveData的小型诊断页面。 - 按下面所示从控制台读取这些值,在页面中读取,如果应用使用 worker,也在 worker 中读取。
- 如果你运营的服务器请求了与网络相关的 Client Hints,就比较到达该服务器的这些提示。
- 导航之后重复读取,确认上下文保持同一个结果。
const c = navigator.connection;
console.log(c.effectiveType, c.rtt, c.downlink, c.type, c.saveData);
设想一个部署,它使用另一个地区的代理出口,并配合一个宽带桌面配置文件。原生运行显示宿主的实测值。使用配置文件的运行应当显示配置文件的连接类别,其取整后的值在上下文内保持稳定。只列出 rtt 的字段级运行,应当只改变这一个值,让 effectiveType、downlink 和其他字段保持原生运行所报告的样子。如果字段级运行改变了超出所列字段的内容,或什么都没改变,首先要重新阅读的就是策略文本。
好的结果是这样的。策略中列出的字段在页面和请求头中一起变化,而省略的字段保留原生值。各来源之间的值可能被取整或略有差异,这是预期之内的,并不是失败。同样重要的是哪些东西不会变化。代理吞吐量、路由,以及服务器在自己的请求上测得的任何延迟都保持原样,所以期望服务器观察到不同速度的比较,测的是错误的对象。
当某个值看起来不对时,请按以下顺序检查:加载的是哪个配置文件、JSON 语法、字段名称、策略是否在上下文的第一个页面之前设置、命令行与配置文件设置中哪个生效,以及代理路由。因无效而被忽略的策略会返回实测值,所以页面看起来没有变化并不表示浏览器停止了工作。用单独的上下文来测试修正后的策略最为干净。
复核记录只保留配置和可见结果:版本、配置文件名称、策略文本、路由的标签以及读取到的值。不要包含页面内容、凭据和私密的请求数据。这足以让别人重复这次比较,也让记录可以安全地与团队分享。
每当浏览器版本、配置文件或代理提供商发生变化时,都要重复比较,因为取整规则、受支持的属性和路由本身都可能变动。上一个版本保存下来的比较是有用的参考。如果列出的字段仍然一起变化,省略的字段仍然保持原生,策略就仍然在做部署所期望的事。如果数字不同但相互关系依旧成立,原因通常是正常的取整和按主机的差异,而不是策略失效。
BotBrowser 的适用之处
BotBrowser 支持一个 --bot-network-info-override 策略,可以是配置文件基线、原生值或字段级 JSON,它为每个浏览器上下文设置 navigator.connection 的属性和对应的 Client Hints,让你能使页面看到的网络值与所选的配置文件和代理场景保持一致。它不会改变代理的真实速度或延迟、服务端的测量,也不会改变网站根据其他网络信号做出的推断,而无效的策略会被忽略,保留实测的原生值。
网络值只是众多输入之一,所以要让它们与为同一路由选择的时区、区域设置和语言保持一致,详见时区与区域设置配置。浏览器支持和规范细节会变化,所以当某个属性或提示在新的浏览器版本上表现不同时,请查阅下面的来源。
公开来源
实践结论
一次网络信息复核留下三样东西:--bot-network-info-override 的原文、页面与 Worker 中读到的五个取值,以及你自己的服务器收到的 RTT、Downlink、ECT 和 Save-Data 请求头。当一次运行看起来像原生值时,先读策略文本再看数字,因为被拒绝的 JSON 对象会静默退回实测值。浏览器版本、配置文件或代理服务商变化之后重做这对运行,然后按字段逐项比较两份记录,而不是盯住一个数字。
本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界