实现思路
本项目的目标是实现一个简单的SOCKS5代理服务器。SOCKS5是一种通用的代理协议,允许客户端通过代理服务器访问网络资源。代理服务器负责转发客户端的请求到目标服务器,并将目标服务器的响应返回给客户端。通过实现这个项目,可以深入理解TCP/IP协议、SOCKS5协议、Go语言的基础知识和并发编程技术。#### 项目结构
项目分为四个主要版本,每个版本逐步增加新的功能,最终实现一个完整的SOCKS5代理服务器。
- 监听端口:创建一个TCP服务器,监听指定的端口,接受客户端的连接请求。
- 身份验证:处理客户端的身份验证请求,确保客户端使用支持的认证方法。
- 连接请求:处理客户端的连接请求,解析目标地址和端口,建立与目标服务器的连接。
- 数据转发:在客户端和目标服务器之间转发数据,确保数据的双向传输。
代码构思
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 函数:处理客户端的身份验证请求。
- main 和 process 函数:保持不变,但在处理客户端连接时调用
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 函数:处理客户端的连接请求。
- main 和 process 函数:在处理客户端连接时调用
auth和connect函数。
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。
- main 和 process 函数:保持不变,但在处理客户端连接时调用
auth和connect函数。
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
}
学习路径
-
理解TCP/IP协议:
- 基础知识:了解TCP/IP协议的基本概念,包括IP地址、端口号、TCP三次握手等。
- 实践:编写简单的TCP客户端和服务器,练习使用
net包中的Listen和Dial函数。
-
Go语言基础:
- 语法:掌握Go语言的基本语法,包括变量声明、控制结构、函数定义等。
- 常用库:熟悉常用的标准库,如
fmt、log、net、bufio等。 - 实践:编写简单的Go程序,练习基本的输入输出操作和网络编程。
-
SOCKS5协议:
- 协议规范:学习SOCKS5协议的详细规范,理解其握手过程和数据传输机制。
- 消息格式:熟悉SOCKS5协议的各种消息格式,包括身份验证请求、连接请求等。
- 实践:编写代码解析和生成SOCKS5协议的消息。
-
错误处理:
- 错误类型:了解Go语言中的错误类型和错误处理机制。
- 错误传播:学会使用
error类型和fmt.Errorf函数处理和传播错误。 - 实践:在代码中添加详细的错误处理逻辑,确保程序的健壮性。
-
并发编程:
- goroutine:理解Go语言的并发模型,学会使用
goroutine创建并发任务。 - channel:学习
channel的基本概念和用法,实现 goroutine 之间的通信和同步。 - 实践:编写并发程序,练习使用
goroutine和channel实现高效的并发处理。
- goroutine:理解Go语言的并发模型,学会使用
-
数据流管理:
- io包:熟悉
io包中的常用函数,如Read、Write、Copy等。 - 数据传输:学会使用
io.Copy函数管理数据流,确保数据的高效传输。 - 实践:编写代码实现客户端和服务器之间的数据双向传输。
- io包:熟悉