-------------本文仅用于学习记录使用
一 、SOCKS5 协议原理详解
SOCKS 全称socket secure,是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。在OSI模型中,SOCKS是会话层的协议,位于表示层与传输层之间,最新协议是SOCKS5。
SOCKS5的特点
1.SOCKS5相比于SOCKS4,加入了UDP协议支持,在框架上加入了强认证功能,并且地址信息也加入了域名和IPV6 的支持。
2、SOCKS5服务器在将通讯请求发送给真正服务器的过程中,对于请求数据包本身不加任何改变,只是传递数据包,而不关心是何种应用协议,所以SOCKS代理服务器比应用层代理服务器更快。
3.与VPN(虚拟专用网络)相比,SOCKS5可以代理应用层的某些应用,而不是代理全局网络,而VPN控制的是你电脑的整个网络,只要需要连接到互联网的流量都会经过VPN。
1、交互原理
①首先客户端向代理服务器发出请求信息,用以协商版本和认证方法。
随后代理服务器应答,将选择的方法发送给客户端。
②客户端和代理服务器进入由选定认证方法所决定的子协商过程,子协商过程结束后,
客户端发送请求信息,其中包含目标服务器的IP地址和端口。代理服务器验证客户端身份,通过后会与目标服务器连接,目标服务器经过代理服务器向客户端返回状态响应。
③连接完成后,代理服务器开始作为中转站中转数据。
2、交互原理详解
【1】认证
(图示上面描述,下面为占字节数)
①客户端向代理服务器发送代理请求,其中包含了代理的版本和认证方式:
VER:1字节,版本号,固定为0x05,代表使用SOCKS5协议
NMETHODS(1字节):方法数目,表示后面的方法编号列表(METHODS字段)中有多少种客户端支持的认证方法
METHOD(1-255字节):方法编号列表,存储客户端支持的认证方法的编号
【2】代理服务器从给定的方法编号列表中选择一个方法并返回选择报文
METHOD字段用来返回选择的方法编号
支持的认证方式有(16进制表示):
0x00: 不需要认证
0x01: GSSAPI认证
0x02: 用户名和密码方式认证
0x03: IANA认证
0x80-0xfe: 保留的认证方式
0xff: 不支持任何认证方式,当客户端收到此信息必须关闭连接。
【3】协商完成后,客户端向代理服务器发送代理请求,客户端会向代理服务器发送下面格式的请求
CMD:指令编号,
0x01 CONNECT 指令,用于 TCP 代理
0x02 BIND 指令,一般用于要客户端主动接受来自服务器连接时
0x03 UDP ASSOCIATE 指令,用于 UDP 代理
RSV:保留字段:必须为 0
ATYP:地址类型:
0x01 表明地址为(DST.ADDR字段)IPV4 地址,长度为4字节
0x03域名,表明地址为域名,第一个字节用作域名的长度标识
0x04 表明地址为IPV6 地址,长度为16字节
DST.ADDR:目标地址:4个字节,要访问的目标服务器的地址或域名,类型由ATYP字段决定
DST.PORT:目标端口号:目标地址对应的端口号。
【4】SOCKS 服务端会根据请求类型和源、目标地址,执行对应操作,并且返回对应的一个或多个报文信息,格式如下:
REP:请求的结果,
0x00 成功
0x01常规 SOCKS 服务故障
0x02 规则不允许的连接
0x03 网络不可达
0x04 主机无法访问
0x05 拒绝连接
0x06 连接超时
0x07 不支持的命令
0x08 不支持的地址类型
RSV:保留字段:必须为 0
ATYP:地址类型
BND.ADDR:绑定地址:即请求成功后客户端需要连接的代理服务器的地址或域名,客户端之后的通信均通过改地址对应的服务器
BND.PORT:绑定端口号:绑定地址对应的端口号
【5】当代理服务器返回成功消息,则后续客户端通过绑定地址和绑定端口号与代理服务器通信,由代理服务器转发客户端的请求到目标服务器,并将目标服务器的响应转发给客户端。
3、案例详解
#1、握手阶段:接收客户端发送的握手请求并返回握手响应信息。
#2、请求阶段:接收客户端发送的请求信息,提取出请求的目标地址和端口号。
#3、响应阶段:向客户端发送请求成功的响应信息,并尝试连接目标服务器。
#4、转发阶段:建立客户端与目标服务器之间的连接,并进行数据转发。
#在使用 Python 实现 SOCKS5 代理服务器时,handle() 函数通常是用于处理客户端连接的回调函数,当有新的客户端连接请求到达时(新的连接请求,比如socket connnect 建立之后 就传输数据包了,断线重连会再次触发handle),该函数将被触发执行。具体来说,在 Socket 编程中,可以使用 socketserver 模块提供的 StreamRequestHandler 类来实现一个 SOCKS5 代理服务器。而 StreamRequestHandler 类中的 handle() 方法就是用于处理客户端连接请求的回调函数。当有新的客户端连接请求到达时,handle() 方法将被自动调用,并传入已连接的客户端 Socket 对象和客户端地址信息。
def handle(self):
sock = None
remote = None
try:
sock = self.connection #当前连接的套接字对象
# 1、二中【1】【2】步、代理服务器接收客户端发送的握手请求(代理客户端连接的时候是sock5协议,如proxydroid,发过来就是eg:050100 05 1字节 表示socks5协议的版本号),并向客户端发送握手响应无需认证返回
data = sock.recv(262) #读取一定字节即可 ,如1024或其他
data = '0500' #16进制 05 代表使用SOCKS5协议 00: 不需要认证
data = binascii.unhexlify(data)
sock.send(data)
# 2. 二中【3】步,
data = self.rfile.read(4)
# self.rfile 指的是客户端连接套接字对象的输入流,用于从客户端向代理服务器发送数据。先读到ATYP-地址类型
data = binascii.hexlify(data)
mode = data [ 1 ] #CMD
addrtype = data [ 3 ] #ATYP:地址类型:
#DST.ADDR
if addrtype == 1:
addr = socket.inet_ntoa(self.rfile.read(4))
#如果 ATYP 的值为 0x01,表示请求的是 IPv4 地址,则需要从数据包中提取出 4 字节的二进制格式地址,并使用 socket.inet_ntoa() 函数将其转换成点分十进制格式的 IP 地址
elif addrtype == 3: # Domain name
addr = self.rfile.read(ord(sock.recv(1) [ 0 ]))
#如果 ATYP 的值为 0x03,表示请求的是域名地址,则需要先提取出一个字节作为域名长度,然后再提取出对应长度的字节作为域名信息
#DST.PORT
port = struct.unpack('>H', self.rfile.read(2))
#3、二中【4】步,SOCKS 服务端会根据请求类型和源、目标地址,执行对应操作,并且返回对应的一个或多个报文信息
reply = b'05000001' # ①05 VER socks5协议 ②00 REP 成功 ③00 RSV:保留字段:必须为 0 ④01:ATYP:地址类型 IPv4
reply = binascii.unhexlify(reply)
try:
if mode == 1: # CMD:指令编号,0x01 CONNECT 指令,用于 TCP 代理
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote.connect((addr, port [ 0 ])) # 和游戏服务器建立连接
else:
reply = b'05070001' # REP 0x07 不支持的命令
#BND.ADDR:绑定地址:即请求成功后客户端需要连接的代理服务器的地址或域名,客户端之后 的通信均通过改地址对应的服务器
#BND.PORT:绑定端口号:绑定地址对应的端口号
local = remote.getsockname()
reply += socket.inet_aton(local [ 0 ]) + struct.pack(">H", local [ 1 ])
except socket.error:
# REP 0x05 拒绝连接
reply = b'05050001000000000000'
reply = binascii.unhexlify(reply)
#组装响应完毕,回过去
sock.send(reply)
# 4、二中【5】步,使用select模块来实现双向数据传输,将客户端和目标服务器之间的数据互相转发。
if reply [ 1 ] == 0: # 3、中②00 REP 成功
if mode == 1: # 1. Tcp connect
sockets = [sock, remote]
while True:
rlist, wlist, xlist = select.select(sockets, [], sockets, 5)
if xlist:
break
for rsock in rlist:
wsock = remote_socket if rsock is client_socket else client_socket
data = rsock.recv(4096)
if not data:
sockets.remove(rsock)
continue
wsock.sendall(data)
except socket.error:
pass
except IndexError:
pass