GO语言工程实践课后作业:实现思路、代码以及路径记录| 豆包MarsCode AI刷题

88 阅读9分钟

实现思路

本项目的目标是实现一个简单的SOCKS5代理服务器。SOCKS5是一种通用的代理协议,允许客户端通过代理服务器访问网络资源。代理服务器负责转发客户端的请求到目标服务器,并将目标服务器的响应返回给客户端。通过实现这个项目,可以深入理解TCP/IP协议、SOCKS5协议、Go语言的基础知识和并发编程技术。#### 项目结构

项目分为四个主要版本,每个版本逐步增加新的功能,最终实现一个完整的SOCKS5代理服务器。

  1. 监听端口:创建一个TCP服务器,监听指定的端口,接受客户端的连接请求。
  2. 身份验证:处理客户端的身份验证请求,确保客户端使用支持的认证方法。
  3. 连接请求:处理客户端的连接请求,解析目标地址和端口,建立与目标服务器的连接。
  4. 数据转发:在客户端和目标服务器之间转发数据,确保数据的双向传输。

代码构思

v1: 基础版本

功能描述

  • 监听端口:创建一个TCP服务器,监听指定的端口,接受客户端的连接请求。
  • 数据回显:接收客户端发送的数据,并原样返回给客户端。

关键点

  • 使用 net:使用 net 包中的 Listen 函数创建一个TCP服务器,监听127.0.0.1:1080端口。
  • 主循环:在主循环中,使用 Accept 函数接受客户端的连接请求。
  • goroutine:每当有新的客户端连接时,启动一个新的 goroutine 处理该连接。
  • 数据读写:在 process 函数中,使用 bufio.Reader 读取客户端发送的数据,并使用 Write 方法将数据原样返回给客户端。

代码结构

  • main 函数:启动服务器,监听端口,接受客户端连接。
  • process 函数:处理客户端连接,读取数据并原样返回。
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
		}
	}
}

v2: 增加身份验证

功能描述

  • 身份验证:处理客户端的身份验证请求,确保客户端使用支持的认证方法。

关键点

  • 引入 auth 函数:专门处理客户端的身份验证请求。
  • 读取客户端数据:读取客户端发送的版本号和方法列表,检查版本号是否为SOCKS5(0x05),并选择支持的认证方法(无认证)。
  • 发送响应:向客户端发送响应,确认选择的认证方法。
  • 调用 auth 函数:在 process 函数中,调用 auth 函数进行身份验证,只有验证成功后才继续处理后续的连接请求。

代码结构

  • auth 函数:处理客户端的身份验证请求。
  • mainprocess 函数:保持不变,但在处理客户端连接时调用 auth 函数。

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
}

v3: 增加连接请求处理

功能描述

  • 连接请求:处理客户端的连接请求,解析目标地址和端口,建立与目标服务器的连接。

关键点

  • 引入 connect 函数:专门处理客户端的连接请求。
  • 读取连接请求头:读取客户端发送的连接请求头,包括版本号、命令类型、保留字段和地址类型。
  • 解析目标地址和端口:根据地址类型(IPv4、域名、IPv6),解析目标地址和端口。
  • 建立连接:使用 net.Dial 函数建立与目标服务器的连接。
  • 发送响应:向客户端发送响应,确认连接请求的成功或失败。
  • 调用 connect 函数:在 process 函数中,调用 connect 函数处理连接请求,只有请求成功后才继续处理后续的数据传输。

代码结构

  • connect 函数:处理客户端的连接请求。
  • mainprocess 函数:在处理客户端连接时调用 authconnect 函数。

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
}

v4: 完善数据转发

功能描述

  • 数据转发:在连接请求处理的基础上,增加了数据转发的功能,确保客户端和目标服务器之间的数据双向传输。

关键点

  • 使用 context:管理并发操作,确保在数据传输过程中能够及时取消操作。
  • 启动两个 goroutine:一个负责将客户端的数据转发到目标服务器,另一个负责将目标服务器的数据转发回客户端。
  • 使用 io.Copy 函数:实现高效的数据传输。
  • 资源释放:在数据传输完成后,使用 context.Done() 通道等待两个 goroutine 完成,确保资源的正确释放。

代码结构

  • connect 函数:处理客户端的连接请求,并启动数据转发的 goroutine。
  • mainprocess 函数:保持不变,但在处理客户端连接时调用 authconnect 函数。

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, 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)
	}
	_, 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) {
	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])

	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)
	_, 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
}

学习路径

  1. 理解TCP/IP协议

    • 基础知识:了解TCP/IP协议的基本概念,包括IP地址、端口号、TCP三次握手等。
    • 实践:编写简单的TCP客户端和服务器,练习使用 net 包中的 ListenDial 函数。
  2. Go语言基础

    • 语法:掌握Go语言的基本语法,包括变量声明、控制结构、函数定义等。
    • 常用库:熟悉常用的标准库,如 fmtlognetbufio 等。
    • 实践:编写简单的Go程序,练习基本的输入输出操作和网络编程。
  3. SOCKS5协议

    • 协议规范:学习SOCKS5协议的详细规范,理解其握手过程和数据传输机制。
    • 消息格式:熟悉SOCKS5协议的各种消息格式,包括身份验证请求、连接请求等。
    • 实践:编写代码解析和生成SOCKS5协议的消息。
  4. 错误处理

    • 错误类型:了解Go语言中的错误类型和错误处理机制。
    • 错误传播:学会使用 error 类型和 fmt.Errorf 函数处理和传播错误。
    • 实践:在代码中添加详细的错误处理逻辑,确保程序的健壮性。
  5. 并发编程

    • goroutine:理解Go语言的并发模型,学会使用 goroutine 创建并发任务。
    • channel:学习 channel 的基本概念和用法,实现 goroutine 之间的通信和同步。
    • 实践:编写并发程序,练习使用 goroutinechannel 实现高效的并发处理。
  6. 数据流管理

    • io包:熟悉 io 包中的常用函数,如 ReadWriteCopy 等。
    • 数据传输:学会使用 io.Copy 函数管理数据流,确保数据的高效传输。
    • 实践:编写代码实现客户端和服务器之间的数据双向传输。