这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
SOCK5代理协议
简介
SOCKS5是一种代理服务协议,主要用于客户端与目标服务器之间通讯的透明传递。
在现代网络中,很多情况下,由于网络或者防火墙的原因,我们很难直接去访问对方的网络,所以需要一种代理机制来充当本地网络和大型网络之间的网关。代理服务器通过拦截发送方和接收方之间的连接来工作。 所有传入的数据都通过一个端口进入,并通过另一个端口转发到目标网络中。
因为SOCKS是运行在OSI七层协议中的第五层会话层,所以它可以处理包括HTTP、HTTPS、POP3、SMTP 和 FTP等多种请求类型,所以可以使用SOCKS协议来进行邮件发送、网页浏览、文件传输等。
SOCKS5协议设计之初是为了让有权限的用户可以穿过防火墙的限制,访问外部资源。
相比较SOCKS4,SOCKS5加入了认证功能,更加的安全。
可以用SOCKS5协议来实现代理服务器。
协议运行过程
下图为SOCK5协议的运行过程
认证过程
1. 客户端发出请求
| VERSION | METHODS_COUNT | METHODS... |
|---|---|---|
| 1字节 | 1字节 | 1到255字节,长度由METHODS_COUNT值决定 |
| 0x05 | 0x03 | 0x00 0x01 0x02 |
- VERSION SOCKS协议版本,目前固定0x05
- METHODS_COUNT 客户端支持的认证方法数量
- METHODS... 客户端支持的认证方法,每个方法占用1个字节
METHOD定义
- 0x00 不需要认证(常用)
- 0x01 GSSAPI认证
- 0x02 账号密码认证(常用)
- 0x03 - 0x7F IANA分配
- 0x80 - 0xFE 私有方法保留
- 0xFF 无支持的认证方法
2. 服务端返回支持的认证方法
| VERSION | METHOD |
|---|---|
| 1字节 | 1字节 |
| 0x05 | 0x00 |
- VERSION SOCKS协议版本,目前固定0x05
- METHOD 本次连接所用的认证方法,上例中为无需认证 如果是0x02则是账号密码认证 3. 客户端根据认证方法发出认证请求
注意 如果是无认证,则不需要这一步,同样也不需要第四步
| VERSION | USERNAME_LENGTH | USERNAME | PASSWORD_LENGTH | PASSWORD |
|---|---|---|---|---|
| 1字节 | 1字节 | 1-255字节 | 1字节 | 1-255字节 |
| 0x01 | 0x01 | 0x0a | 0x01 | 0x0a |
- VERSION 认证子协商版本(与SOCKS协议版本的0x05无关系)
- USERNAME_LENGTH 用户名长度
- USERNAME 用户名字节数组,长度为USERNAME_LENGTH
- PASSWORD_LENGTH 密码长度
- PASSWORD 密码字节数组,长度为PASSWORD_LENGTH
4. 服务端响应结果
校验后返回结果
| VERSION | STATUS |
|---|---|
| 1字节 | 1字节 |
-
VERSION 认证子协商版本,与客户端VERSION字段一致
-
STATUS 认证结果 0x00成功 大于0失败
建立连接
1. 客户端发送请求
| VERSION | COMMAND | RSV | ADDRESS_TYPE | DST.ADDR | DST.PORT |
|---|---|---|---|---|---|
| 1字节 | 1字节 | 1字节 | 1字节 | 1-255字节 | 2字节 |
-
VERSION SOCKS协议版本,固定0x05
-
COMMAND 命令
- 0x01 CONNECT 连接上游服务器
- 0x02 BIND 绑定,客户端会接收来自代理服务器的链接,著名的FTP被动模式
- 0x03 UDP ASSOCIATE UDP中继
-
RSV 保留字段
-
ADDRESS_TYPE 目标服务器地址类型
- 0x01 IP V4地址
- 0x03 域名地址(没有打错,就是没有0x02),域名地址的第1个字节为域名长度,剩下字节为域名名称字节数组
- 0x04 IP V6地址
-
DST.ADDR 目标服务器地址
-
DST.PORT 目标服务器端口
被动模式FTP 为了解决服务器主动发起到客户端连接会被阻止的问题,另一种更完善的工作模式出现了,它就是 FTP 的被动模式,缩写作 PASV,它工作的前提是客户端明确告知 FTP 服务器它使用被动模式。 在被动模式的 FTP 中,客户端启动到服务器的两个连接, 解决了防火墙阻止从服务器到客户端的传入数据端口连接的问题 。
2. 代理服务器与目标服务器建立连接
3. 代理服务器向主机返回结果
| VERSION | RESPONSE | RSV | ADDRESS_TYPE | BND.ADDR | BND.PORT |
|---|---|---|---|---|---|
| 1字节 | 1字节 | 1字节 | 1字节 | 1-255字节 | 2字节 |
-
VERSION SOCKS协议版本,固定0x05
-
RESPONSE 响应命令
- 0x00 代理服务器连接目标服务器成功
- 0x01 代理服务器故障
- 0x02 代理服务器规则集不允许连接
- 0x03 网络无法访问
- 0x04 目标服务器无法访问(主机名无效)
- 0x05 连接目标服务器被拒绝
- 0x06 TTL已过期
- 0x07 不支持的命令
- 0x08 不支持的目标服务器地址类型
- 0x09 - 0xFF 未分配
-
RSV 保留字段
-
BND.ADDR 代理服务器连接目标服务器成功后的代理服务器IP
-
BND.PORT 代理服务器连接目标服务器成功后的代理服务器端口
数据传输
1. 客户端发送数据
2. 代理服务器relay数据(转发)
3. 代理服务器将结果不加解析发送给主机,起到透明代理的功能
使用GOLANG编写的简易SOCK5代理服务器
// 定义一些常量 用于协议
const socks5Ver = 0x05
const cmdBind = 0x01
const atypIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04
func main() {
// 服务端建立服务 监听1080端口
server, err := net.Listen("tcp", "127.0.0.1:1080")
if err != nil {
panic(err.(any))
}
for {
//服务端处理收到的请求
client, err := server.Accept()
if err != nil {
log.Printf("Accept failed %v", err)
continue
}
go process(client)
}
}
func process(conn net.Conn) {
defer conn.Close()
//流式读取
reader := bufio.NewReader(conn)
//进行认证
err := auth(reader, conn)
下面是认证过程 实例是无认证
func auth(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
// VER: 协议版本,socks5为0x05
// NMETHODS: 支持认证的方法数量
// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
// X’00’ NO AUTHENTICATION REQUIRED
// X’02’ USERNAME/PASSWORD
ver, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read ver failed:%w", err)
}
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
methodSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read methodSize failed:%w", err)
}
method := make([]byte, methodSize)
_, err = io.ReadFull(reader, method)
if err != nil {
return fmt.Errorf("read method failed:%w", err)
}
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
// 返回相应 选择无认证方法
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
认证结束
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return
}
//建立连接
err = connect(reader, conn)
建立连接过程 只支持ipv4与域名
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER 版本号,socks5的值为0x05
// CMD 0x01表示CONNECT请求
// RSV 保留字段,值为0x00
// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
// 0x01表示IPv4地址,DST.ADDR为4个字节
// 0x03表示域名,DST.ADDR是一个可变长度的域名
// DST.ADDR 一个可变长度的值
// DST.PORT 目标端口,固定2个字节
buf := make([]byte, 4)
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read header failed:%w", err)
}
ver, cmd, atyp := buf[0], buf[1], buf[3]
if ver != socks5Ver {
return fmt.Errorf("not supported ver:%v", ver)
}
if cmd != cmdBind {
return fmt.Errorf("not supported cmd:%v", ver)
}
addr := ""
// 根据不同的地址格式进行处理
switch atyp {
case atypIPV4:
_, err = io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read atyp failed:%w", err)
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeHOST:
hostSize, err := reader.ReadByte()
if err != nil {
return fmt.Errorf("read hostSize failed:%w", err)
}
host := make([]byte, hostSize)
_, err = io.ReadFull(reader, host)
if err != nil {
return fmt.Errorf("read host failed:%w", err)
}
addr = string(host)
case atypeIPV6:
return errors.New("IPv6: no supported yet")
default:
return errors.New("invalid atyp")
}
_, err = io.ReadFull(reader, buf[:2])
if err != nil {
return fmt.Errorf("read port failed:%w", err)
}
port := binary.BigEndian.Uint16(buf[:2])
// 建立与目标服务器的连接
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
return fmt.Errorf("dial dst failed:%w", err)
}
//保证关闭连接
defer dest.Close()
log.Println("dial", addr, port)
// 返回主机响应
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// VER socks版本,这里为0x05
// REP Relay field,内容取值如下 X’00’ succeeded
// RSV 保留字段
// ATYPE 地址类型
// BND.ADDR 服务绑定的地址
// BND.PORT 服务绑定的端口DST.PORT
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 接受主机与服务器的请求与响应并转发
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
<-ctx.Done()
return nil
}
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return
}
}