SOCKS 5 代理协议

参考:
RFC 1928: SOCKS Protocol Version 5
RFC 1929: Username/Password Authentication for SOCKS V5

简介

SOCKS 5描述的协议是对SOCKS 4协议的升级. 关于SOCKS 4的介绍可以查看本博客先前的post: SOCKS 4/4A TCP代理协议

SOCKS 5协议旨在为TCP和UDP域中的C/S架构应用程序提供一种框架, 以便应用程序可以方便, 安全地使用代理服务穿过防火墙. 从概念上来看, SOCKS 5协议作用于应用层和传输层之间, 因此不为网络层提供服务, 例如转发ICMP消息. 相比SOCKS 4, SOCKS 5进行了以下扩展: 支持UDP, 多种通用的认证方案, 支持域名/IPv6的寻址策略.

与SOCKS 4类似, SOCKS 5的服务过程也可以分为两个阶段:

  1. 代理请求: 客户端建立与SOCKS服务端的TCP连接, 并发送代理请求. 服务端可以根据请求的内容回复接受或者拒绝. 若接受请求, 则服务端建立对应的连接为后续数据转发做准备.
  2. 转发数据: 请求过程完成后, SOCKS服务端仅需简单地在客户端与目标主机之间转发数据即可.

SOCKS 5支持TCP和UDP, 同时也定义了CONNECT操作与BIND操作, 并额外定义了一个UDP ASSOCIATE操作.

Note: 尽管都支持CONNECT操作与BIND操作, 但是SOCKS 5与SOCKS 4并不兼容, SOCKS 5定义了新的数据包形式, 所以SOCKS 5服务器无法解析SOCKS 4请求. CONNECT与BIND的一致是概念上的, 而不是形式上的.

基于TCP的客户端操作步骤

当一个TCP客户端希望连接到一个仅能通过防火墙访问的目标主机时, 它可以通过建立一个到SOCKS服务端对应端口的TCP连接来实现. SOCKS服务通常监听1080端口. 客户端到服务端的TCP连接建立成功后, 客户端: 1.进入认证方法协商阶段, 2.使用指定的方法进行身份认证, 3.发送转发请求. 服务端评估转发请求, 建立对应的连接或拒绝请求.

除非特殊说明, 后文数据包中的数字都表示对应字段的字节长度. 0xhh表示该字段的值为指定值. Variable表示对应字段的值的字节长度可变.

客户端连接到SOCKS服务端后首先发送版本标识符/认证方法协商消息:

1
2
3
4
5
6
7
8
9
10
    +-----+----------+----------+
| VER | NMETHODS | METHODS |
+-----+----------+----------+
| 1 | 1 | 1 to 255 |
+-----+----------+----------+

其中:
- VER: SOCKS版本号, 此处为0x05.
- NMETHODS: 客户端支持的认证方法数量.
- METHODS: 客户端支持的认证方法列表, 每一个字节对应一种认证方法.

SOCKS服务端选择指定的认证方法, 并回复客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    +-----+--------+
| VER | METHOD |
+-----+--------+
| 1 | 1 |
+-----+--------+

其中:
- VER: SOCKS版本号, 此处为0x05.
- METHOD: 服务端指定的认证方法
- 0x00: 无需认证.
- 0x01: GSSAPI(Generic Security Services API).
- 0x02: 用户名密码认证.
- 0x03 ~ 0x7F: IANA保留.
- 0x80 ~ 0xFE: 保留的私有方法.
- 0xFF: 没有接受的方法(服务端支持的方法未在客户端提供的方法列表中给出).

随后, SOCKS服务端与客户端进入指定认证方法的认证阶段. 不同的认证方法的认证流程由不同的协议规定, 后面的章节将介绍基于用户名密码的认证方法(RFC 1929).

认证阶段结束后, 客户端将发送代理请求的详细信息. 如果协商的认证方法包括了完整性的校验和/或机密性的封装,这些请求必须使用对应的方法封装.

SOCKS代理请求形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    +-----+-----+-------+------+----------+----------+
| VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+-----+-----+-------+------+----------+----------+
| 1 | 1 | 0x00 | 1 | Variable | 2 |
+-----+-----+-------+------+----------+----------+

