[ SOCKS5代理实例理解分析 | 青训营笔记 ]

465 阅读12分钟

SOCKS5 简介

SOCKS5(Socket Secure 5)是一种网络协议,用于在计算机网络上提供代理服务。它允许客户端通过代理服务器与目标服务器进行通信,隐藏客户端的真实IP地址和身份信息。SOCKS5代理是一种传输层代理,工作在网络协议栈中的传输层,可以支持多种网络协议(如TCP和UDP)的代理。

通过 SOCKS5 代理,客户端可以在不直接与目标服务器通信的情况下,通过代理服务器来访问网络资源。这种代理方式对于保护客户端的隐私和实现网络匿名性非常有用,同时还可以绕过防火墙和地理限制。

SOCKS5 原理

socks5原理

  • 客户端请求代理连接:客户端通过建立与 SOCKS5 代理服务器的连接来请求代理服务。通常,客户端会在其配置中指定代理服务器的地址和端口。

  • 协商认证方式:一旦连接建立,客户端和代理服务器会协商认证方式。SOCKS5 协议支持多种认证方式,包括无需认证、用户名/密码认证等。客户端和代理服务器会选择一种双方都支持的认证方式进行验证。

  • 请求目标服务器连接:认证完成后,客户端发送请求给代理服务器,包括目标服务器的地址和端口号。客户端还可以指定是否需要远程解析 DNS,以及是否启用 UDP 代理。

  • 代理服务器连接目标服务器:代理服务器使用自己的 IP 地址和端口与目标服务器建立连接。这样,代理服务器充当了客户端和目标服务器之间的中间人。

  • 代理数据传输:一旦代理服务器成功建立与目标服务器的连接,它就开始在客户端和目标服务器之间传输数据。客户端发送的数据会经过代理服务器中转,然后代理服务器将响应从目标服务器传回给客户端。

  • 连接关闭:当客户端或目标服务器关闭连接时,代理服务器也会关闭与两者的连接。这标志着一次 SOCKS5 代理会话的结束。

v1 建立TCP连接

代码和原理

package main

import (
	"bufio"
	"log"
	"net"
)

func main() {
	//监听端口
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	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)
	for {
		//每次读一个字节
		b, err := reader.ReadByte()
		if err != nil {
			break
		}
		//写入字节
		_, err = conn.Write([]byte{b})
		if err != nil {
			break
		}
	}
}

这段代码是一个简单的TCP服务器。它通过在本地监听 1080 端口,并接受客户端的连接。对于每个连接,它创建一个独立的 goroutine 来处理。

在处理连接的函数 process 中,首先使用 defer 关键字将连接的关闭操作推迟到函数返回之前,确保连接在处理完成后被关闭。然后,创建了一个 bufio.NewReader 用于从连接中读取数据。

随后,使用一个无限循环,每次从连接中读取一个字节,并将其写回连接中。如果读取或写入过程中发生错误,循环会被中断,连接会被关闭。

运行

1.png

2.png

v2 认证阶段

代码和原理

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	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)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	log.Println("auth success")
}

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)
	}
	log.Println("ver", ver, "method", method)
	// +----+--------+
	// |VER | METHOD |
	// +----+--------+
	// | 1  |   1    |
	// +----+--------+

	// 构造不需要认证的响应
	_, err = conn.Write([]byte{socks5Ver, 0x00})
	if err != nil {
		return fmt.Errorf("write failed:%w", err)
	}
	return nil
}

这段代码是一个简单的 SOCKS5 服务器的实现。它通过监听本地的 1080端口,接受客户端的连接请求。当客户端与服务器建立连接后,会调用 process 函数进行处理。

process 函数中,首先调用 auth 函数进行认证。auth 函数实现了 SOCKS5 的认证过程。

在认证过程中,首先从客户端读取版本号(ver)和支持的认证方法数量(methodSize)。然后根据方法数量创建一个缓冲区,并读取对应数量的方法字节。接着,服务器向客户端发送一个不需要认证的响应,即版本号为 0x05,认证方法为 0x00

如果认证过程出现错误,则会记录错误日志,并返回。如果认证成功,则会打印 "auth success"。

整个代码的作用是实现一个简单的 SOCKS5 服务器,用于接收客户端的连接请求,并进行简单的认证处理。目前代码中只实现了认证过程,并返回了不需要认证的响应。

运行

3.png

4.png

v3 请求阶段

代码和原理

package main

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	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)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	err = connect(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
}

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
}

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", cmd)
	}
	addr := ""
	// 根据目标地址类型不同
	switch atyp {
	case atypeIPV4:
		// IPV4地址的话 读4个字节
		_, 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])

	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)
	}
	return nil
}

这段代码是一个简单的 SOCKS5 服务器实现,它可以处理客户端的连接请求并进行简单的认证和连接处理。

首先,在 main 函数中,通过 net.Listen 函数监听 127.0.0.1:1080 的 TCP 连接。然后使用 server.Accept 循环接受客户端的连接请求,并将连接交给 process 函数处理。

process 函数中,首先调用 auth 函数进行认证处理。认证过程主要是读取客户端发送的协议版本、支持的认证方法等信息,并返回一个不需要认证的响应。

接下来,调用 connect 函数进行连接处理。连接处理主要是读取客户端发送的目标地址类型、目标地址和目标端口,并返回一个连接成功的响应。

整个代码的作用是实现一个简单的 SOCKS5 服务器,用于接收客户端的连接请求,并进行认证和连接处理。

运行

5.png

6.png

v4 relay阶段

代码和原理

package main

import (
	"bufio"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	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)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	err = connect(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
}

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
}

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", cmd)
	}
	addr := ""
	switch atyp {
	case atypeIPV4:
		_, 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])

	// 与ip和对应的端口号建立tcp连接
	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
}

这段代码是一个实现 SOCKS5 代理服务器的简单示例。以下是代码的主要功能和流程解释:

  1. 定义了一些常量,包括 SOCKS5 协议版本、命令类型、地址类型等。
  2. main 函数中,创建一个 TCP 服务器监听在 127.0.0.1:1080 地址上。
  3. 通过无限循环接受客户端连接,在每个连接上调用 process 函数进行处理。
  4. process 函数处理每个客户端连接的逻辑。它首先读取客户端发送的认证请求,并根据协议规范进行认证。
  5. auth 函数处理认证过程。它首先读取客户端发送的协议版本,然后读取支持的认证方法数量和方法列表。根据规范,服务器返回支持的认证方法给客户端。
  6. 客户端在收到认证响应后,可以选择其中一种认证方法进行身份验证。
  7. 客户端完成认证后,调用 connect 函数建立与目标服务器的连接。
  8. connect 函数读取客户端发送的连接请求,并解析目标地址和端口。
  9. 根据解析得到的目标地址和端口,建立与目标服务器的连接。
  10. 客户端和目标服务器之间的数据传输通过两个 goroutine 实现:一个从客户端读取数据并发送到目标服务器,另一个从目标服务器读取数据并发送到客户端。
  11. 使用上下文 ctx 和取消函数 cancel 来控制数据传输的生命周期。
  12. 当其中一个 goroutine 完成数据传输或发生错误时,取消函数被调用,通知另一个 goroutine 停止数据传输。
  13. 最后,函数等待上下文的完成信号,结束处理。

总体而言,这段代码实现了一个简单的 SOCKS5 代理服务器,它可以接受客户端连接并转发流量到目标服务器。

运行

7.png

8.png