socks5 协议简介
rfc1928
socks是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。根据OSI七层模型来划分,SOCKS属于会话层协议,位于表示层与传输层之间。
当防火墙后的客户端要访问外部的服务器时,就跟socks代理服务器连接。该协议设计之初是为了让有权限的用户可以穿过过防火墙的限制,使得高权限用户可以访问外部资源。经过10余年的时间,大量的网络应用程序都支持socks5代理。
这个协议最初由David Koblas开发,而后由NEC的Ying-Da Lee将其扩展到版本4,最新协议是版本5,与前一版本相比,socks5做了以下增强:
- 增加对UDP协议的支持;
- 支持多种用户身份验证方式和通信加密方式;
- 修改了socks服务器进行域名解析的方法,使其更加优雅;
-
某服务器的防火墙仅允许部分端口(如http的80端口)通信,那么可以利用socks5协议和一个打开80端口监听的socks5服务器连接,从而可以连接公网上其他端口的服务器。利用一些额外的技术手段,甚至可以骗过内部的http代理服务器,这时在使用内网http代理上网的环境下也可以不受限制的使用网络服务,这称之为socks over HTTP(我们常说的穿墙)。
-
内网穿透:在大学里,学校给我们提供了很多服务器资源,我们可以在内网使用。但放寒假回家后,无法进入学校内网,也就无法连接上内网的服务器资源。解决办法:在公网的VPS上搭一个socks代理,并将内网的一台web服务器和该VPS的socks端口打通,通过这台web服务器便可以访问所有内网服务器资源(常见的花生壳nat穿透和这个类似)。
某服务器的防火墙仅允许部分端口(如http的80端口)通信,那么可以利用socks5协议和一个打开80端口监听的socks5服务器连接,从而可以连接公网上其他端口的服务器。利用一些额外的技术手段,甚至可以骗过内部的http代理服务器,这时在使用内网http代理上网的环境下也可以不受限制的使用网络服务,这称之为socks over HTTP(我们常说的穿墙)。
内网穿透:在大学里,学校给我们提供了很多服务器资源,我们可以在内网使用。但放寒假回家后,无法进入学校内网,也就无法连接上内网的服务器资源。解决办法:在公网的VPS上搭一个socks代理,并将内网的一台web服务器和该VPS的socks端口打通,通过这台web服务器便可以访问所有内网服务器资源(常见的花生壳nat穿透和这个类似)。
当然,使用代理服务器后,将不可避免的出现通信延迟,所以应该尽量选择同网络(通运营商)、距离近的服务器。
在客户端、服务端协商好使用用户名密码认证后,客户端发出用户名密码,格式为:

- VER:鉴定协议版本
- ULEN:用户名长度
- UNAME:用户名
- PLEN:密码长度
- PASSWD:密码
服务器鉴定后发出如下回应:

- VER:鉴定协议版本
- STATUS:鉴定状态
其中鉴定状态 0x00 表示成功,0x01 表示失败。
- VER:socks版本(在socks5中是0x05);
- NMETHODS:在METHODS字段中出现的方法的数目;
- METHODS:客户端支持的认证方式列表,每个方法占1字节。
服务器从客户端提供的方法中选择一个最优的方法并通过以下消息通知客户端(贪心算法:双方都支持、安全性最高):

- VER:socks版本(在socks5中是0x05);
- METHOD:服务端选中的方法(若返回0xFF表示没有方法被选中,客户端需要关闭连接);
METHOD字段的值可以取如下值:
- X’00’ NO AUTHENTICATION REQUIRED
- X’01’ GSSAPI
- X’02’ USERNAME/PASSWORD
- X’03’ to X’7F’ IANA ASSIGNED
- X’80’ to X’FE’ RESERVED FOR PRIVATE METHODS
- X’FF’ NO ACCEPTABLE METHODS
之后客户端和服务端根据选定的认证方式执行对应的认证。认证结束后客户端就可以发送请求信息(如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装)。
socks5请求格式:

