对比阅读约 3 分钟

socks5 与 socks5h:六个客户端实际发给代理的是什么

socks5:// 并不是在每个客户端里都表示本地 DNS。我们记录了 curl、Requests、HTTPX、aiohttp、Playwright 和 Node 在每种协议写法下发给 SOCKS5 代理的地址。

本页内容

在 curl 中,socks5:// 在你的机器上解析目标主机名,socks5h:// 则把主机名交给代理。其他客户端并不遵循这条规则:在这次测试的六个客户端中,有三个在使用普通的 socks5:// 时发送了主机名,其中两个在连接之前就拒绝了 socks5h://。

客户端(测试版本)socks5:// 发给代理的内容socks5h://
curl 8.22.0IPv4 地址(本地解析)主机名
Requests 2.34.2 + PySocks 1.7.1IPv4 地址(本地解析)主机名
HTTPX 0.28.1 + socksio 1.0.0主机名主机名
aiohttp 3.14.3 + aiohttp-socks 0.12.0主机名ValueError,没有建立连接
Playwright 1.63.0(Chromium 153.0.8010.12)主机名net::ERR_NO_SUPPORTED_PROXIES,没有建立连接
Node.js 24.20.0 + socks-proxy-agent 10.1.0IPv4 地址(本地解析)主机名

每个单元格记录的是:让客户端请求 http://dnsprobe.example/ 时,127.0.0.1 上一个带日志的 SOCKS5 服务器收到了什么。这个名称只存在于本地 hosts 文件中,指向 127.0.0.2。“IPv4 地址”表示代理看到的是 127.0.0.2,始终不知道主机名。“主机名”表示代理收到了 dnsprobe.example,需要由它来解析。换成 https:// 目标时,每个客户端的结果相同。

为什么地址类型很重要

一个 SOCKS5 请求只带一个目标地址,它的 ATYP 字节说明地址类型:X'01' 是 IPv4,X'03' 是域名,X'04' 是 IPv6。RFC 1928 第 4 节 发送域名的客户端把 DNS 留给代理;发送 IP 地址的客户端已经自己完成了查询。这会带来三个后果:

  1. 你自己的解析器会看到这次查询。 目标站点的 DNS 查询会发往你机器上配置的解析器,不经过代理连接。Requests 文档说得很直接:socks5 “causes the DNS resolution to happen on the client, rather than on the proxy server”。Requests:SOCKS
  2. 得到的答案可能属于错误的位置。 RFC 7871 指出,许多权威域名服务器 “return different responses based on the perceived topological location of the user”,依据是查询来源的地址,而这通常是你的解析器。RFC 7871 第 1 节 因此,在本地解析的 CDN 主机名可能指向为你的网络挑选的服务器,而代理连接的正是这个地址。出口 IP 在你要求的地区,但它到达的服务器是为你所在的地区挑选的,于是内容可能是另一个地区的。这是从请求的构造方式推出的结论;回环测试没有测量任何 CDN。
  3. 只有代理能解析的名称会失败。 对一个在本地无法解析的名称,curl 8.22.0 停在 curl: (6) Could not resolve host: dnsprobe-missing.example,Requests 抛出包含 [Errno -2] Name or service not known 的 ConnectionError,socks-proxy-agent 则以 getaddrinfo ENOTFOUND dnsprobe-missing.example 失败。三者都没有向代理发送请求。使用 socks5h:// 时,三者都把名称发给了代理。

HTTP 与 SOCKS5 代理指南说明了在选择代理协议时 DNS 的位置该如何考虑。本文余下的部分讲的是如何为每个客户端选对协议写法。

配置里该怎么写

客户端把主机名发给代理避免使用
curlsocks5h:// 或 --socks5-hostnamesocks5:// 和 --socks5 会在本地解析
Requests + PySockssocks5h://socks5:// 会在本地解析
HTTPX + socksiosocks5:// 或 socks5h://socks:// 抛出 ValueError
aiohttp + aiohttp-sockssocks5://socks5h:// 抛出 ValueError
Playwright Chromiumsocks5://socks5h:// 在导航时失败
Node socks-proxy-agentsocks5h:// 或 socks://socks5:// 会在本地解析

没有哪一个 URL 对六个客户端都正确。如果同一个 PROXY_URL 设置供多个工具使用,socks5h:// 会让 aiohttp-socks 和 Playwright 出错,而 socks5:// 会让 curl、Requests 和 socks-proxy-agent 在本地解析。请按客户端分别设置协议写法。

拒绝 socks5h:// 的两个客户端失败的时机不同。aiohttp-socks 在 ProxyConnector.from_url() 中抛出异常,此时还没有打开任何套接字:

code
ValueError: Invalid scheme component: socks5h

Playwright 在 chromium.launch() 中接受了这个代理,到第一次导航时才失败,而且没有连接代理:

code
page.goto: net::ERR_NO_SUPPORTED_PROXIES at http://dnsprobe.example/

Playwright 文档给出的 server 选项的 SOCKS 写法是 socks5://myproxy.com:3128。Playwright:proxy 选项 其余的启动和上下文设置见 Playwright 代理指南。

不带版本的 socks:// 同样不一致。Chromium 和 socks-proxy-agent 把它当作 SOCKS5 并发送了主机名。curl 以 SOCKS4 握手开头(首字节 0x04),只支持 SOCKS5 的服务器无法应答。Requests 抛出 ValueError: Unable to determine SOCKS version from socks://127.0.0.1:11080,HTTPX 抛出 ValueError: Unknown scheme for proxy URL URL('socks://127.0.0.1:11080'),aiohttp-socks 抛出 ValueError: Invalid scheme component: socks。

