第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
学习目标
- 逐字节读懂 SOCKS5 握手:方法协商、认证、请求、应答的完整报文
- 分清 SOCKS4 / SOCKS4a / SOCKS5 的能力边界(IPv6、域名、UDP、认证)
- 理解
UDP ASSOCIATE如何中继 UDP,及它为何对 DNS/QUIC/游戏重要 - 用
ssh -D一行起一个真 SOCKS5 代理,抓包对照 第21章 的实现
前置知识
- 第04章 HTTP 代理协议(与之对比)、第21章 Go 手写 SOCKS5(本章是其协议依据)
- RFC 1928 (SOCKS5)、RFC 1929 (用户名密码认证)
原理
SOCKS 是什么、特殊在哪
SOCKS(SOCKet Secure)是一个**会话层(L5)**代理协议(第02章)。和 HTTP 代理最大的不同:
SOCKS 不关心你传的是什么应用协议。 它只负责"帮我建立一条到 host:port 的连接",之后就盲转字节。所以同一个 SOCKS 代理能代理 HTTP、HTTPS、SSH、SMTP、数据库、BitTorrent——任意 TCP,外加 UDP。
这种"协议无关 + 能远端解析 DNS"的组合,是 SOCKS 在隧道/科学上网场景统治地位的原因。
三个版本的能力演进
| 特性 | SOCKS4 | SOCKS4a | SOCKS5 |
|---|---|---|---|
| 目标用 IPv4 | ✅ | ✅ | ✅ |
| 目标用域名(远端 DNS) | ❌(只能传 IP) | ✅ | ✅ |
| 目标用 IPv6 | ❌ | ❌ | ✅ |
| 认证 | 仅 userid | 仅 userid | ✅ 无/用户密码/GSSAPI |
| UDP | ❌ | ❌ | ✅ UDP ASSOCIATE |
| BIND(反向连接) | ✅ | ✅ | ✅ |
现代几乎只用 SOCKS5,下面以它为主。
SOCKS5 完整握手(四步)
┌────────── 阶段1:方法协商 ──────────┐
Client → Proxy: VER | NMETHODS | METHODS...
05 | 01 | 00 "我支持 1 种方法:无认证(00)"
Proxy → Client: VER | METHOD
05 | 00 "就用无认证(00)"
┌────────── 阶段2:认证(若选了 0x02 用户密码,RFC 1929)──────────┐
Client → Proxy: VER | ULEN | UNAME | PLEN | PASSWD
01 | 05 | alice | 06 | secret
Proxy → Client: VER | STATUS
01 | 00 "00=成功,非0=失败"
┌────────── 阶段3:建立连接请求 ──────────┐
Client → Proxy: VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT
05 | 01 | 00 | 03 | 0b "httpbin.org" | 00 50
│CONNECT │域名 长度11 │端口80
Proxy → Client: VER | REP | RSV | ATYP | BND.ADDR | BND.PORT
05 | 00 | 00 | 01 | 0.0.0.0 | 0000 "00=成功"
┌────────── 阶段4:盲转 ──────────┐
此后这条 TCP 连接成为隧道,双向裸转字节(HTTP/TLS/SSH 任意)
字段字典(写代理/抓包时对着查)
CMD(命令):
0x01CONNECT:最常用,建立到目标的 TCP 连接0x02BIND:让代理监听一个端口,等目标回连(FTP 主动模式等,罕用)0x03UDP ASSOCIATE:建立 UDP 中继
ATYP(地址类型):
0x01IPv4(4 字节)0x03域名(1 字节长度 + N 字节,远端 DNS)0x04IPv6(16 字节)
REP(应答码)——排错时极有用:
| 值 | 含义 | 值 | 含义 |
|---|---|---|---|
0x00 | 成功 | 0x05 | 连接被拒绝 |
0x01 | 一般性失败 | 0x06 | TTL 过期 |
0x02 | 规则不允许 | 0x07 | 命令不支持 |
0x03 | 网络不可达 | 0x08 | 地址类型不支持 |
0x04 | 主机不可达 |
对照 第21章 的代码:那里
reply(client, 0x07)正是"命令不支持",0x08是"地址类型不支持",0x05是"连接被拒"——现在你知道每个魔数的来历了。
认证方法(阶段1 的 METHOD):
0x00无需认证0x01GSSAPI0x02用户名/密码(RFC 1929)0xFF无可接受方法(代理拒绝)
UDP ASSOCIATE:SOCKS 如何中继 UDP
TCP 之外,SOCKS5 还能转 UDP——这对 DNS 查询、QUIC/HTTP3、游戏、VoIP 至关重要。机制比 TCP 绕:
1. 客户端用 TCP 连代理,发 CMD=0x03 (UDP ASSOCIATE)
2. 代理回应里给出一个"UDP 中继地址 BND.ADDR:BND.PORT"
3. 客户端把要发的 UDP 数据包,加上 SOCKS UDP 头,发到这个中继地址
4. 那条 TCP 连接保持存活 = UDP 关联存活;TCP 断 = UDP 中继释放
客户端发往中继的每个 UDP 包,前面要加这个头:
+-----+------+------+----------+----------+----------+
| RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+-----+------+------+----------+----------+----------+
| 2 | 1 | 1 | 变长 | 2 | 变长 |
+-----+------+------+----------+----------+----------+
全0 分片号 地址类型 目标地址 目标端口 真实载荷
注意:很多 SOCKS 代理只实现了 TCP(CONNECT),不支持 UDP ASSOCIATE。
ssh -D的 SOCKS 代理就不支持 UDP。这是排查"SOCKS 代理下 DNS/QUIC 不通"的头号原因。
SOCKS vs HTTP 代理:怎么选
| 维度 | HTTP 代理 | SOCKS5 代理 |
|---|---|---|
| 协议层 | L7(明文 HTTP)/ L4(CONNECT 隧道) | L5(会话层) |
| 能代理什么 | HTTP 直转;其它靠 CONNECT 隧道 | 任意 TCP + UDP |
| 看得懂应用内容 | 明文 HTTP 看得懂 | 完全看不懂(纯隧道) |
| 能改写/缓存 HTTP | ✅ | ❌ |
| 远端 DNS | ✅(默认) | ✅(域名 ATYP) |
| 认证 | Basic/Digest(407) | 无/用户密码/GSSAPI |
| 典型用途 | 网页代理、缓存、企业出网审计 | 通用隧道、SSH 动态转发、科学上网 |
一句话:要在 HTTP 层做文章用 HTTP 代理;要通用透明地隧道任意流量用 SOCKS5。
️ 实现 / 命令
实验一:ssh -D 一行起一个真 SOCKS5 代理
SSH 自带 SOCKS5 服务端,这是最方便的真代理:
# -D 1080:在本地 1080 起一个 SOCKS5 代理,流量经 SSH 隧道从 jump-host 出去
ssh -D 1080 -N -C user@jump-host &
# 用它(远端 DNS,不泄漏)
curl --socks5-hostname 127.0.0.1:1080 https://httpbin.org/ip
# {"origin": "<jump-host 的公网IP>"} ← 源站看到的是跳板机 IP
这条命令把 第01章(隐藏客户端)、第03章(远端 DNS 不泄漏)、本章(SOCKS5)全串起来了。
实验二:抓包对照报文图
# 一边抓 1080,一边发请求
sudo tcpdump -i lo -n -X 'tcp port 1080' &
curl --socks5-hostname 127.0.0.1:1080 http://httpbin.org/ip -s -o /dev/null
在抓包的十六进制里,你会逐字节看到上面的报文图:
05 01 00 ← 阶段1:VER=5, NMETHODS=1, METHOD=00
05 00 ← 代理回:选 00 无认证
05 01 00 03 0b 68 74 74 70 ... ← 阶段3:CONNECT, 域名, 长度0b(11), "httpbin..."
... 00 50 ← 端口 0x0050 = 80
05 00 00 01 00 00 00 00 00 00 ← 代理回:REP=00 成功
把这段十六进制和 第21章 的 handle() 函数并排看,协议就真正刻进脑子了。
实验三:验证 SOCKS 不支持 UDP 时 DNS 怎么办
# ssh -D 的 SOCKS 不支持 UDP ASSOCIATE。用 --socks5(本地DNS)时 DNS 走本地 UDP(没经代理)
# 用 --socks5-hostname 时,DNS 由"代理远端"用 TCP 帮你查(绕开了 UDP 限制)
# 这就是为什么科学上网客户端要么用支持 UDP 的代理,要么强制 DNS-over-TCP/远端解析
curl --socks5-hostname 127.0.0.1:1080 https://example.com/ -s -o /dev/null -w "%{http_code}\n"
# 200(域名由远端解析,绕开了本地 UDP DNS)
排错
| 现象 | REP / 错误 | 根因 |
|---|---|---|
curl: (97) SOCKS5 ... Host unreachable | REP=0x04 | 代理连不上目标主机 |
curl: (97) ... connection refused | REP=0x05 | 目标端口没监听 |
... command not supported | REP=0x07 | 用了 BIND/UDP 但代理只支持 CONNECT |
| SOCKS 下 DNS/QUIC 不通 | — | 代理不支持 UDP ASSOCIATE(如 ssh -D) |
| 想隐藏访问目标却被本地 DNS 看到 | — | 用了 --socks5(本地 DNS);改 --socks5-hostname |
| 认证失败 | 阶段2 STATUS≠0 | 用户名/密码错,或代理要 GSSAPI |
代理回 0xFF 后断开 | METHOD=0xFF | 客户端给的认证方法代理都不接受 |
本章小结
- SOCKS 是会话层、协议无关的代理:建好到 host:port 的连接后只盲转字节,能转任意 TCP/UDP。
- SOCKS5 握手四步:方法协商 → 认证 → CONNECT 请求(ATYP) → 应答(REP),全是紧凑二进制。
- UDP ASSOCIATE 中继 UDP(DNS/QUIC/游戏),但很多代理不实现,是不通的常见原因。
- 选型:HTTP 层做文章用 HTTP 代理;通用隧道任意流量用 SOCKS5。
ssh -D是最顺手的真 SOCKS5。
下一章 第07章,我们回到 HTTP 世界的前沿:HTTP/2 的多路复用、gRPC 的流、HTTP/3(QUIC) 的连接迁移,给代理带来了哪些全新挑战。