Contents

socks5 协议

SOCKS Protocol Version 5

https://datatracker.ietf.org/doc/html/rfc1928

socks5目标: TCP 和 UDP 域中的客户端-服务器应用程序提供一个框架,以便方便、安全地使用网络防火墙的服务

0x01234567 在字节顺序中表现

Big-Endian

0x100: 0x01
0x101: 0x23
0x102: 0x45
0x103: 0x67

Little-Endian

0x100: 0x67
0x101: 0x45
0x102: 0x23
0x103: 0x01

Username/Password Authentication for SOCKS V5

https://datatracker.ietf.org/doc/html/rfc1929

3次握手后,客户端 连接 服务端socks5端口1080

X'05’ , X开头的数据都是十六进制

报文格式:

+----+----------+----------+
|VER | NMETHODS | METHODS  |
+----+----------+----------+
| 1  |    1     | 1 to 255 |
+----+----------+----------+

字段解释:

  • VER 版本号,X'05'
  • NMETHODS 支持的认证方法数量
  • METHODS 支持的认证方法ID列表

最小3个byte长度:

package socks5
var (
	VER      [1]byte
	NMETHODS [1]byte
	METHODS  [1]byte
)

最大257byte长度:

package socks5
var (
	VER      [1]byte
	NMETHODS [1]byte
	METHODS  [255]byte
)
+----+--------+
|VER | METHOD |
+----+--------+
| 1  |   1    |
+----+--------+

报文格式:

  • VER 版本号
  • METHOD客户端给定选择的认证方法里面选择其中一种.

METHOD值:

  • X'00' 无认证需要
  • X'01' GSSAPI
  • X'02' 用户名,密码方式验证
  • X'03' to X'7F' IANA 分配
  • X'80' to X'FE' 私人方法保留
  • X'FF' 没有可接受的方法列表,此时认证失败,客户端必须关闭连接.

总共2个byte长度:

package socks5
var (
	VER     [1]byte
	METHOD  [1]byte
)

服务端响应METHOD为X'02',则进行用户名密码校验

+----+------+----------+------+----------+
|VER | ULEN |  UNAME   | PLEN |  PASSWD  |
+----+------+----------+------+----------+
| 1  |  1   | 1 to 255 |  1   | 1 to 255 |
+----+------+----------+------+----------+
  • VER socks5版本
  • ULEN 用户名长度
  • UNAME 用户名
  • PLEN 密码长度
  • PASSWD 密码

最小长度5个字节

  Ver    byte
  Ulen   byte
  Uname  [1]byte 
  Plen   byte 
  Passwd [1]byte

最大长度513字节

  Ver    byte
  Ulen   byte
  Uname  [255]byte 
  Plen   byte 
  Passwd [255]byte
+----+--------+
|VER | STATUS |
+----+--------+
| 1  |   1    |
+----+--------+
  • STATUS
    • X'00’ 表示成功
    • 其他值则客户端必须关闭连接

长度为2byte

VER    [1]byte
STATUS [1]byte
+----+-----+-------+------+----------+----------+
|VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1  |  1  | X'00' |  1   | Variable |    2     |
+----+-----+-------+------+----------+----------+
  • VER: 协议版本
  • CMD: 命令
    • 0x01: CONNECT 建立 TCP 连接
    • 0x02: BIND 上报反向连接地址
    • 0x03: 关联 UDP 请求
  • RSV: 保留字段,值为 0x00
  • ATYP: 地址类型
    • 0x01: IPv4
    • 0x03: 域名 fully-qualified domain name.
    • 0x04:IPv6
  • DST.ADDR 期望目标地址
  • DST.PORT 期望目标端口 采用big-endian表示
type AddressType byte

const (
IPV4 AddressType = 1
IPV6 AddressType = 4
DOMAIN AddressType = 3

IPv4len = 4
IPv6len = 16

)

type SocksReply struct {
Ver    byte
Cmd    byte
Rsv    byte
Atyp   AddressType

DstAddr [addressLength]byte
DstPort [2]byte
}



func addressLength(atyp AddressType) int {
    switch atyp {
        case IPV4:
    return net.IPv4len
        case IPV6:
    return net.IPv6len
        case DOMAIN:
    return toDstAddr(domain) 
	
    }
}
// 255为域名最大长度
// 最大域名长度 https://en.wikipedia.org/wiki/Fully_qualified_domain_name

// https://en.wikipedia.org/wiki/SOCKS 
//1 byte of name length followed by 1–255 bytes for the domain name
// 比如域名长度为 200 ,则第一位值为200,后面是域名值
//  The first octet of the address field contains the number of octets of name that follow, there is no terminating NUL octet.
func toDstAddr(domain string) []byte {
    	
  // 将域名转换为 []byte 类型
  domainBytes := []byte(domain)
  
  // 计算域名的字节长度
  domainLen := len(domainBytes)
  
  // 创建一个包含域名字节长度和域名字节的 []byte 类型的数组
  dstAddr := make([]byte, 1+domainLen)
  
  // 将域名的字节长度写入 dstAddr 的第一个字节
  dstAddr[0] = byte(domainLen)
  
  // 将域名的字节写入 dstAddr 的剩余字节
  copy(dstAddr[1:], domainBytes)
  
  return dstAddr
}

域名下最大长度为262字节,7(固定字段长度)+域名长度

ipv4最大长度为10字节

ipv4最大长度为22字节