其中:
- VER: SOCKS协议版本, 此处为0x05.
- CMD:
- 0x01: CONNECT操作.
- 0x02: BIND操作.
- 0x03: UDP ASSOCIATE操作.
- RSV: 保留字段.
- ATYPE: 目标主机地址类型
- 0x01: IPv4.
- 0x03: Domain name.
- 0x04: IPv6.
- DST.ADDR: 目标主机地址
- IPv4 4个字节.
- Domain name n+1个字节, 第一个字节表示域名长度n, 后续n个字节为域名.
- IPv6 16个字节.
- DST.PORT: 目标主机端口.

身份认证流程结束后, 客户端立即向服务端发送代理请求. 服务端评估请求, 并且返回如下形式的回复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    +-----+-----+-------+------+----------+----------+
| VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+-----+-----+-------+------+----------+----------+
| 1 | 1 | 0x00 | 1 | Variable | 2 |
+-----+-----+-------+------+----------+----------+

其中:
- VER: SOCKS协议版本, 此处为0x05.
- REP: 回复状态代码
- 0x00: 成功.
- 0x01: 服务端错误.
- 0x02: 规则集禁止访问.
- 0x03: 网络不可达.
- 0x04: 主机不可达.
- 0x05: 连接被拒.
- 0x06: TTL超时.
- 0x07: 不支持的CMD操作符.
- 0x08: 不支持的目标主机地址类型.
- 0x09 ~ 0xFF: 未指定.
- RSV: 保留字符, 固定为0x00.
- ATYP: 绑定地址类型
- 0x01: IPv4.
- 0x03: Domain name.
- 0x04: IPv6.
- BND.ADDR: 服务端(与目标主机的连接)绑定的地址.
- BND.PORT: 服务端(与目标主机的连接)绑定的端口.

如果先前选择的认证方法包含用于身份验证, 完整性和(或)机密性的封装, 则SOCKS服务端的回复也将被对应方法封装.

当回复信息为失败(REP不等于0)时, SOCKS服务端必须在发送回复后关闭TCP连接, 这之间的间隔不应该超过10秒.

当回复信息为成功(REP等于0), 并且代理请求的操作类型是CONNCET或BIND时, 客户端接收到回复即可以开始传输数据. 如果先前选择的认证方法包含用于身份验证, 完整性和(或)机密性的封装, 则客户端传输的数据也将被对应方法封装. 类似地, 当目标主机发送给客户端的数据数据到达SOCKS服务端时, 服务端必须按照对应的方法封装后再转发给客户端.

CONNECT

SOCKS服务端对CONNECT请求(客户端代理请求中CMD为0x01)的回复中, BND.PORT表示服务端分配的连接到目标主机的端口号. 同理, BND.ADDR包含被分配的IP. 通常, 服务端提供的用于连接目标主机的IP与客户端连接服务端所使用的IP并不相同, 因为服务端通常是多地址的. 服务端应该使用目标主机地址, 端口以及客户端的源IP, 端口评估是否接受CONNECT代理请求.

BIND

BIND代理请求适用于客户端需要接受从目标主机发起的入站连接的情况. FTP是一个常见的例子, FTP使用客户端到目标主机的主要连接传输命令以及状态报告, 而使用目标主机到客户端的连接传输数据.

SOCKS协议所预期的行为是: 客户端应用协议在使用CONNECT代理请求成功建立主要连接后才使用BIND代理请求用于建立第二个连接. SOCKS服务端将使用BIND代理请求中的目标主机地址及端口评估是否接受请求.

BIND操作期间SOCKS服务端将向客户端回复两次消息. 第一次回复发送于服务端创建并且绑定一个新的socket时. 回复中的BND.PORT字段包含服务端分配的用于监听入站连接的端口. 回复中的BND.ADDR字段包含服务端分配的用于监听入站连接的IP. 客户端需要将这两个字段通知(通过主要连接或者控制连接)目标主机. 第二次回复发送于目标主机到服务端的入站连接建立成功或失败时. 第二次回复中的BND.PORT及BND.ADDR字段包含服务端所连接到的主机的地址.

UDP ASSOCIATE

UDP ASSOCIATE代理请求用于建立UDP连接以转发UDP数据报. DST.ADDR, DST.PORT包含客户端用于发送UDP数据包的地址及端口. SOCKS服务端可以使用这些信息限制访问. 如果客户端在建立UDP连接时不拥有这些信息, 则客户端必须使用全零的端口号和地址.

