GO语言实战案例(三)
Proxy
对 Go 语言实现的 SOCKS5 代理服务器的四个版本进行详细分析,展示如何从一个简单的回显服务器开始,逐步扩展功能,最终实现一个完整的 SOCKS5 代理服务。
第一段代码:基础 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
}
}
}
1.1. 代码结构分析
这是一个最基本的 TCP 代理服务器实现。它在本地的 127.0.0.1:1080 地址上监听 TCP 请求,并为每一个连接创建一个新的 goroutine 来处理。该服务器只是简单地实现了一个“回显”功能,即接收到的数据会被原封不动地写回到客户端。
主要功能:
- 使用
net.Listen在指定端口上监听连接。 - 对每一个连接,使用
bufio.NewReader来读取客户端的数据。 - 数据会被简单地读取(通过
ReadByte)并通过Write方法返回给客户端。
问题:
- 此代码没有对客户端请求做任何协议处理,它仅仅实现了一个 TCP 回显服务器,不具备 SOCKS5 协议的任何功能。
1.2. 代码扩展需求
为了使其成为一个 SOCKS5 代理服务器,我们需要添加 SOCKS5 协议的握手过程、认证方式、命令解析以及目标连接功能。接下来,我们将逐步在此基础上进行迭代。
第二段代码:实现 SOCKS5 协议握手
在第二段代码中,基本结构保持不变,但增加了 SOCKS5 协议的握手和认证部分。
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
}
}
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)
}
// Respond with no authentication required
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
2.1. 握手协议解析
在这段代码中,我们开始实现了 SOCKS5 协议的握手过程。
握手过程:
- 客户端首先向服务器发送一个版本号(VER),后面紧跟着一个表示支持的认证方法数量的字节(NMETHODS),以及这些认证方法的字节(METHODS)。
- 服务器收到后,判断是否支持这些认证方法,并回复一个版本号(VER)和一个选择的认证方法(METHOD)。
代码解读:
- 通过
reader.ReadByte()读取客户端发送的协议版本(VER)。 - 服务器检查版本是否为 SOCKS5(0x05),并回应客户端支持的认证方法。此代码实现了最简单的“不需要认证”方法,即
0x00。 - 通过
conn.Write([]byte{socks5Ver, 0x00})向客户端发送握手完成的确认信息。
2.2. 不足之处
- 目前代码只实现了握手部分,缺少对客户端后续请求的处理(如
CONNECT命令解析)。 - 对于
METHODS中的其他认证方法(如用户名密码认证)并没有做任何处理。
第三段代码:支持 CONNECT 命令
接下来,我们添加了 SOCKS5 协议中的 CONNECT 命令处理逻辑,使服务器能够根据客户端请求的目标地址和端口建立连接。
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)
}
// Process address
// Additional logic for IPv4, host, and port parsing...
}
3.1. CONNECT 命令解析
命令字段:
- VER: 协议版本,固定为
0x05。 - CMD: 客户端请求的操作,
0x01表示CONNECT命令,要求建立一个 TCP 连接。 - ATYP: 地址类型,
0x01表示 IPv4 地址,0x03表示域名,0x04表示 IPv6 地址。 - DST.ADDR 和 DST.PORT: 目标地址和目标端口。
此代码通过 reader.ReadFull() 读取请求头部信息,并判断客户端请求是否为 CONNECT 命令。如果是,就进一步解析目标地址和端口。
3.2. 新增功能
- 支持根据不同地址类型(IPv4 和域名)解析目标地址。
- 通过
net.Dial()建立与目标服务器的连接。
第四段代码:数据转发与完整代理功能
在最后一个版本中,我们加入了数据转发功能,确保客户端与目标服务器之间能够双向传输数据。
// Handle bidirectional data forwarding
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
4.1. 数据转发
此版本实现了 SOCKS5 代理的核心功能——数据转发。通过 io.Copy,服务器在客户端和目标服务器之间建立了数据流通道。当客户端请求成功时,服务器将客户端和目标服务器之间的通信数据进行转发。
代码实现:
- 使用
go func()启动两个并发 goroutine,将客户端请求数据转发到目标服务器,反之亦然。 - 使用
context.WithCancel来确保当其中一个数据流关闭时,另一个流也能够及时终止。
4.2. 增强功能
- 完整支持 SOCKS5
CONNECT命令。 - 实现了双向数据转发,确保客户端和目标服务器的通信无缝进行。
完整代码
总结
通过四个版本的迭代,我们从一个简单的回显服务器逐步演进为一个功能完备的 SOCKS5 代理服务器。每次迭代都针对 SOCKS5 协议的不同部分进行扩展,最终实现了协议握手、命令解析、目标连接以及数据转发等核心功能。通过这样的实践,不仅能更好地理解 SOCKS5 协议的工作原理,还能掌握 Go 语言处理网络通信的基本技巧。