+----+-----+-------+------+----------+----------+
|VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1  |  1  | X'00' |  1   | Variable |    2     |
+----+-----+-------+------+----------+----------+
  • VER 协议版本
  • REP 回复字段
    • X'00' 已成功
    • X'01' 通常的socks服务失败
    • X'02' 规则集内不允许
    • X'03' 网络不可达
    • X'04' 主机不可达
    • X'05' 连接已拒绝
    • X'06' TTL 过期
    • X'07' 命令不被支持
    • X'08' 地址类型不被支持
    • X'09'X'FF' 没有定义
  • RSV
  • ATYP
  • BND.ADDR 服务绑定地址
  • BND.PORT 服务绑定端口 采用big-endian表示

在对CONNECT请求的回复中,BND.PORT包含了服务器分配给连接目标主机的端口号,而BND.ADDR包含了对应的IP地址。

提供的BND.ADDR地址通常与客户端用来连接SOCKS服务器的IP地址不同,因为这样的服务器通常支持多个网络接口。

SOCKS服务器应该考虑DST.ADDRDST.PORT以及客户端源地址和端口来评估CONNECT请求。

BIND请求用于那些需要client接受server连接的协议。

FTP就是一个典型示例,它使用 client-to-server 连接传输命令和状态报告, 但可能使用server-to-client连接按需传输数据(如LS、GET、PUT)。

在应用协议中,客户端应该只在使用 CONNECT 请求建立主连接后,才使用 BIND 请求建立次连接.

SOCKS服务器应该在评估BIND请求时考虑 DST.ADDRDST.PORT

BIND操作过程中,SOCKS服务器会向客户端发送两个回复。

第一个回复是在服务器创建并绑定新套接字后发送。

BND.PORT字段包含SOCKS服务器为监听传入连接分配的端口号。

BND.ADDR字段包含相关联的IP地址。

客户端通常会使用这些信息通过主或控制连接通知应用服务器会合地址。

第二个回复仅在待定传入连接成功或失败后发送。

在第二个回复中,BND.PORTBND.ADDR字段包含连接主机的地址和端口号.

UDP ASSOCIATE 请求用于在 UDP 中继进程 中建立一个关联,以便处理 UDP 数据报。

DST.ADDRDST.PORT 字段包含客户端期望用于发送 UDP 数据报的地址和端口。

服务器可以根据这些信息限制对关联的访问。

如果客户端在发送 UDP ASSOCIATE 请求时没有这些信息,则必须使用端口号和地址全部为 0。

UDP 关联会在发起 UDP ASSOCIATE 请求的 TCP 连接终止时 终止。

在对 UDP ASSOCIATE 请求的响应中, BND.PORTBND.ADDR 字段指示客户端必须将 UDP 请求消息发送到哪个端口/地址进行中继。

基于 UDP 的客户端必须将数据包发送到 UDP 中继服务器, 端口号为 UDP ASSOCIATE 请求响应中 BND.PORT 字段指定的端口。 如果选定的认证方法支持封装来保证真实性、完整性和/或保密性,数据包必须使用相应的封装方法进行封装。 每个 UDP 数据包都携带一个 UDP 请求头,其中包含

+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
+----+------+------+----------+----------+----------+
| 2  |  1   |  1   | Variable |    2     | Variable |
+----+------+------+----------+----------+----------+
  • RSV (Reserved):保留字段,必须设置为 0x0000。
  • FRAG (Fragment):片段标志,用于指示数据包是否被分片:Current fragment number
    • 最高位:
      • 0:表示数据包没有被分片。
      • 1:表示数据包是分片的一部分。
    • 其他位:
      • 用于表示分片的顺序 范围为1-127
  • ATYP (Address Type):地址类型,指定目标地址的类型:
    • 0x01:IPv4 地址。
    • 0x03:域名。
    • 0x04:IPv6 地址。
  • DST.ADDR (Destination Address):根据 ATYP 指定的类型,可以是 IP 地址或域名。
  • DST.PORT (Destination Port):目标端口号。
  • DATA (User Data):用户数据,封装后的实际数据内容。
  • 当 UDP 中继服务器决定转发一个 UDP 数据包时,它会默默地进行,不会通知请求客户端。
  • 同样,它也会丢弃无法或不会转发的数据包。
  • 当 UDP 中继服务器从远程主机收到回复数据包时,它必须使用上述 UDP 请求头和任何与认证方法相关的封装对该数据包进行封装。
  • UDP 中继服务器必须从 SOCKS 服务器获取预计将发送数据包到 BND.PORT 的客户端的 IP 地址。
  • 它必须丢弃来自任何除了记录在特定关联中的源 IP 地址之外的任何数据包。

FRAG 字段指示数据包是否被分片:

  • 最高位指示片段序列的结束。
  • X'00' 值表示该数据包是独立的。
  • 1 到 127 之间的值指示片段在片段序列中的位置。

每个接收器都会有一个用于重新组装片段的队列和一个与这些片段相关的重新组装计时器。

  • 当重新组装计时器过期或新到达的 FRAG 字段的值小于该片段序列中处理的最高 FRAG 值时,重新组装队列必须重新初始化并放弃相关的片段。
  • 重新组装计时器必须至少为 5 秒。
  • 建议应用程序尽可能避免数据包分片。

实现分片是可选的;不支持分片的实现必须丢弃任何 FRAG 字段不是 X'00' 的数据包。

一个支持 SOCKS 的 UDP 编程接口必须报告低于操作系统提供的实际空间的可用 UDP 数据包缓冲区大小:

  • 如果 ATYPX'01',则减少 10 + 与方法相关的字节。
  • 如果 ATYPX'03',则减少 262 + 与方法相关的字节。
  • 如果 ATYPX'04',则减少 20 + 与方法相关的字节。