你在德国出口节点配置了代理,运行采集任务,但目标网站却一直返回错误地区的内容。出口地址检查显示确实是德国的,请求也成功了,但结果看起来却像是来自你服务器实际所在的地方。一个常见原因是:连接确实经过了代理,但域名解析没有经过。
这就是 DNS 泄漏。在隐私语境下,这被描述为你的解析器能看到你访问了哪些域名,这确实是事实,但对于数据采集来说,有一个更直接的后果:DNS 解析通常具有地理感知能力,所以在本地解析而在远程出口连接,会产生不匹配,这可能会给你返回错误的区域端点,并悄悄污染一个基于地理位置定位的数据集。
到底泄漏了什么,以及为什么这在这里很重要
向一个主机名发起请求涉及两个步骤:将名称转换为地址,然后连接到该地址。代理拦截的是第二步。它是否也拦截第一步,完全取决于你的客户端。
当客户端在本地解析时,DNS 查询会从你自己的网络发往你自己的解析器,把域名暴露给你的 ISP 或解析器运营商,并返回一个针对你所在位置计算出的结果。
正是这最后一点会破坏采集工作。大型网站背后通常有 CDN 和地理感知型 DNS,会根据查询来源返回不同的地址,所以本地解析的查询可能会指向靠近你服务器的边缘节点,而不是靠近你的代理出口。然后你通过一个德国地址连接到那个节点,这种不匹配可能会产生错误地区的内容、多次运行之间结果不一致,或者在目标网站看来是异常的信号。如果你的工作依赖于城市级定位,那么当地理位置出现问题时,这种故障模式值得首先排查,与此同时另一个常见原因是地理位置数据库的差异。
HTTP 代理很少泄漏,SOCKS5 经常泄漏
这种行为因协议而异,而这正是问题的核心。
对于 HTTP 代理,HTTPS 请求使用 CONNECT 并将主机名发送给代理,由代理在远程进行解析。普通 HTTP 同样会发送一个包含主机名的绝对 URL。这两种情况下,代理都会按设计执行查询,所以普通的 HTTP 代理使用不会泄漏 DNS。
对于 SOCKS5,该协议两种方式都支持。它既可以接受一个主机名并在远程解析,也可以接受一个客户端自己已经解析好的地址。具体采用哪种方式取决于客户端的决定,而许多客户端默认在本地解析。这就是为什么同一个网关在一种配置下会泄漏,而在另一种配置下不会,而且这几乎总是客户端设置的问题,与代理本身无关。
在大多数工具中,这种区别只是一个字符的差异。socks5:// 表示在本地解析,socks5h:// 表示将主机名交给代理处理。这个 h 就是全部的修复方案。
按客户端修复
curl。 使用 socks5h 方案,或者带该方案的 --proxy,而不是 socks5:
# 泄漏:在本地解析
curl -x socks5://customer-USERNAME-country-de:PASSWORD@p.shifter.io:443 https://example.com
# 正确:主机名在代理端解析
curl -x socks5h://customer-USERNAME-country-de:PASSWORD@p.shifter.io:443 https://example.com
Python。 使用 requests 和 PySocks 时,方案(scheme)承载着同样的含义,而且很容易出错,因为两种写法都能正常工作:
import requests
user = "customer-USERNAME-country-de"
PROXY = f"socks5h://{user}:PASSWORD@p.shifter.io:443" # 注意这个 h
r = requests.get("https://example.com",
proxies={"http": PROXY, "https": PROXY}, timeout=20)
最简单的方式是,对同一个网关使用 HTTP 方案,它默认在远程解析,从而完全避开这个问题。这正是在 Python 中使用住宅代理一文中的配置方式。
Node。 Socks 代理库通常会提供一个用于远程解析的开关;请检查它是否已启用,而不要想当然地认为,因为不同库和版本之间的默认值不同。
无头浏览器。 这是最容易发生泄漏的地方,因为浏览器有自己独立于代理设置的解析行为。Chromium 通过自己的堆栈进行解析,在使用 SOCKS 时需要明确限制 DNS 路径;Firefox 有一个控制是否将 SOCKS 主机名交由远程解析的偏好设置,而且它并不总是默认开启。如果你在驱动浏览器,请务必验证而不是假设,并注意下文提到的浏览器特有的泄漏渠道。
测试自己是否真的存在泄漏
不要仅凭配置就下结论。有三个层级的检查方式。
最快的方法是比较目标网站看到的内容和你预期的内容:请求一个能报告其观测到的地址的端点,确认它与你的出口地区相符,然后请求一个地理敏感页面,确认内容也相符。如果地址正确但内容错误,DNS 就是首要怀疑对象。
更直接的方法是,在代理请求运行期间监控出站 DNS 流量。在发起请求的机器上,如果你看到针对目标主机名的查询从 53 端口发出,说明解析是在本地进行的:
# 终端 1
sudo tcpdump -n -i any port 53
# 终端 2
curl -x socks5h://customer-USERNAME-country-de:PASSWORD@p.shifter.io:443 https://example.com
如果抓包结果中出现了针对目标主机名的查询,说明客户端在本地解析了,你就找到了泄漏点。
第三,对于浏览器自动化,可以使用公开的 DNS 泄漏测试页面之一,它们会报告代表你应答的解析器,然后检查这些解析器是位于你的出口所在地区,还是你自己所在的地区。
值得了解的其他泄漏渠道
DNS 是最常见的,但为了完整起见,还有三种渠道会产生同类问题。
IPv6。 如果代理只处理 IPv4,而你的系统对于双栈目标优先使用 IPv6,那么这部分流量可能会完全绕过代理。在执行采集的机器上禁用 IPv6,或者在客户端强制使用 IPv4,可以消除一整类令人困惑的结果。
WebRTC。 在真实浏览器中,WebRTC 可能通过一个独立于代理设置的机制暴露本地和公网地址。如果你在为任何涉及身份的场景驱动浏览器,请将其禁用。
系统和库的默认设置。 有些运行时会激进地缓存 DNS 结果,或者无论代理配置如何都会去查询操作系统的解析器,而 hosts 文件中的条目或本地缓存解析器会覆盖你配置的一切。
如果身份一致性对你的工作很重要,DNS 只是众多需要相互吻合的信号层之一,更广泛的信号集合在 TLS 和 HTTP/2 指纹识别和避免被封锁中有所涉及。
快速检查清单
除非你确实需要 SOCKS5,否则优先对网关使用 HTTP 方案,因为它默认在远程解析。如果你使用 SOCKS5,请在所有地方都使用 socks5h,并在代码库中搜索裸写的 socks5:// 以找出遗漏的地方。用 53 端口的抓包来验证,而不是相信配置本身。如果你的代理路径只支持 IPv4,就强制使用 IPv4。在自动化浏览器中禁用 WebRTC。然后重新运行一个地理敏感请求,确认内容与出口地区相符,而不是与你自己的所在地相符。
结论
DNS 泄漏意味着你的流量经过了代理,但域名解析没有经过,这会把你查询的域名暴露给你自己的解析器,而对采集工作来说更重要的是,它会根据你的位置而不是你的出口位置来解析域名。HTTP 代理默认在远程解析,很少泄漏;SOCKS5 只要客户端在本地解析就会泄漏,这就是为什么 socks5h 与 socks5 之别是最常见的修复方案。无头浏览器需要特别关注,因为它们的解析行为独立于你的代理设置。用抓包来测试,而不是相信配置,同时顺便排查 IPv6 和 WebRTC。当一个地理定位的任务返回错误地区的内容,而出口地址看起来是对的,这就是首先应该检查的地方。
该网关在同一端点上同时支持 HTTP(S) 和 SOCKS5,所以切换方案进行测试不会有任何成本:参见网关与身份验证。相关产品是住宅代理,支持国家和城市定位,并采用按 GB 计费。