在 curl 中,socks5:// 在你的机器上解析目标主机名,socks5h:// 则把主机名交给代理。其他客户端并不遵循这条规则:在这次测试的六个客户端中,有三个在使用普通的 socks5:// 时发送了主机名,其中两个在连接之前就拒绝了 socks5h://。
| 客户端(测试版本) | socks5:// 发给代理的内容 | socks5h:// |
|---|---|---|
| curl 8.22.0 | IPv4 地址(本地解析) | 主机名 |
| Requests 2.34.2 + PySocks 1.7.1 | IPv4 地址(本地解析) | 主机名 |
| 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.0 | IPv4 地址(本地解析) | 主机名 |
每个单元格记录的是:让客户端请求 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 地址的客户端已经自己完成了查询。这会带来三个后果:
- 你自己的解析器会看到这次查询。 目标站点的 DNS 查询会发往你机器上配置的解析器,不经过代理连接。Requests 文档说得很直接:
socks5“causes the DNS resolution to happen on the client, rather than on the proxy server”。Requests:SOCKS - 得到的答案可能属于错误的位置。 RFC 7871 指出,许多权威域名服务器 “return different responses based on the perceived topological location of the user”,依据是查询来源的地址,而这通常是你的解析器。RFC 7871 第 1 节 因此,在本地解析的 CDN 主机名可能指向为你的网络挑选的服务器,而代理连接的正是这个地址。出口 IP 在你要求的地区,但它到达的服务器是为你所在的地区挑选的,于是内容可能是另一个地区的。这是从请求的构造方式推出的结论;回环测试没有测量任何 CDN。
- 只有代理能解析的名称会失败。 对一个在本地无法解析的名称,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 的位置该如何考虑。本文余下的部分讲的是如何为每个客户端选对协议写法。
配置里该怎么写
| 客户端 | 把主机名发给代理 | 避免使用 |
|---|---|---|
| curl | socks5h:// 或 --socks5-hostname | socks5:// 和 --socks5 会在本地解析 |
| Requests + PySocks | socks5h:// | socks5:// 会在本地解析 |
| HTTPX + socksio | socks5:// 或 socks5h:// | socks:// 抛出 ValueError |
| aiohttp + aiohttp-socks | socks5:// | socks5h:// 抛出 ValueError |
| Playwright Chromium | socks5:// | socks5h:// 在导航时失败 |
| Node socks-proxy-agent | socks5h:// 或 socks:// | socks5:// 会在本地解析 |
没有哪一个 URL 对六个客户端都正确。如果同一个 PROXY_URL 设置供多个工具使用,socks5h:// 会让 aiohttp-socks 和 Playwright 出错,而 socks5:// 会让 curl、Requests 和 socks-proxy-agent 在本地解析。请按客户端分别设置协议写法。
拒绝 socks5h:// 的两个客户端失败的时机不同。aiohttp-socks 在 ProxyConnector.from_url() 中抛出异常,此时还没有打开任何套接字:
ValueError: Invalid scheme component: socks5hPlaywright 在 chromium.launch() 中接受了这个代理,到第一次导航时才失败,而且没有连接代理:
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 中加入这一行:
127.0.0.2 dnsprobe.example在一个终端中启动服务器,在另一个终端中为每种协议写法发送一个请求:
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 时,服务器打印出:
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 中。
相关内容
- HTTP 与 SOCKS5 代理:如何选择协议,以及 DNS 在哪里运行。
- 代理环境变量:每个客户端读取哪个变量。
- NO_PROXY matching, tested:针对绕过规则的同类测试。
- Playwright 代理设置、HTTPX 异步代理和 Python Requests 代理:完整的客户端配置。
ipvolt 正在为开发者和智能体构建代理基础设施。加入候补名单,开放访问时只发一封邮件。
参考来源
- curl man page: --proxy (socks5:// and socks5h://)
- RFC 1928: SOCKS Protocol Version 5, section 4 (address types)
- Requests: SOCKS proxies
- urllib3 2.8.0 source: contrib/socks.py (scheme to rdns)
- PySocks README: set_proxy and rdns
- HTTPX: SOCKS proxies
- HTTPX 0.28.1 source: proxy schemes in _transports/default.py
- aiohttp-socks 0.12.0 README: ProxyConnector and rdns
- python-socks 3.1.1 source: parse_proxy_url schemes
- socks-proxy-agent source: parseSocksURL
- Chromium network docs: SOCKSv5 proxy scheme and implicit bypass rules
- Playwright: browserType.launch proxy option
- RFC 7871: Client Subnet in DNS Queries, section 1