GO语言工程实践课后作业 | 豆包MarsCode AI刷题

145 阅读4分钟

从思路到实现的一次 Socks5 代理设计探究

在学习 GO 语言的工程实践课程后,我进行了一个课后作业的练习,旨在加深对 GO 语言开发过程的理解。本篇文章将和大家分享实现该作业的思路、具体代码以及在这一过程中我所探索的路径。这次的课后作业是关于 Socks5 代理的设计与实现。希望通过我的分享,能够为大家的学习和实践带来一些启发。

一、Socks5 代理的实现思路

本次作业的要求是实现一个基本的 Socks5 代理服务器。Socks5 代理协议是一种通用的代理服务器协议,可以用于转发客户端的请求,通过代理服务器访问目标服务器。其主要功能包括:

  1. 用户认证:支持简单的无用户名密码认证。
  2. 连接代理:接受客户端的连接请求,转发到目标服务器。
  3. 数据转发:在客户端与目标服务器之间进行数据的中转。

思路分析

  • Socket 编程:Socks5 代理涉及到网络编程,需要使用 GO 的 net 包实现 TCP 的监听、连接和数据转发。通过 net.Conn 来管理与客户端和目标服务器的连接。
  • 协议解析:需要遵循 Socks5 协议的规范,首先从客户端接收握手请求,然后解析目标地址,并建立与目标服务器的连接。
  • 并发处理:由于代理服务器需要同时处理多个客户端请求,使用 Goroutine 来实现并发是一个高效的选择。

二、代码实现

以下是实现这次作业的核心代码:

package main

import (
	"io"
	"log"
	"net"
)

func main() {
	listener, err := net.Listen("tcp", ":1080")
	if err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
	defer listener.Close()

	log.Println("Socks5 proxy server is running on port 1080...")

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("Failed to accept connection: %v", err)
			continue
		}
		go handleClient(conn)
	}
}

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

	// Step 1: Handle handshake
	buf := make([]byte, 2)
	if _, err := io.ReadFull(conn, buf); err != nil {
		log.Printf("Failed to read handshake: %v", err)
		return
	}

	// Verify Socks5 version
	if buf[0] != 0x05 {
		log.Println("Unsupported Socks version")
		return
	}

	// Send response: no authentication required
	_, err := conn.Write([]byte{0x05, 0x00})
	if err != nil {
		log.Printf("Failed to write handshake response: %v", err)
		return
	}

	// Step 2: Handle connection request
	buf = make([]byte, 4)
	if _, err := io.ReadFull(conn, buf); err != nil {
		log.Printf("Failed to read request header: %v", err)
		return
	}

	// Read destination address and port
	if buf[3] == 0x01 { // IPv4
		addr := make([]byte, 4)
		if _, err := io.ReadFull(conn, addr); err != nil {
			log.Printf("Failed to read destination address: %v", err)
			return
		}
		port := make([]byte, 2)
		if _, err := io.ReadFull(conn, port); err != nil {
			log.Printf("Failed to read destination port: %v", err)
			return
		}

		dstAddr := net.IP(addr).String()
		dstPort := int(port[0])<<8 | int(port[1])
		dst := net.JoinHostPort(dstAddr, strconv.Itoa(dstPort))

		// Step 3: Connect to the destination server
		targetConn, err := net.Dial("tcp", dst)
		if err != nil {
			log.Printf("Failed to connect to target: %v", err)
			conn.Write([]byte{0x05, 0x05}) // Connection refused
			return
		}
		defer targetConn.Close()

		// Send success response to client
		conn.Write([]byte{0x05, 0x00, 0x00, 0x01, addr[0], addr[1], addr[2], addr[3], port[0], port[1]})

		// Step 4: Relay data between client and target server
		go io.Copy(targetConn, conn)
		io.Copy(conn, targetConn)
	} else {
		log.Println("Address type not supported")
	}
}

代码解析

  1. 监听和接收客户端连接:使用 net.Listen 监听本地端口 1080,接受来自客户端的连接请求,并为每个连接启动一个新的 Goroutine。
  2. 握手过程:根据 Socks5 协议,首先从客户端接收握手请求,确认 Socks 版本,并返回认证方式(这里实现了无需认证)。
  3. 连接请求解析:解析客户端发送的目标地址和端口信息,并尝试连接目标服务器。
  4. 数据中转:连接建立成功后,使用 io.Copy 实现客户端和目标服务器之间的数据转发。

三、开发过程中的路径记录

  • 开发环境搭建:我使用了 VS Code 作为开发工具,GO 版本为 1.18。首先确保安装和配置了 GO 环境,并使用 go mod init 来初始化项目。
  • 协议学习:在实现 Socks5 代理之前,我花了一些时间学习了 Socks5 协议的基本工作原理,包括握手、认证、连接请求等各个步骤,确保在实现时符合协议规范。
  • 测试与调试:为了测试代理的功能,我使用了浏览器和 Curl 工具来验证代理的有效性。同时,通过 Wireshark 抓包来观察数据的传输情况,确保代理工作符合预期。
  • 遇到的问题与解决:在实现代理转发时,最初遇到的数据传输不稳定的问题是由于 Goroutine 处理不当导致的。通过调整 Goroutine 的使用,并确保 io.Copy 正确地双向复制数据,最终解决了这个问题。

四、总结与反思

通过这次 Socks5 代理的实践,我对 GO 语言的网络编程有了更深入的理解。虽然这次作业的规模较小,但它包含了网络编程中的许多关键要素,包括 Socket 编程、并发处理、协议解析等。从项目初始化到最终的测试和调试,每一步都让我更加熟悉了 GO 语言在网络开发中的应用。同时,这次作业也让我意识到,在网络编程中,细节决定了程序的可靠性和稳定性。

希望这篇文章能对正在学习 GO 语言和网络编程的朋友们有所帮助,欢迎大家留言讨论!