对于服务器而言,SOCK5协议的流程可用四个步骤解释:
协商 > 验证 > 接收请求 > 转发数据
SOCK5在RFC1928及RFC1929中有详细说明,RFC1928描述了SOCK5的“协商、接收请求”,RFC1929描述了SOCK5的“验证”过程。
对于上面的四个步骤,可简要地概述如下(以客户端对过代理connect远程主机为例):
1、 在SOCK5的协商过程中,客户端向服务器发送了SOCK代理的版本号(对于SOCK5而言当然就是0x05了),以及客户端支持的验证方法,服务器将会接收到以下数据结构的数据包:
-------------------------------------------------------------------------------------------
| 0x05 | Len | m.1 | m.2 | m.3 | … … | m.n |
-------------------------------------------------------------------------------------------
0x05 : 是版本号,在“协商、验证、接收请求”的过程中都会在数据报头中
Len : 批明了客户端支持的验证方法的数据,每个验证方法占用一个字节的空间
常见的验证方法是“用户名 - 密码”、“不需要验证”,它们的代码分别为 0x02、0x00
如果客户端只支持以上两种验证的话,就会发送如下的数据包给服务器:
---------------------------------------------------------------------
| 0x05 | 0x02 | 0x00 | 0x02 |
---------------------------------------------------------------------
0 1 2 3 4
SOCK5代理服务器接收到客户端的信息后,会先检查版本号,如果为0x05的话,再检查是否支持客户的验证方式。根据上面的假设,客户端发送了0x00、0x02的验证代码号给服务器,SOCK5服务如果是需要“用户名 - 密码”的话,就返回0x02,如果是不需任何验证,就返回0x00,但如果这两种方式都不支持,那就返回0xff。回应客户端的数据包格式为:
---------------------------------------
| 0x05 | 验证方法 |
---------------------------------------
0 1 2
2、 如果上面的协商过程中,服务器所支持的是“用户名 - 密码”验证方式,那就需要进行验证过程,验证过程就是客户端发送密码给SOCK5服务器,服务器
判断其正确性再回应客户端的一个过程。
客户端发送验证信息的数据包格式如下:(假设“用户名长度为m”)
------------------------------------------------------------------------------------------------
| 0x05 | 用户名长度 | 用户名 | 密码长度 | 密码 |
------------------------------------------------------------------------------------------------
0 1 2 m m+1 n
SOCK5服务器的应答数据包格式如下:
---------------------------------------
| 0x05 | 状态码 |
---------------------------------------
0 1 2
格式中的数据项与前面的“协商”部分相似,至于服务器发送出去的状态码,如果为 0x00 ,则表明验证成功,否则验证失败。
3、 进行了验证过程后(对于无需任何验证的0x00,不会经过上面2的步骤),服务器就等待接收客户要连接的远程主机了,客户端会发送如下结构的数据给SOCK5
代理服务器:
------------------------------------------------------------------------------------------
| 0x05 | cmd | 0x00 | atyp | dst_addr | dst_port |
------------------------------------------------------------------------------------------
0 1 2 3 4
cmd : 指明客户端请求代理服务做的动作,这里的例子为为TCP Connect连接(也是最为常见的动作),其值为 0x01
atyp : 指明dst_addr的类型,对于IPv4地址,其值为 0x01
dst_addr : 在这里为IPv4地址,占4个字节
dst_port : 要连接的远程主机的端口号,占2个字节
服务器接收收该请求后,就会连接远程主机,回应客户端如下数据结构的信息:
------------------------------------------------------------------------------------------
| 0x05 | rep | 0x00 | atyp | dst_addr | dst_port |
------------------------------------------------------------------------------------------
rep : 如果服务器连接成功,其值为 0x00 ,其他的值可以参考RFC,也可以笼统地设置成 0xff
atyp : 与客户端的值一样,为 0x01 (表示后面的地址类型为 IPv4)
dst_addr : SOCK5服务器连接到远程主机的IP地址
dst_port : SOCK5服务器连接到远程主机的端口号
4、 当顺利地通过了上面的步骤以后,服务器就能给客户端转发数据了。转发过程可以描述为“收到数据.远程主机 – 发送数据.客户端”或者是“收到数据. 客户
端– 发送数据. 远程主机”的形式,在Windows的WinSock2.0下,如果SOCK5代理服务器与客户连接的套接字为 sClt ,而与远程主机连接的套接字为 sRmt ,那么伪语言表示为:
while(无错)
{
有数据到达;
if(是远程主机发送过来的)
{
recv(sRmt, buf);
send(sClt, buf);
}
else
{
recv(sClt, buf);
send(sRmt, buf);
}
}
在实现SOCK5代理服务器的时候,关键在于转发这部分,不仅仅因为转发是目的之根本,也因为在转发的时候得考虑系统及网络资源的占用,我在实现的时候采用了“每个客户端一个线程 + select模型”,这样子的话可以简化设计及实现,而且可用阻塞send(在非阻塞模型中,send会立即返回,但并不说明数据完全发送,在WSAEWOULDBLOCK错误发生时还得保存下未发送的数据,在一方接收慢另一方接收快的情况下可能会造成服务器缓存数据过多 … …. 等等)。当然,如果要实现支持代理大量客户端的话,还得优化线程及对数据的收发。