SOCKS5 协议及简单实现

October 16, 2017 23:31


What's SOCKS5

SOCKS5 是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。

VS HTTP Proxy

工作在比 HTTP 代理更低的层次(表示层和传输层之间),SOCKS 是会话层协议,还可以转发 UDP 流量和反射代理。

Show me the code

Client create a TCP connection

require 'socket'
host = 'mydomin.com'
port = 12332
local_socket = TCPSocket.new(host, port)

Check version and auth

建议 TCP 连接后,客户端首先需要发送请求来协商版本及认证方式,格式为

VER NMETHODS(METHODS length) METHODS
1 1 1-255

上面 VER 为版本号,NMETHODS 为 METHODS 数据长度,METHODS 为验证方式,每个方式占一字节,可以同时支持多种验证方式

o X'00' NO AUTHENTICATION REQUIRED
o X'01' GSSAPI
o X'02' USERNAME/PASSWORD
o X'03' to X'7F' IANA ASSIGNED
o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
o X'FF' NO ACCEPTABLE METHODS

比如发送一个 SOCKS 5 的代理请求,不需要进行验证

local_socket.sendmsg("\x05\x01\x00")

Server auth response

代理服务器收到请求后,需要做出应答,格式为

VER METHOD
1 1

VER 为版本, METHOD 为服务端选定的认证方式

server_socket.sendmsg("\x06\x00")

然后客户端和服务务就要根据选定的方式执行对应的认证

Send Request

认证完成后,客户端就要发送请求信息了,格式如下

VER CMD RSV ATYP DST.ADDR DST.PORT
1 1 0x00 1 variable 2

CMD 是 SOCKS 的命令

o CONNECT X'01'
o BIND X'02'
o UDP ASSOCIATE X'03'

RSV 为保留字节,ATYP 为地址类型

o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'

如果 ATYP 是 ipv4, DST.ADDR 就是 4 字节长(1.2.3.4),分别存放 IP 的地个数字,如果是 domain 的话, DST.ADDR 第一字节为 domain 长度,DST.PORT 为固定的两字节表示端口

服务端收到这个数据后就要开始解析

data = server_socket.recv(512)
ver = case data[0].ord
when 4 then :sock4
when 5 then :sock5
else :unknown
end

cmd = case data[1].ord
when 1 then :connect
when 2 then :bind
when 3 then :udp
else :unknown
end

host, addr_type = nil
case data[3].ord
when 1 # IP v4
  addr_type = :ipv4
  host = data[4..-3].unpack('cccc').join('.')
when 3 # domain
  addr_type = :domain
  host = data[5..-3] # 我们可以直接从后住前取,就不用管第 5 位的 domain 长度了
when 4 # IP v6
  addr_type = :ipv6
  host = data[4..-3].unpack('S*').map {|i| i.to_s(16) }.join(':')
end
port = data[-2..-1].unpack('n').first

Reply the request

收到请求自然要回应了,格式如下

VER REP RSV ATYP BND.ADDR BND.PORT
1 1 0x00 1 variable 2

REP 为回答的状态

o X'00' succeeded
o X'01' general SOCKS server failure
o X'02' connection not allowed by ruleset
o X'03' Network unreachable
o X'04' Host unreachable
o X'05' Connection refused
o X'06' TTL expired
o X'07' Command not supported
o X'08' Address type not supported
o X'09' to X'FF' unassigned

RSV 保留字段, BND.ADDR、BND.PORT 服务端绑定的地址和端口, 其它字段数据意义和请求的相同

好了,我们来建立请求,会回复一下客户端吧


def fn
  begin
    remote_socket = TCPSocket.new(request_host, request_port)
  rescue
    server_socket.sendmsg("\x05\x01\x00\x01\x00\x00\x00\x00\x00\x00")
    remote_socket.close if remote_socket && !remote_socket.closed?
    return
  end

  begin
    # 这里我们简单的回复一个 0.0.0.0:0 的绑定地址
    server_socket.sendmsg("\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00")
  ensure
    remote_socket.close
  end
end

Copy data

回复完成后,如果是 TCP 连接,就直接为他们相互 Copy 数据就好了

[
  Thread.new { IO.copy_stream(server_socket, remote_socket) },
  Thread.new { IO.copy_stream(remote_socket, server_socket) }
].each(&:join)

End

这里只是对协议的流程进行简单讲解,并没有实现协议的所有功能。当然,代码也是做为例子用的,逻辑差差不多就这样了。

References

Comments: