socks5代理协议
最近用到ssh正向和反向端口转发,顺便看到动态端口转发实际上使用的是本地socks代理,于是对socks协议进行了学习,同时参考了其他网友的帖子,记录如下:
socks的使用场合就不啰嗦了,拓扑如下
客户端[socks客户端](C_ip,C_port)<------>(proxyIn_ip,proxyIn_port)socks服务器(proxyOut_ip,proxyOut_port)<----->(S_ip,S_port)-服务器(目标主机)
客户端:
socks实际上也有客户端,一些支持socks代理的应用程序自己集成实现了socks客户端功能,就不需要单独的socks客户端了;
而对于没有实现socks客户端功能的应用程序就只能使用proxifier、sockscap64、sstap等全局代理软件作为socks客户端,负责和socks服务器通信实现代理,
这类全局代理程序通常都是拦截普通通信socket实现代理功能。
socks服务器:
一般是多网络连接(multi-homed)服务器安装socks服务端软件作为socks服务器,windows上常见的如ccproxy等;
多网络连接其中一个网络接口连接客户端,另一接口连接服务器(目标主机)网络。
服务器(目标主机):
客户端最终要访问的应用服务器,通常也叫目标主机。
socks服务器通常监听于tcp知名端口1080,socks客户端和socks服务器控制通信是建立在tcp连接之上,
建立好tcp连接后,开始进行socks通信,按照协商功能分类可以分为三个阶段:
阶段一:认证方法协商
1、tcp连接成功后首先由客户端发起认证方法协商消息,格式如下:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
VER: socks协议版本号,最新为5
NMETHODS:客户端支持的认证方法数量
METHODS:当前定义的方法如下(每种方法占一个字节):
- X'00' NO AUTHENTICATION REQUIRED (不认证)
- X'01' GSSAPI
- X'02' USERNAME/PASSWORD (用户密码认证)
- X'03' to X'7F' IANA ASSIGNED (IANA分配)
- X'80' to X'FE' RESERVED FOR PRIVATE METHODS (为私有方法预留)
报文封装内容如下:
2、代理服务器向客户端回应方法选择消息的格式:
+----+--------+ |VER | METHOD | +----+--------+ | 1 | 1 | +----+--------+
VER: socks协议版本号,和客户端的一致
METHOD: 认证方法。代理服务器从客户端提供的认证方法中选择一个自己支持的,如果不支持就用X'FF'表示拒绝认证
报文封装内容如下,选择0x2用户密码认证:
阶段二:代理服务器认证客户端(RFC1928定义为方法相关的子协商):
The client and server then enter a method-specific sub-negotiation.
客户端向代理服务器发送用户名和密码
代理服务器回应认证结果,成功为x00,非x00表示失败
阶段三:请求代理:
客户端在认证成功之后,需要发送一个数据包来请求服务端
+-----+-----+-------+------+----------+----------+
| VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+-----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+-----+-----+-------+------+----------+----------+
VER: 协议版本号,此处为X'05'
CMD: 指定请求代理的方式
- CONNECT X'01'
- BIND X'02'
- UDP ASSOCIATE X'03'
RSV: 预留位置,标准Socks5应为X'00'
ATYP: 指定DST.ADDR的类型
- IPV4地址: X'01'
- 域名: X'03'
- IPV6地址: X'04'
DST.ADDR: 目标的地址,长度由ATYP指定
- IPV4: 4个byte
- 域名: 第一个byte指定长度n,其后跟着n个byte标识域名
- IPV6: 16个byte
DST.PORT: 目标的端口
客户端在认证成功之后,需要发送一个数据包来请求服务端:
+-----+-----+-------+------+----------+----------+
| VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+-----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+-----+-----+-------+------+----------+----------+
VER: 协议版本号 X'05'
REP: 回复请求的状态
- X'00' 成功代理
- X'01' SOCKS服务器出现了错误
- X'02' 不允许的连接
- X'03' 找不到网络
- X'04' 找不到主机
- X'05' 连接被拒
- X'06' TTL超时
- X'07' 不支持的CMD
- X'08' 不支持的ATYP
- X'09' to X'FF' Socks5标准中没有分配对应的状态
RSV: 预留位
ATYP: BND.ADDR的类型
- IPV4: X'01'
- 域名: X'03'
- IPV6: X'04'
BND.ADDR: 服务端指定的地址,长度由ATYP指定
- IPV4: 4个byte
- 域名: 第一个byte指定长度n,其后跟着n个byte标识域名
- IPV6: 16个byte
BND.PORT: 服务端指定的端口
CONNECT 方法:
这个是最常用的代理方法,当服务端接收到的数据包中 CMD 为 0x01 时,服务器使用 Connect 方法进行代理。
此时,服务端尝试使用TCP协议连接对应的 (DST.ADDR, DST.PORT),根据连接的情况,决定 REP 的值。
如果连接成功,回复的数据包中的 BND.ADDR,BND.PORT 没有太大的意义,象征性的填写Socks 服务端在此次连接中使用的 ADDR 和 PORT 即可。
BIND 方法:
Bind方法使用于目标主机需要主动连接客户机的情况(如 FTP 协议主动模式)
当服务端接收到的数据包中 CMD 为 0x02 时,服务器使用 Bind 方法进行代理。使用 Bind 方法代理时服务端需要回复客户端至多两次数据包。
服务端使用 TCP 协议连接对应的 (DST.ADDR, DST.PORT),如果失败则返回失败状态的数据包并且关闭此次会话。如果成功,则监听 (BND.ADDR, BND.PORT) 来接受请求的主机的请求,然后返回第一次数据包,该数据包用以让客户机发送指定目标主机连接客户机地址和端口的数据包。
在目标主机连接服务端指定的地址和端口成功或失败之后,回复第二次数据包。此时的(BND.ADDR, BND.PORT) 应该为目标主机与服务端建立的连接的地址和端口。
FTP 实例:
FTP Client – SOCKS Client – SOCKS Server – FTP Server
1.FTP Client 试图建立 FTP 控制流。SOCKS Client 向 SOCKS Server 发送 CONNECT 请求,后者响应请求,最终FTP控制流建立。CONNECT 请求包中指明 FTPServer.ADDR / FTPServer.PORT。
2. FTP Client 试图建立 FTP 数据流。SOCKS Client 建立新的到 SOCKS Server 的 TCP 连接,并在新的 TCP 连接上发送 BIND 请求。BIND 请求包中仍然指明 FTPSERVER.ADDR / FTPSERVER.PORT。
3. SOCKS Server 收到 BIND 请求,创建新套接字,侦听在 AddrA/PortA 上,并向 SOCKSClient 发送第一个 BIND 响应包,包中 BND.ADDR/BND.PORT 即 AddrA/PortA。
4. SOCKS Client 收到第一个 BIND 响应包。FTP Client 通过 FTP 控制流向 FTP Server 发送 PORT 命令,通知 FTP Server 应该主动建立到 AddrA/PortA 的 TCP 连接。
5. FTP Server 收到 PORT 命令,主动建立到 AddrA/PortA 的 TCP 连接,假设 TCP 连接相关四元组是: AddrB,PortB,AddrA,PortA
6. SOCKS Server 收到来自 FTP Server 的 TCP 连接请求,向 SOCKS Client 发送第二个BIND 响应包,包中 BND.ADDR/BND.PORT 即 AddrB/PortB。然后 SOCKS Server 开始转发FTP数据流。
整个过程完成后建立两条连接:控制连接(红色)和数据连接(蓝色)
UDP ASSOCIATE 方法:
使用 UDP ASSOCIATE 时,客户端的请求包中 (DST.ADDR, DST.PORT) 不再是目标的地址,而是客户端指定本身用于发送 UDP 数据包的地址和端口。
为了支持 UDP 转发,服务端应该建立一个 UDP 服务器用以接收客户端发送过来的 UDP 包。然后在TCP连接中回复一个数据包,(BND.ADDR, BND.PORT)应该是UDP服务器绑定的地址与端口。
UDP服务器接收到信息之后,对来源进行筛选,如果收到的UDP包来自第一次收到的请求包中的 (DST.ADDR, DST.PORT),那么,就认为是来自客户端,需要对数据包的头部进行解析,获取到该数据包需要到达的地址
+-----+------+------+----------+----------+----------+
| RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+-----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+-----+------+------+----------+----------+----------+
RSV: 保留字段,默认X'0000'。
FRAG: 该数据包的片段序号,如果值为X'00'则说明该数据包为独立数据包,如果为1~127的某个值,则说明为整个数据包的一个片段。
当收到数据包的片段之后,如果构建的Socks5代理服务器支持UDP分包代理,那么需要接收包之后,进行一个缓存拼接,去掉头部信息之后根据序号从小到大拼接。但这种方法较为复杂,服务器可以不接受此种方式。即,丢弃所有FRAG≠X'00'
的数据包。
ATYP: 指定DST.ADDR的类型
- IPV4: X'01'
- 域名: X'03'
- IPV6: X'04'
DST.ADDR: 该数据包希望到达的目标地址
DST.PORT: 该数据包希望到达的目标端口
DATA: 实际要传输的数据
数据交互
代理UDP业务