- VER:socks版本(在socks5中是0x05)
- CMD:SOCK的命令码:
- CONNECT X’01’
- BIND X’02’
- UDP ASSOCIATE X’03’
- RSV:保留字段
- ATYP:地址类型:
- IP V4地址: X’01’
- 域名地址: X’03’
- IP V6地址: X’04’
-
DST.ADDR:目的地址
- DST.PORT:目的端口
服务器按以下格式回应客户端的请求:

- VER:socks版本(在socks5中是0x05)
- REP:应答状态码:
- X’00’ succeeded
- X’01’ general socks server failure
- X’02’ connection not allowed by ruleset
- X’03’ Network unreachable
- X’04’ Host unreachable
- X’05’ Connection refused
- X’06’ TTL expired
- X’07’ Command not supported
- X’08’ Address type not supported
- X’09’ to X’FF’ unassigned
- RSV:保留字段(需设置为X’00’)
- ATYP:地址类型:
- IP V4 address: X’01’
- DOMAINNAME: X’03’
- IP V6 address: X’04’
- BND.ADDR:服务器绑定的地址
- BND.PORT:服务器绑定的端口
如果被选中的方法包括有认证信息的封装、完整性和/或机密性相关检查,则server端在发送响应包时也需要把这些响应消息封装进去。
http://www.inet.no/dante
BSD/CMU
1.1.18
09/2005
Linux
v4, v5
FreeCap
GPL
3.18
02/2005
Windows
-
Hummingbird socks
-
-
-
Windows
-
ProxyCap
-
2.03
-
Windows
-
SocksCap
Non-Comercial home use
-
-
-
v5
Super Socks5Cap
-
1.5.3
-
Windows
-
tsocks
GPL
1.8
10/2002
-
-
nylon
-
-
06/2003
OpenBSD
-
python实现socks5代理
为了方便,我们直接使用python中的SocketServer库,直接运行以下程序即可在本机建立了一个socks5的代理服务器:
- import socket, sys, select, SocketServer, struct, time
- class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass
- def handle_tcp(sock, remote):
- fdset = [sock, remote]
- while True:
- r, w, e = select.select(fdset, [], [])
- if sock in r:
- if remote.send(sock.recv(4096)) <= 0: break
- if remote in r:
- if sock.send(remote.recv(4096)) <= 0: break
- class Socks5Server(SocketServer.StreamRequestHandler):
- def handle(self):
- try:
- print('socks connection from ', self.client_address)
- sock = self.connection
- # 1. Version
- sock.recv(262)
- sock.send(b"\x05\x00");
- # 2. Request
- data = self.rfile.read(4)
- mode = ord(data[1])
- addrtype = ord(data[3])
- if addrtype == 1: # IPv4
- addr = socket.inet_ntoa(self.rfile.read(4))
- elif addrtype == 3: # Domain name
- addr = self.rfile.read(ord(sock.recv(1)[0]))
- port = struct.unpack('>H', self.rfile.read(2))
- reply = b"\x05\x00\x00\x01"
- try:
- if mode == 1: # 1. Tcp connect
- remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- remote.connect((addr, port[0]))
- print('Tcp connect to', addr, port[0])
- else:
- reply = b"\x05\x07\x00\x01" # Command not supported
- local = remote.getsockname()
- reply += socket.inet_aton(local[0]) + struct.pack(">H", local[1])
- except socket.error:
- # Connection refused
- reply = '\x05\x05\x00\x01\x00\x00\x00\x00\x00\x00'
- sock.send(reply)
- # 3. Transfering
- if reply[1] == '\x00': # Success
- if mode == 1: # 1. Tcp connect
- handle_tcp(sock, remote)
- except socket.error:
- print('socket error')
- def main():
- server = ThreadingTCPServer(('', 1080), Socks5Server)
- server.serve_forever()
- if __name__ == '__main__':
- main()
客户端实现代码:
- import socket, socks, requests
- def main():
- socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 1080)
- socket.socket = socks.socksocket
- print(requests.get('http://127.0.0.1:1080').text)
- if __name__ == '__main__':
- main()