住宅代理检测:出口地址能暴露什么
站点用哪些记录区分住宅出口与机房出口,每种记录能证明什么,以及浏览器 profile 改不了哪些信号。
BotBrowser Team
买了住宅带宽,不等于浏览器就变成住宅用户。站点在渲染页面前就能读到注册记录、路由数据、反向委托、时延和 TLS 握手,而这些观察彼此矛盾的频率高到任何一项都不能单独作为结论。BotBrowser 提供声明的浏览器 profile 和可配置的代理路由,用于已获授权的任务。它不能改变出口 IP 的 ASN、路由或反向委托,不能把机房地址变成住宅地址,也不保证站点如何分类或是否接受。
TL;DR
- 注册、宣告路由和反向 DNS 是三份不同的记录。一个地址可以登记在消费级 ISP 名下,却由托管网络宣告。
- ASN 查询回答的是“这个前缀分配给了谁”,不是“这是不是家庭宽带”。RIR 数据是分配数据。
- 自发布的 geofeed 可能出错,标准本身要求使用方核验准确度,而不是直接采信。
- 时延和抖动描述的是当下的路径状况。拥塞、距离远的机房和受限上行会呈现相似的形状。
- JA3 或 JA4 描述一次 TLS 握手。代理终止 TLS 时,站点看到的握手属于代理,不属于其后的浏览器。
- 记录每项观察的归属组件。改 profile 能移动浏览器侧的值,移不动 ASN、路由宣告或反向委托。
目录
站点在页面加载前观察到什么
站点最先知道的是连接的源地址。之后的一切都是挂在这个地址上的推断,而这些推断来自职责更窄的系统。区域互联网注册机构把地址空间分配给组织,路由系统宣告前缀,域名运营方发布指针和位置文件。每个系统只回答一个问题,答案的精确度也就只到这个问题的边界。
这就是“住宅代理”的问题常常变成术语之争的原因。一个团队买到的地址出现在消费级 ISP 的区块里,却仍然收到拦截页;另一个团队用机房地址跑了几个月,在一次路由变更后突然失效。把观察拆开看,两种结果都不神秘:地址没变,变的是站点所查阅的那份记录。
| 观察 | 常见来源 | 能证明什么 | 不能证明什么 | 谁能改变 |
|---|---|---|---|---|
| 前缀注册 | RIR 的 RDAP 或 WHOIS | 该地址块分配给了哪个组织 | 客户是从家里还是办公室使用该地址 | 注册机构,通过转移或重新分配 |
| 路由宣告 | 域间路由数据 | 当前由哪个网络宣告该前缀 | 物理位置或接入技术 | 宣告该前缀的网络 |
| 反向委托 | PTR 记录 | 地址所有者为此地址发布了什么名字 | 该名字是否最新、是否属实 | 持有反向区的运营方 |
| 自发布位置 | geofeed 文件 | 发布方为该前缀声明的地点 | 该声明对单个地址是否准确 | 发布方 |
| 路径时延 | 请求测量 | 采样期间路径的表现 | 由哪种接入网络产生 | 单个样本无人能改变 |
| TLS 握手 | ClientHello 与证书交换 | 哪个客户端栈协商了连接 | 哪个浏览器或设备发起了请求 | 终止 TLS 的一方 |
实际后果是:住宅标签是关于记录的陈述,不是链路本身的属性。今天被站点判为住宅的地址,可能在一次分配转移后被重新分类;通过了全部静态检查的地址,仍可能栽在针对会话的行为规则上。
注册、路由与反向委托是三份记录
注册数据被引用得最多,也被误读得最多。注册记录描述的是向某个组织做出的分配。它可以说明某个地址块属于某家互联网服务提供商,也可以说明分配发生在哪个国家。它不会说明某个具体客户在某个具体地址上使用某种具体接入方式;203.0.113.24 的 RDAP 记录是一个分配对象。读到消费级 ISP 的注册就得出“住宅出口”,跳过了提供商把该地址分配给用户的那一步,而提供商会不断重新分配地址。
路由数据回答的是另一个问题:从 203.0.113.24 看到的路由并不等于前缀持有者。被宣告的前缀经由某个网络可达,而宣告该前缀的网络不一定是注册记录里的持有组织。提供商可以从另一个自治系统宣告客户空间,托管公司也可以 origin 一个注册机构仍挂在区域 ISP 名下的前缀。把宣告网络当作持有者、或把持有者当作宣告者,会得出底层数据并不支持的笃定结论。
反向 DNS 在三者中作为身份信号最弱,因为它是一个委托指针,任何控制反向区的人都能发布。一个看起来像家庭主机名的指针可能已过期、已被重新分配,也可能只是运营方偏好这种命名。缺失指针同样说明不了什么:很多正常网络从不发布。即使站点使用反向名,也只是把它作为加权决策中的一项输入,而不是证据。
位置文件需要同样的谨慎。RFC 8805 定义了自发布 IP 地理位置的格式,规范明确指出不准确会出现在多个层面。它要求使用方在可行范围内核验发布的地点,并自行决定偏差阈值。因此 geofeed 支持的是发布方做出的位置声明;它不证明某个地址是家庭宽带,也不约束站点必须接受该声明。
注册与路由的分歧足够常见,单字段分类器会把真实流量标错。有用的问题不是“这个地址看起来像不像住宅”,而是“哪些记录一致、哪些矛盾、每处矛盾对我即将做出的决定意味着什么”。浏览器请求背后的路由机制见代理配置与地理对齐:那篇讲如何选择出口点、如何从观测到的公网地址推导页面可见的值。那篇配置路由,本篇决定路由的记录能支撑什么。
用给定的记录运行一个分类器
下面的例子对五条合成记录做分类。它不发起网络请求、不读取真实站点,也不声称任何真实地址具备这些属性。它的目的是展示一个可辩护决策的形状:每个输入成为独立字段,矛盾被计数,任何字段都不会被压成一个“住宅”结论。
import assert from 'node:assert/strict';
const records = [
{
id: 'registered-residential-isp',
asn: 64500,
registeredUse: 'isp',
announced: true,
ptrSuffix: 'customer.example-isp.test',
ptrOwner: 'example-isp.test',
},
{
id: 'registered-host',
asn: 64501,
registeredUse: 'hosting',
announced: true,
ptrSuffix: 'cloud.example-host.test',
ptrOwner: 'example-host.test',
},
{
id: 'isp-asn-announced-by-host',
asn: 64502,
registeredUse: 'isp',
announced: false,
ptrSuffix: 'static.example-host.test',
ptrOwner: 'example-host.test',
},
{
id: 'stale-pointer',
asn: 64503,
registeredUse: 'isp',
announced: true,
ptrSuffix: 'customer.old-name.test',
ptrOwner: 'other-owner.test',
},
{ id: 'no-pointer', asn: 64504, registeredUse: 'isp', announced: true, ptrSuffix: null, ptrOwner: null },
];
const registeredUseLabels = { isp: 'isp-registration', hosting: 'hosting-registration' };
function classify(record) {
const registration = registeredUseLabels[record.registeredUse] ?? 'unknown-registration';
const routing = record.announced ? 'announced' : 'not-announced-in-sample';
const reverseDns = record.ptrSuffix
? record.ptrOwner && record.ptrSuffix.endsWith(`.${record.ptrOwner}`)
? 'delegated-to-registrant'
: 'not-delegated-to-registrant'
: 'absent';
const disagreements = [
registration === 'isp-registration' && reverseDns === 'not-delegated-to-registrant',
registration === 'isp-registration' && routing === 'not-announced-in-sample',
].filter(Boolean).length;
return { registration, routing, reverseDns, disagreements };
}
for (const record of records) {
const result = classify(record);
console.log(
`${record.id} registration=${result.registration} routing=${result.routing} ptr=${result.reverseDns} disagreements=${result.disagreements}`
);
}
把它粘贴到本地 .mjs 文件并用 node 运行。输出是五行,每条记录一行,字段名与表格一致。把它们当成一个小型对照,而不是结论。
registered-residential-isp registration=isp-registration routing=announced ptr=delegated-to-registrant disagreements=0
registered-host registration=hosting-registration routing=announced ptr=delegated-to-registrant disagreements=0
isp-asn-announced-by-host registration=isp-registration routing=not-announced-in-sample ptr=delegated-to-registrant disagreements=1
stale-pointer registration=isp-registration routing=announced ptr=not-delegated-to-registrant disagreements=1
no-pointer registration=isp-registration routing=announced ptr=absent disagreements=0
五条里有两条矛盾,而且这两处矛盾都有信息量。第三条是消费级 ISP 注册,但在采样中未被宣告,这正是前缀被重新分配、或观察取自不完整路由视图时的形态。第四条是指针已不属于该注册者,这是地址过期或重新签发的样子。两种情况都不会给出“住宅”或“机房”的答案,因为输入不支持那个答案。
第五条最容易被团队忽略。消费级注册、没有反向指针,在这些规则下矛盾数为零,可它对站点几乎什么都没说。把反向名看得重的站点,会与忽略反向名的站点给出不同处理。这个差别是站点侧的策略选择,不是关于地址的事实。
这个分类器刻意做得小。它不解析真实前缀、不校验注册机构的记录、不把矛盾加权成评分,也不模拟任何厂商的判定。任何加进去的阈值都是你自己的策略,带有它自己的错误率,该阈值应当与它所产生的决定记录在一起。
时延形状与握手比看上去更弱
时延看起来像直接测量,所以容易被过度信任。消费级连接的请求往往比机房请求延迟更高、波动更大,于是团队把紧致的分布读成托管信号。推断是双向成立的:拥塞的家庭上行可以显得又慢又乱,同城的机房可以显得又快又稳;记录样本量以及 p50、p95,而不是只记一个数。移动网络自带波动,代理又增加了一跳,而这一跳的行为属于提供商而不是客户端。
因此单个样本作为分类器毫无用处。如果确实使用时延,它应当属于一个记录了样本量、时间窗和路径的分布,即便那样也只支持“这条路径在该时间窗内表现如此”。站点在把会话与声明的设备做对照时,有一个更强的问题:声明设备的典型表现,是否与会话产生的表现一致?一个声明光纤的桌面 profile,每个请求却带着远端中继的形状,这是值得解释的矛盾,而解释可能是中继,而不是接入网络。
握手则是团队常常在反方向出错的地方。JA3 和 JA4 是由 TLS 握手派生出的值,厂商文档明确说明它们在握手中计算,当某次请求未计算握手时可能缺失。这个值描述的是协商连接的客户端栈。这个栈是不是读者机器上的浏览器,完全取决于 TLS 在哪里终止。
代理用 CONNECT 转发时,规范把代理描述为隧道打开后的双向盲转发。这种模式下,站点的 TLS 握手与浏览器完成,站点的 TCP 对端是代理。若代理改为终止 TLS 再重新发起(端口 443),站点的握手属于代理,浏览器的握手是一次站点看不到的独立连接,观察到的 JA3 属于终止方。两种布置都很常见,却从同一个“JA3 变了”的观察得出相反结论。TLS 与 HTTP/2 指纹讲这些值如何拼装、为什么未排序的哈希测的是客户端并未固定的顺序;本篇的点更窄:在把握手归因于浏览器之前,先确认终止方是谁。
还有一个顺序陷阱。站点可以先把握手与声明的浏览器版本比对,再把声明版本与 User-Agent 和 Client Hints 比对,然后把地址注册与声明国家比对。这些是各自独立、各有错误率的规则。一个会话在某条规则上失败,不代表在其他规则上也失败;把三者合并成“指纹不符”的报告,无法告诉团队到底是哪条规则触发。
信誉数据与这些观察并列,而不是凌驾其上。站点可以维护标记地址段或自治系统的名单,并在页面加载前应用。那是关于某个地址段过往行为的策略输入,不是对当前会话的测量;它随名单变化而变化,不随客户端变化。团队用调整浏览器 profile 去回应信誉拦截,是把名单决策当成了浏览器问题。
浏览器 profile 能改什么、不能改什么
声明的 profile 控制浏览器生成并暴露的值,其中包括 navigator.userAgentData 返回的值。User-Agent 与 Client Hints、时区与语言、屏幕度量、canvas 与 WebGL 输出、字体枚举、音频处理特征,以及这些表面之间的一致性,都在这个边界内。profile 可以让它们彼此对齐、并与声明的设备对齐,这正是会话内部自洽的来源。
出口地址在这个边界之外;--proxy-server 决定这个地址,profile 改不动它。profile 不能改变由哪个自治系统宣告地址,不能改变前缀的注册,不能发布反向指针,也不能让托管地址归属于消费级 ISP。它同样不能改变数据包经过的路径,或站点查阅的信誉名单。当问题属于这些之一时,改 profile 得到的只是内部自洽、却仍按地址被分类的会话。
正是这个区分让住宅问题变得可处理。先问失败的观察属于哪一层。若站点拒绝会话是因为声明设备与观测到的浏览器值不符,profile 层是相关层。若拒绝是因为地址落在托管段,或反向指针指向云服务商,任何 profile 改动都不会移动这个结果。若站点在渲染前就拒绝了请求,故障可能根本与身份无关。
对于通过同一提供商运行多个上下文的团队,IPv4 与 IPv6 代理一致性覆盖了那些看起来像身份问题的路由故障:静默回退的地址族、从另一条路径应答的解析器、会话中途改变的路由。这些故障会让同一个浏览器产生互相矛盾的观察,应在路由层诊断,而不是靠调整 profile。
同样的纪律适用于账号。站点与某个账号的历史、此前的会话,以及它已持有的数据,都在这里讨论的任何浏览器与网络层之外。干净的出口不会重置账号历史,住宅地址也不会让一个老账号表现得像新账号。把账号状态当作独立的输入,有它自己的归属方。
实践结论
把出口分类的输入与任务一起记录下来:地址、注册记录及其日期、宣告网络、反向委托(如有)、时延的采样窗口,以及 TLS 终止方。然后写明每项输入归属哪个组件、支撑哪个决定。这份记录回答的正是购买住宅代理本应回答的问题,也显示出答案何时取决于一份记录,而不是浏览器。
常见问题
住宅 ASN 是否保证站点把会话当作住宅处理?
不保证。ASN 标识分配给某个组织的网络。站点可以任意组合地加权注册、路由、反向委托、时延、信誉名单和会话行为,并且可以在不通知的情况下改变权重。住宅注册只是去掉了一个可能的信号,并不能控制决策。
为什么代理显示的 JA3 或 JA4 与其后的浏览器不同?
因为这个值描述的是与站点完成 TLS 握手的那套栈。若代理终止 TLS 并向上游新建连接,站点看到的是代理的握手;若代理转发 CONNECT 隧道,站点看到的是浏览器的握手。先确定握手在哪里终止,再对客户端下结论。
geofeed 或 WHOIS 记录能否证明某个地址是家庭宽带?
这两种记录都不是为此设计的。geofeed 发布运营方声明的地点,标准要求使用方核验准确度;注册记录描述向组织做出的分配。两者都可能准确,却对某条具体用户线路在某个时刻如何使用一无所知。
又慢又抖的连接是否证明住宅出口?
它证明的是采样期间路径的状况。拥塞、距离、移动上行、中继跳数和受限链路都会带来波动。没有分布、采样窗口和路径,时延只支撑对路径的描述,不支撑对接入网络的分类。
站点用住宅地址拦截会话时,团队应该做什么?
先看站点实际使用的观察(如果可得),并把它对应到拥有它的那一层。渲染前的拒绝可能是信誉或访问规则;页面内的挑战可能是行为判定;不符报告可能点出被比较的字段。改 profile 只作用于浏览器侧那一层,所以在为更多出口带宽花钱之前,先确认失败的是哪一层。
来源
本文属于指南: 代理配置总览:浏览器网络决策图