本文内容:
本文主要分析Socks5的TCP连接过程,对官方RFC文档进行翻译和解读
然后简单用Go语言实现了一下Socks5的TCP连接转发
最后对数据包进行分析,分析握手连接过程中的字节信息变化
RFC文档解读
RFC1928:https://tools.ietf.org/html/rfc1928
客户端第一次请求
当客户端向服务端发送连接请求的时候,会发送如下信息:
VER | NMETHODS | METHODS |
---|---|---|
0x05 | 1 | 1 TO 255 |
VER的值当被设置为0x05,标明当前Socks的版本信息
NMETHODS(方法选择)中包含在METHODS中出现的方法标识的数据(用字节表示)
服务端第一次回复
VER | METHOD |
---|---|
0x05 | 1 |
- VAR被设置为0x05,标明所选Socks的版本
- METHOD参数如下:
- 0x00,说明服务端连接无需经过验证(NO AUTHENTICATION REQUIRED)
- 0x01,通用安全服务应用程序接口(GSSAPI)
- 0x02,要求用户名/密码认证(USERNAME/PASSWORD)
- 0x03 to 0x7F,IANA 分配(IANA ASSIGNED)
- 0x80 TO 0xFE,私人方法保留(RESERVED FOR PRIVATE METHODS)
- 0xFF,则说明服务端不支持客户端列出的所选方法,此时客服端应该关闭连接(NO ACCEPTABLE METHODS)
客户端第二次请求
当客户端与服务端关于方法的协商完毕后,客户端将发送完整的详细请求信息
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
0x05 | 1 | 0x00 | 1 | Variable | 2 |
VER的值当被设置为0x05,标明当前Socks的版本信息
CMD参数如下:
- 0x01,建立连接(CONNECT)
- 0x02,绑定(Bind)
- 0x03,使用UDP(UDP ASSOCIATE)
RSV,保留字段,被设置为0x00
ATYP,目标地址类型,参数如下:
- 0x01,IPV4地址,长度为4个字节
- 0x03,域名,其中第一个字节用来标识域名的长度
- 0x04,IPV6地址,长度为16个字节
DST.ADDR,目标地址(长度可变)
DST.PORT,目标端口
服务端第二次回复
客户端与服务端建立连接并完成请求后,便立即发送SOCKS请求信息认证协商
服务器评估该请求,并返回如下形式的回复:
VER | REP | PSV | ATYP | BND.ADDR | BND.PORT |
---|---|---|---|---|---|
0x05 | 1 | 0x00 | 1 | Variable | 2 |
- VER的值当被设置为0x05,标明当前Socks的版本信息
- REP,回复字段,参数如下:
- 0x00, 连接成功(succeeded)
- 0x01,服务器发生故障(general SOCKS server failure)
- 0x02,由于规则连接不容许(connection not allowed by ruleset)
- 0x03,网络不可达(Network unreachable)
- 0x04,主机不可达(Host unreachable)
- 0x05,链接被拒绝(ConnectiSSon refused)
- 0x06,TTL终止失效(TTL expired)
- 0x07,命令不被支持(Command not supported)
- 0x08,地址类型不被支持(Address type not supported)
- 0x09 to 0xFF,没有被设计(unassigned)
- PSV,保留字段,设置为0x00
- AYTP,目标地址,参数如下:
- 0x01,IPV4地址,长度为4个字节
- 0x03,域名,其中第一个字节用来标识域名的长度
- 0x04,IPV6地址,长度为16个字节
- BND.ADDR,服务器绑定地址
- BND.PORT,服务器绑定端口
对于不同CMD的请求,BND.ADDR和BND.PORT有如下区别:
CONNECT
在对CONNECT的答复中
BND.PORT包含服务器分配用于连接到目标主机的端口号,而BND.ADDR包含关联的IP地址
服务端提供的BND.ADDR通常不同于客户端用于访问*SOCKS *服务器的IP地址,因为此类服务器通常是多宿主的
SOCKS服务器将使用DST.ADDR和DST.PORT以及客户端源地址和端口来评估CONNECT请求。
BIND(这个好像没啥用)
BIND请求用于要求客户端接受来自服务器的连接的协议中。
FTP是一个众所周知的示例,它使用主要的客户端到服务器连接来发送命令和状态报告
但可以使用服务器到客户端连接来按需传输数据(例如LS,GET,PUT)。UDP ASSOCIATE
用于处理UDP请求报文
DST.ADDR和DST.PORT字段包含客户端期望用于在其上发送UDP数据报以进行关联的地址和端口。
服务器可以使用该信息来限制对关联的访问
如果客户端在UDP ASSOCIATE时没有发送信息,则客户端必须使用全零的端口号和地址。
当UDP ASSOCIATE请求到达的TCP连接终止时,UDP关联终止。
在对UDP ASSOCIATE请求的回复中,BND.PORT和BND.ADDR字段指示客户端必须在其中发送要中继的UDP请求消息的端口号/地址。
回复处理
当回复(REP值不是0x00)表示失败时,
SOCKS服务器务必在发送回复后不久终止TCP连接。在检测到导致故障的情况后,该时间不得超过10秒。
如果回复(REP值为0x00)表示成功,并且请求是BIND或CONNECT
则客户端现在可以开始传递数据
完整的连接流程
- 向服务器的端口建立TCP连接。
- 向服务器发送
0x05 0x01 0x00
- 如果接到
0x05 0x00
则是可以代理 - 发送
0x05 0x01 0x00 地址类型 + 目的地址 + 目的端口
- 接受服务器返回的自身地址和端口,连接完成以后操作和直接与目的方进行TCP连接相同。
Go语言实现(Demo)
package main |
这里主要通过对RFC1928
中规定的连接过程进行了实现
同时利用Go语言的网络方面的并发高性能进行TCP连接与转发
代码十分简陋,只能处理无需认证的Socks5请求,大概流程都写了注释,方便参考过程
比如著名SSR
等工具,会对数据进行加密转发,同时考虑到了UDP
转发以及更多的误处理(不可多言-_-)
数据包分析
下载地址:Socks5流量包
以上是我根据Go
语言写的代码运行抓包而来
这里分析数据交换过程
由于本代码没有设置KeepLive
,所以会重复进行TCP
三次握手
实际的Socks5
握手在如图的红色框中的数据包[TCP segment of a reassembled PDU]中
这里我们追踪其中一条即可
客户端第一次请求
这里客户端会向服务端发送连接请求
由于这里无需验证,所以会请求0x05作为版本号,同时发送0x00表明自己是无需验证的连接
服务端第一次回复
原理一致,表明版本号以及说明自己支持无需验证的连接方式
客户端第二次请求
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
0x05 | 1 | 0x00 | 1 | Variable | 2 |
- VER的值当被设置为0x05,标明当前Socks的版本信息
- CMD参数:0x01,建立连接(CONNECT)
- RSV,保留字段,被设置为0x00
- ATYP,目标地址类型:0x03,域名,其中第一个字节用来标识域名的长度,这里为0x0d
- DST.ADDR,这里为
77 77 77 2e 62 61 69 64 75 2e 63 6f 6d
,指www.baidu.com
- DST.PORT,这里为
01 bb
,转化为十进制为443
端口
服务端第二次回复
VER | REP | PSV | ATYP | BND.ADDR | BND.PORT |
---|---|---|---|---|---|
0x05 | 1 | 0x00 | 1 | Variable | 2 |
VER的值当被设置为0x05,标明当前Socks的版本信息
REP,回复字段:0x00, 连接成功(succeeded)
PSV,保留字段,设置为0x00
AYTP,绑定地址类型:0x01,IPV4地址,长度为4个字节
在对CONNECT的答复中
BND.PORT包含服务器分配用于连接到目标主机的端口号,而BND.ADDR包含关联的IP地址
BND.ADDR,这里设置为
00 00 00 00
BND.PORT,服务器绑定端口,这里为
00 00
到此握手结束,可以发送数据了,总体流程如下:
总之,本文只是简单介绍了Socks5
的连接过程以及数据包分析
如果对Socks5
有更多的应用以及学习兴趣的话
建议学习ShadowSocks(逃…….