Socks5协议
具体协议过程参考理解socks5协议的工作过程和协议细节。这篇文章主要是通过代码去理解socks5协议。
实验条件介绍
命令行设置proxy
export https_proxy=socks5://127.0.0.1:7891 http_proxy=socks5://127.0.0.1:7891
之后通过curl发起http请求。
curl www.baidu.com
这时候,curl这个命令就会走socks协议,并封装socks协议数据发送给socks5://127.0.0.1:7891
。
协商阶段
根据参考资料,握手阶段-协商阶段发送的数据格式如下:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
#上方的数字表示字节数,下面的表格同理,不再赘述
VER: 协议版本,socks5为0x05
NMETHODS: 支持认证的方法数量
METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
- 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
那么socks服务端接受到请求时,需要解析内容,选中一个METHOD返回给客户端,格式如下:
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, user string, err error) {
...
if _, err = io.ReadFull(rw, buf[:2]); err != nil {
return
}
nmethods := buf[1]
if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
return
}
// 返回信息,需要认证
if authenticator != nil {
// 版本和认证方法
if _, err = rw.Write([]byte{5, 2}); err != nil {
return
}
}
...
}
上面代码逻辑就是处理这个过程。代码中固定返回的是X’02’ USERNAME/PASSWORD
这种认证方式。
认证阶段
认证阶段是一个可选的阶段,如果不需要认证,可以直接跳过。
如果需要认证,客户端向socks5服务器发起一个认证请求,这里以0x02
的认证方式举例:
|VER | ULEN | UNAME | PLEN | PASSWD |
+----+------+----------+------+----------+
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+----+------+----------+------+----------+
VER: 版本,**通常为`0x01`**
ULEN: 用户名长度
UNAME: 对应用户名的字节数据
PLEN: 密码长度
PASSWD: 密码对应的数据
socks5服务器收到客户端的认证请求后,解析内容,验证信息是否合法,然后给客户端响应结果。响应格式如下:
+----+--------+
|VER | STATUS |
+----+--------+
| 1 | 1 |
+----+--------+
注意这里的
VER
并不少socks的版本
func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, user string, err error) {
...
// 返回信息,需要认证
if authenticator != nil {
// 版本和认证方法
if _, err = rw.Write([]byte{5, 2}); err != nil {
return
}
// 认证阶段
header := make([]byte, 2)
if _, err = io.ReadFull(rw, header); err != nil {
return
}
authBuf := make([]byte, MaxAuthLen)
// 读取username
userLen := int(header[1])
if userLen <= 0 {
// 返回认证失败
rw.Write([]byte{1, 1})
err = ErrAuth
return
}
if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil {
return
}
user = string(authBuf[:userLen])
// 读取pass
if _, err = rw.Read(header[:1]); err != nil {
return
}
passLen := int(header[0])
if passLen <= 0 {
// 返回认证失败
rw.Write([]byte{1, 1})
err = ErrAuth
return
}
if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil {
return
}
pass := string(authBuf[:userLen])
if ok := authenticator.Verify(user, pass); !ok {
// 返回认证失败
rw.Write([]byte{1, 1})
err = ErrAuth
return
}
// 返回认成功
if _, err = rw.Write([]byte{1, 0}); err != nil {
return
}
} else {
// 告诉客户端不需要验证
if _, err = rw.Write([]byte{5, 0}); err != nil {
return
}
}
return
}
...
请求阶段
顺利通过协商阶段后,客户端向socks5服务器发起请求细节,格式如下:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
- VER 版本号,socks5的值为0x05
- CMD
0x01
表示CONNECT请求0x02
表示BIND请求0x03
表示UDP转发
- RSV 保留字段,值为0x00
- ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
0x01
表示IPv4地址,DST.ADDR为4个字节0x03
表示域名,DST.ADDR是一个可变长度的域名0x04
表示IPv6地址,DST.ADDR为16个字节长度
- DST.ADDR 一个可变长度的值
- DST.PORT 目标端口,固定2个字节
上面的值中,DST.ADDR是一个变长的数据,它的数据长度根据ATYP的类型决定。分为下面3种情况:
- X’01’
- 一个4字节的ipv4地址
- X’03’
- 一个可变长度的域名,这种情况下DST.ADDR的第一个字节表示域名长度,剩下部分是域名内容。
- X’04’
- 一个16字节的ipv6地址
socks5服务器收到客户端的请求后,需要返回一个响应,结构如下
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
- VER socks版本,这里为0x05
- REP Relay field,内容取值如下
- 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 保留字段
- ATYPE 同请求的ATYPE
- BND.ADDR 服务绑定的地址
- BND.PORT 服务绑定的端口DST.PORT
// 请求阶段
if _, err = io.ReadFull(rw, buf[:3]); err != nil {
return
}
command = buf[1]
addr, err = ReadAddr(rw, buf)
if err != nil {
return
}
switch command {
case CmdConnect, CmdUDPAssociate:
localAddr := ParseAddr(rw.LocalAddr().String())
if localAddr == nil {
err = ErrAddressNotSupported
} else {
// 返回响应
_, err = rw.Write(bytes.Join([][]byte{{5, 0, 0}, localAddr}, []byte{}))
}
...
}
func ReadAddr(r io.Reader, b []byte) (Addr, error) {
_, err := io.ReadFull(r, b[:1])
if err != nil {
return nil, err
}
switch b[0] {
case AtypDomainName:
_, err = io.ReadFull(r, b[1:2])
if err != nil {
return nil, err
}
domainLength := uint16(b[1])
_, err = io.ReadFull(r, b[2:2+domainLength+2])
return b[:1+1+domainLength+2], err
case AtypIPv4:
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
return b[:1+net.IPv4len+2], err
case AtypIPv6:
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
return b[:1+net.IPv6len+2], err
}
return nil, ErrAddressNotSupported
}
Relay阶段
socks5服务器收到请求后,解析内容。如果是UDP请求,服务器直接转发; 如果是TCP请求,服务器向目标服务器建立TCP连接,后续负责把客户端的所有数据转发到目标服务。
Trojan协议
连接Trojan
服务端需要TLS
握手,后续的流量就是TLS
保护的了。
trojan客户端与trojan服务端之间使用自定义的协议,官方文档上已经有相应的说明:
+-----------------------+---------+----------------+---------+----------+
| hex(SHA224(password)) | CRLF | Trojan Request | CRLF | Payload |
+-----------------------+---------+----------------+---------+----------+
| 56 | X'0D0A' | Variable | X'0D0A' | Variable |
+-----------------------+---------+----------------+---------+----------+
where Trojan Request is a SOCKS5-like request:
+-----+------+----------+----------+
| CMD | ATYP | DST.ADDR | DST.PORT |
+-----+------+----------+----------+
| 1 | 1 | Variable | 2 |
+-----+------+----------+----------+
where:
o CMD
o CONNECT X'01'
o UDP ASSOCIATE X'03'
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o DST.ADDR desired destination address
o DST.PORT desired destination port in network octet order
trojan客户端发起的数据包中,头部都是上面结构所示的那样:
- 头部最开始是56字节的hash,这个hash是配置中设置的字符串密码(实现商量好的)计算出来的hash值。
- hash之后跟CRLF换行符。
- CRLF后是TrojanRequest的结构。
- TrojanRequest后又是一个CRLF换行符。
- CRLF之后便是真正的数据。