GO语言工程实践SOCKS5代理| 青训营

113 阅读3分钟

当编写一个SOCKS5代理服务器时,需要考虑多个步骤,包括初始化、握手、连接请求和数据传输:

  1. 初始化: 在主函数中,创建一个TCP监听器,以侦听客户端的连接请求。

  2. 握手阶段: 当客户端连接到代理服务器时,首先进行SOCKS5协议的握手阶段。这包括以下步骤:

    • 读取客户端发送的握手版本和支持的认证方法列表。
    • 选择一个支持的认证方法并发送回客户端,表明无需认证。
  3. 连接请求: 客户端与代理服务器完成握手后,客户端会发送连接请求。这包括以下步骤:

    • 读取客户端发送的连接请求版本、命令(通常为0x01表示CONNECT)、保留字段、地址类型(IPv4、IPv6、域名)以及目标地址和端口。
    • 解析目标地址和端口,根据地址类型连接目标主机。
  4. 与目标主机连接: 根据客户端发送的目标地址和端口,代理服务器与目标主机建立连接。

  5. 确认连接: 代理服务器要向客户端发送连接已建立的确认,以告知客户端连接已成功建立。

  6. 数据传输: 在确认连接之后,开始在客户端与目标主机之间传输数据。这包括以下步骤:

    • 从客户端读取数据,并将其转发到目标主机。
    • 从目标主机读取数据,并将其转发回客户端。
  7. 错误处理: 在每个阶段都需要考虑错误处理,包括连接错误、读写错误等。在出现错误时,应关闭连接以确保资源被正确释放。

  8. 并发支持: 为了同时处理多个客户端连接,可以使用并发。每当有新的客户端连接时,可以在单独的goroutine中处理该连接,以防止阻塞主循环。

package main

import (
	"fmt"
	"net"
)

func handleConnection(conn net.Conn) {
	defer conn.Close()

	// Read and respond to the initial SOCKS5 handshake
	version := make([]byte, 1)
	conn.Read(version)
	nMethods := make([]byte, 1)
	conn.Read(nMethods)
	methods := make([]byte, nMethods[0])
	conn.Read(methods)
	conn.Write([]byte{0x05, 0x00})

	// Read and respond to the connection request
	reqHeader := make([]byte, 4)
	conn.Read(reqHeader)
	if reqHeader[0] != 0x05 || reqHeader[1] != 0x01 || reqHeader[2] != 0x00 {
		fmt.Println("Invalid SOCKS5 request")
		return
	}

	addrType := reqHeader[3]
	var destAddr string
	switch addrType {
	case 0x01: // IPv4
		ip := make([]byte, 4)
		conn.Read(ip)
		destAddr = net.IP(ip).String()
	case 0x03: // Domain name
		domainLen := make([]byte, 1)
		conn.Read(domainLen)
		domain := make([]byte, domainLen[0])
		conn.Read(domain)
		destAddr = string(domain)
	case 0x04: // IPv6
		ipv6 := make([]byte, 16)
		conn.Read(ipv6)
		destAddr = net.IP(ipv6).String()
	default:
		fmt.Println("Unsupported address type")
		return
	}

	portBytes := make([]byte, 2)
	conn.Read(portBytes)
	destPort := int(portBytes[0])<<8 | int(portBytes[1])

	// Establish connection to the destination
	destConn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", destAddr, destPort))
	if err != nil {
		fmt.Println("Error connecting to destination:", err)
		return
	}
	defer destConn.Close()

	// Respond to the client that the connection is established
	conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})

	// Start forwarding data between the client and the destination
	go func() {
		_, err := conn.Copy(destConn)
		if err != nil {
			fmt.Println("Error copying data:", err)
		}
	}()

	_, err = destConn.Copy(conn)
	if err != nil {
		fmt.Println("Error copying data:", err)
	}
}

func main() {
	listener, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		fmt.Println("Error starting server:", err)
		return
	}
	defer listener.Close()

	fmt.Println("SOCKS5 Proxy server is listening on 127.0.0.1:1080")

	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("Error accepting connection:", err)
			continue
		}

		go handleConnection(conn)
	}
}