客户端与SOCKS服务端之间的TCP连接(客户端与服务端协商认证方法, 发送代理请求时所使用的TCP连接)中止时, 后续的UDP连接也随之中止.

SOCKS服务端针对UDP ASSOCIATE的回复中, BND.PORT, BND.ADDR指定了用于转发的地址及端口, 客户端必须发送需要转发的UDP信息到服务端指定的地址及端口.

基于UDP的客户端操作步骤

SOCKS服务端接收来自客户端的UDP ASSOCIATE代理请求, 后会评估代理请求, 请求通过后, 服务端分配一个端口用于监听该客户端的UDP数据报, 并通过代理请求回复消息中的BND.PORT字段通知客户端对应的端口. 运行在这个端口上的监听程序称为UDP转发服务端.

UDP客户端必须发送需要转发的UDP数据报到UDP转发服务端上. 如果先前选择的认证方法包含用于身份验证, 完整性和(或)机密性的封装, 则客户端传输的UDP数据报也将使用对应方法封装. 每一个UDP数据包携带如下头部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    +-----+------+------+----------+----------+----------+
| RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+-----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+-----+------+------+----------+----------+----------+

其中:
- RSV: 保留字段, 必须为0x0000.
- FRAG: UDP数据报的编号.
- ATYPE: 目标主机地址类型.
- 0x01: IPv4.
- 0x03: Domain name.
- 0x04: IPv6.
- DST.ADDR: 目标主机地址.
- IPv4 4个字节.
- Domain name n+1个字节, 第一个字节表示域名长度n, 后续n个字节为域名.
- IPv6 16个字节.
- DST.PORT: 目标主机端口.
- DATA: 携带的数据.

UDP转发服务端决定转发UDP数据包时, 服务端不会向客户端返回任何消息, 仅仅是安静的转发数据. 类似地, 当服务端丢弃UDP数据包或者无法转发时, 也不会通知客户端. 当转发服务端接收到目标主机的回复数据报时, 服务端也会使用上面的头部信息以及身份认证方法对应的方法来封装UDP数据包.

UDP转发服务端必须从SOCKS服务端取得它所服务的客户端IP地址, 并且丢弃任何来源于其他地址的数据报(每个转发服务端都是为了服务唯一一个客户端而建立的).

FRAG字段表示该UDP数据报是不是一系列的数据报中的一个片段. 0x00表示独立的数据报. 1~127表示该片段在整体中的顺序编号, 最高位的1比特用于表示是否为最后一个片段. 对每一个数据报序列, 其消息接收者(转发服务端)都将维护一个组装队列以及组装计时器. 当计时器超时, 或者最新接收到的数据报片段的FRAG字段值小于已接收的数据报片段中的最大值时, 组装队列将被重置并且丢弃已接收的相关数据报片段. 组装计时器的超时时间不能小于5秒. 建议应用程序应该尽量避免碎片化传输.

SOCKS服务端对于是否支持数据报组装是可选的; 不支持的服务端应该丢弃任何FRAG字段不为0x00的数据报.

用户名密码认证过程

当客户端支持用户名密码认证, 并且服务端指定的方法为用户名密码认证时(METHOD字段值为0x02), 客户端与服务端进入身份认证的子协商阶段. 该阶段始于客户端提交的用户名密码请求:

1
2
3
4
5
6
7
8
9
10
11
12
    +-----+------+----------+------+----------+
| VER | ULEN | UNAME | PLEN | PASSWD |
+-----+------+----------+------+----------+
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+-----+------+----------+------+----------+

其中:
- VER: 当前子协商的版本, 此处为0x01.
- ULEN: 用户名的长度.
- PLEN: 密码长度.
- UNAME: 用户名.
- PASSWD: 密码.

服务端将验证用户名和密码, 并回复:

1
2
3
4
5
6
7
8
9
10
11
    +-----+--------+
| VER | STATUS |
+-----+--------+
| 1 | 1 |
+-----+--------+

其中:
- VER: 子协商版本, 此处为0x01.
- STATUS:
- 0x00 - 认证成功.
- 其他 - 认证失败.

若认证失败时, 服务端必须关闭连接. 当认证成功时, 服务端与客户端进入后续的协商阶段(代理请求, 以及评估代理请求).