SOCKS5 协议简介
SOCKS5 是一种网络代理协议,广泛用于穿透防火墙、隐藏客户端 IP 等场景。它工作在会话层,支持多种认证方式和命令类型。
SOCKS5 协议的基本流程:
- 握手阶段:客户端向服务器发送支持的认证方法列表,服务器选择一种认证方式并回应。
- 认证阶段(可选):如果需要认证,进行相应的认证操作。
- 请求阶段:客户端发送连接请求,包含目标地址和端口。
- 应答阶段:服务器回应连接结果,并开始数据转发。
实现步骤
1. 创建 TCP 监听器
首先,在指定的端口上监听来自客户端的连接请求。
listener, err := net.Listen("tcp", ":1080")
if err != nil {
log.Fatalf("监听失败: %v", err)
}
defer listener.Close()
log.Println("SOCKS5 代理服务器正在运行,监听端口 1080")
2. 接受客户端连接
使用 listener.Accept() 方法接受新的客户端连接,并为每个连接启动一个新的协程进行处理。
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("接受连接失败: %v", err)
continue
}
go handleConn(conn)
}
3. 处理 SOCKS5 握手
客户端会先发送握手请求,包含 SOCKS 协议版本和支持的认证方法。服务器需要读取该信息并回应。
func handleConn(conn net.Conn) {
defer conn.Close()
// 读取握手请求
buf := make([]byte, 262)
_, err := conn.Read(buf)
if err != nil {
log.Printf("读取握手数据失败: %v", err)
return
}
if buf[0] != 0x05 {
log.Printf("不支持的协议版本: %v", buf[0])
return
}
// 回复不需要认证
_, err = conn.Write([]byte{0x05, 0x00})
if err != nil {
log.Printf("发送握手回复失败: %v", err)
return
}
// 继续处理...
}
4. 读取客户端请求
客户端会发送请求细节,包含请求的命令(例如 CONNECT)、目标地址类型、目标地址和端口。
_, err = conn.Read(buf)
if err != nil {
log.Printf("读取请求失败: %v", err)
return
}
if buf[0] != 0x05 {
return
}
// 只处理 CONNECT 命令
if buf[1] != 0x01 {
log.Printf("不支持的命令: %v", buf[1])
return
}
5. 解析目标地址
根据地址类型(IPV4、域名、IPV6),解析目标地址和端口。
var addr string
switch buf[3] {
case 0x01:
// IPv4
ip := net.IP(buf[4:8]).String()
port := int(buf[8])<<8 | int(buf[9])
addr = net.JoinHostPort(ip, fmt.Sprintf("%d", port))
case 0x03:
// 域名
domainLen := int(buf[4])
domain := string(buf[5 : 5+domainLen])
port := int(buf[5+domainLen])<<8 | int(buf[6+domainLen])
addr = net.JoinHostPort(domain, fmt.Sprintf("%d", port))
case 0x04:
// IPv6
ip := net.IP(buf[4:20]).String()
port := int(buf[20])<<8 | int(buf[21])
addr = net.JoinHostPort(ip, fmt.Sprintf("%d", port))
default:
log.Printf("未知的地址类型: %v", buf[3])
return
}
6. 与目标服务器建立连接
使用解析出的地址,与目标服务器建立 TCP 连接。
destConn, err := net.Dial("tcp", addr)
if err != nil {
log.Printf("连接目标服务器失败: %v", err)
// 响应失败
conn.Write([]byte{0x05, 0x01, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
return
}
defer destConn.Close()
// 响应成功
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
log.Printf("发送连接成功回复失败: %v", err)
return
}
7. 转发数据
成功建立连接后,开始在客户端和目标服务器之间转发数据。
go io.Copy(destConn, conn)
io.Copy(conn, destConn)
测试代理服务器
1. 运行代理服务器
go run main.go
2. 使用 curl 测试
curl -I -x socks5://127.0.0.1:1080 https://www.douyin.com/
注意:使用
socks5h:// 可以确保 DNS 解析通过 SOCKS5 代理完成,避免出现协议版本错误。
常见问题
1. 不支持的协议版本
如果收到类似 不支持的协议版本: 71 的错误,可能是因为客户端没有正确使用 SOCKS5 协议。确保使用了正确的代理协议前缀,如 socks5h://。
2. 未使用的变量错误
在 Go 中,声明的变量必须被使用。如果不需要某个返回值,可以使用 _ 忽略。
_, err := conn.Read(buf)
总结
通过本次学习,我们了解了 SOCKS5 代理服务器的基本原理,并使用 Go 语言实现了一个简单的代理服务器。这个过程涉及到 TCP 网络编程、并发处理以及协议的解析,实现过程中需要注意数据的读取和解析顺序,以及错误的处理。