Python 这边有两个安装细节:Requests 需要 requests[socks] 附加依赖,HTTPX 需要 httpx[socks]。缺少它们时,Requests 会抛出 Missing dependencies for SOCKS support,HTTPX 会抛出提到 socksio 的 ImportError;两种情况的解决方法也涵盖了代理 URL 来自 ALL_PROXY 的情形。Python Requests 代理指南和 HTTPX 异步代理指南给出了完整的客户端代码。

这次测试是把代理 URL 显式传给每个客户端的。如果你的代理 URL 来自 ALL_PROXY 或 HTTPS_PROXY,请先确认客户端读取的是哪个变量;代理环境变量指南按客户端列出了这一点,而 NO_PROXY matching, tested 表明各客户端的绕过规则也不相同。

如何检查你自己的配置

想知道你的客户端发送了什么,并不需要代理账号。下载 socks5_log.py,这是一个 42 行、没有依赖的 Python 脚本。它完成 SOCKS5 握手,打印每个请求的地址类型和地址,然后回复 “general SOCKS server failure”,所以不会转发任何流量。

给你的机器加一个只有 hosts 文件知道的测试名称。不要用 localhost:Chromium 会把发往 localhost 名称的请求直接发出,而不是经过代理。Chromium: implicit bypass rules 在 /etc/hosts 中加入这一行:

code
127.0.0.2 dnsprobe.example

在一个终端中启动服务器,在另一个终端中为每种协议写法发送一个请求:

sh
python3 socks5_log.py 1080
curl -x socks5://127.0.0.1:1080 http://dnsprobe.example/
curl -x socks5h://127.0.0.1:1080 http://dnsprobe.example/

使用 curl 8.18.0 时,服务器打印出:

code
LISTENING 127.0.0.1:1080
CMD=1 IPv4 127.0.0.2 port=80
CMD=1 DOMAIN dnsprobe.example port=80

第一个请求以 IP 地址的形式到达,说明 curl 在本地解析了名称。第二个请求以名称的形式到达。两条 curl 命令都以 curl: (97) cannot complete SOCKS5 connection to dnsprobe.example. (1) 结束,这是服务器的拒绝,属于预期结果。把 curl 换成你自己的客户端和代理设置,读同样的两个字段。如果一行都没有出现,说明客户端拒绝了这种协议写法,或者绕过了代理。

除协议写法之外的远程 DNS 选项

有些客户端把同样的选择做成了一个选项,通常叫 rdns:

  • curl:测试中 --socks5-hostname host:port 的行为与 socks5h:// 相同,--socks5 host:port 与 socks5:// 相同。curl man page
  • Requests:没有单独的选项。urllib3 的 SOCKS 管理器根据协议写法设置 PySocks 的 rdns 标志:socks5 为 False,socks5h 为 True。urllib3 源码 直接使用 PySocks 时,set_proxy() 接受 rdns,文档中的默认值是 True。PySocks README
  • aiohttp-socks:ProxyConnector 接受 rdns,README 中注明 “default is True for socks5”。aiohttp-socks README 测试中,ProxyConnector.from_url("socks5://127.0.0.1:11080", rdns=False) 发送了 127.0.0.2,rdns=True 发送了主机名。
  • HTTPX:0.28.1 版把 socks5 和 socks5h 交给同一个 SOCKS 传输层,所以协议写法没有任何影响。HTTPX 源码
  • socks-proxy-agent:只由协议写法决定它的 lookup 标志:socks5 和 socks4 在本地解析,socks5h、socks4a 和 socks 不在本地解析。socks-proxy-agent 源码
  • Chromium:没有选项。它的文档写明,对于 SOCKSv5,“name resolution is always done proxy side”。Chromium 网络文档

方法与局限

记录于 2026 年 10 月 4 日,环境为 Ubuntu 26.04(x86_64)、Python 3.14.4 和 Node.js 24.20.0。版本即第一张表中的版本,另有 urllib3 2.8.0、httpcore 1.0.9、python-socks 3.1.1 和 socks 包 2.8.10。curl 8.18.0 与 8.22.0 一起运行,发送的地址相同。对一个只解析到 IPv6 的名称,curl、Requests 和 socks-proxy-agent 发送的是 IPv6 地址,另外三个客户端发送的是主机名。

这个实验只有一个拒绝所有请求的回环服务器。它不能说明任何代理服务的情况,也不涉及带认证或 UDP 的 SOCKS5,以及 macOS、Windows、Firefox 或 WebKit。库的默认行为会变化,请检查你实际使用的版本。实验压缩包包含服务器、六个客户端脚本、运行器和 README;全部 62 个用例的原始输出在 results.json 中。

相关内容

ipvolt 正在为开发者和智能体构建代理基础设施。加入候补名单,开放访问时只发一封邮件。

参考来源

  1. curl man page: --proxy (socks5:// and socks5h://)
  2. RFC 1928: SOCKS Protocol Version 5, section 4 (address types)
  3. Requests: SOCKS proxies
  4. urllib3 2.8.0 source: contrib/socks.py (scheme to rdns)
  5. PySocks README: set_proxy and rdns
  6. HTTPX: SOCKS proxies
  7. HTTPX 0.28.1 source: proxy schemes in _transports/default.py
  8. aiohttp-socks 0.12.0 README: ProxyConnector and rdns
  9. python-socks 3.1.1 source: parse_proxy_url schemes
  10. socks-proxy-agent source: parseSocksURL
  11. Chromium network docs: SOCKSv5 proxy scheme and implicit bypass rules
  12. Playwright: browserType.launch proxy option
  13. RFC 7871: Client Subnet in DNS Queries, section 1

标签:ProxiesTroubleshooting