Grok 生成的简易 socks5 代理服务器
AI 生成的简易 socks5 代理服务器,使用 golang 实现,使用 docker-compose 部署。
准备
- golang 开发环境
- docker
目录结构
your_project/
├── main.go
├── Dockerfile
└── docker-compose.yml
main.go 代码内容
默认监听 1080 端口,修改端口可以在 docker-compose 映射修改。
package main
import (
"encoding/binary"
"fmt"
"io"
"log"
"net"
"sync"
)
// 全局变量用于会话管理
var (
// allowedClients 存储允许的客户端 IP 及其活跃会话计数
allowedClients sync.Map // map[string]interface{}, value is *int
// destToClient 存储目标地址到客户端 UDP 地址的映射
destToClient sync.Map // map[string]string
)
func main() {
// 启动 TCP 监听器
go func() {
listener, err := net.Listen("tcp", ":1080")
if err != nil {
log.Fatal("TCP 监听失败: ", err)
}
defer listener.Close()
log.Println("TCP 服务器启动在 :1080")
for {
conn, err := listener.Accept()
if err != nil {
log.Println("接受 TCP 连接失败: ", err)
continue
}
go handleConnection(conn)
}
}()
// 启动 UDP 监听器
udpAddr, err := net.ResolveUDPAddr("udp", ":1080")
if err != nil {
log.Fatal("解析 UDP 地址失败: ", err)
}
udpConn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
log.Fatal("UDP 监听失败: ", err)
}
defer udpConn.Close()
log.Println("UDP 服务器启动在 :1080")
buf := make([]byte, 65536)
for {
n, clientAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
log.Println("读取 UDP 数据失败: ", err)
continue
}
go handleUDPPacket(udpConn, clientAddr, buf[:n])
}
}
// handleConnection 处理 TCP 连接
func handleConnection(conn net.Conn) {
defer conn.Close()
// 读取客户端问候
buf := make([]byte, 2)
_, err := io.ReadFull(conn, buf)
if err != nil {
log.Println("读取问候失败: ", err)
return
}
if buf[0] != 0x05 {
log.Println("不支持的协议版本: ", buf[0])
return
}
nmethods := buf[1]
methods := make([]byte, nmethods)
_, err = io.ReadFull(conn, methods)
if err != nil {
log.Println("读取认证方法失败: ", err)
return
}
// 回复无认证
_, err = conn.Write([]byte{0x05, 0x00})
if err != nil {
log.Println("发送认证回复失败: ", err)
return
}
// 读取客户端请求
buf = make([]byte, 4)
_, err = io.ReadFull(conn, buf)
if err != nil {
log.Println("读取请求失败: ", err)
return
}
if buf[0] != 0x05 {
log.Println("不支持的协议版本: ", buf[0])
return
}
cmd := buf[1]
if cmd != 0x01 && cmd != 0x03 {
log.Println("不支持的命令: ", cmd)
conn.Write([]byte{0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
atyp := buf[3]
var addr string
switch atyp {
case 0x01: // IPv4
ip := make([]byte, 4)
_, err = io.ReadFull(conn, ip)
if err != nil {
log.Println("读取 IPv4 地址失败: ", err)
return
}
addr = net.IP(ip).String()
case 0x03: // 域名
lenByte := make([]byte, 1)
_, err = io.ReadFull(conn, lenByte)
if err != nil {
log.Println("读取域名长度失败: ", err)
return
}
domainLen := lenByte[0]
domain := make([]byte, domainLen)
_, err = io.ReadFull(conn, domain)
if err != nil {
log.Println("读取域名失败: ", err)
return
}
addr = string(domain)
case 0x04: // IPv6
ip := make([]byte, 16)
_, err = io.ReadFull(conn, ip)
if err != nil {
log.Println("读取 IPv6 地址失败: ", err)
return
}
addr = net.IP(ip).String()
default:
log.Println("不支持的地址类型: ", atyp)
conn.Write([]byte{0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
portBytes := make([]byte, 2)
_, err = io.ReadFull(conn, portBytes)
if err != nil {
log.Println("读取端口失败: ", err)
return
}
port := binary.BigEndian.Uint16(portBytes)
if cmd == 0x01 { // CONNECT
// 连接目标服务器
destConn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", addr, port))
if err != nil {
log.Println("连接目标失败: ", err)
conn.Write([]byte{0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
defer destConn.Close()
// 发送成功回复
reply := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
_, err = conn.Write(reply)
if err != nil {
log.Println("发送成功回复失败: ", err)
return
}
// 中继数据
go io.Copy(destConn, conn)
io.Copy(conn, destConn)
} else if cmd == 0x03 { // UDP ASSOCIATE
clientIP := conn.RemoteAddr().(*net.TCPAddr).IP.String()
val, _ := allowedClients.LoadOrStore(clientIP, new(int))
count := val.(*int)
*count++
defer func() {
*count--
if *count == 0 {
allowedClients.Delete(clientIP)
}
}()
// 发送 UDP 地址回复
localAddr := conn.LocalAddr().(*net.TCPAddr)
reply := []byte{0x05, 0x00, 0x00}
if localAddr.IP.To4() != nil {
reply = append(reply, 0x01)
reply = append(reply, localAddr.IP.To4()...)
} else {
reply = append(reply, 0x04)
reply = append(reply, localAddr.IP.To16()...)
}
reply = append(reply, byte(localAddr.Port>>8), byte(localAddr.Port&0xff))
_, err = conn.Write(reply)
if err != nil {
log.Println("发送 UDP ASSOCIATE 回复失败: ", err)
return
}
// 保持 TCP 连接打开
buf := make([]byte, 1)
_, err = conn.Read(buf)
if err != nil {
log.Println("TCP 连接关闭: ", err)
}
}
}
// handleUDPPacket 处理 UDP 数据包
func handleUDPPacket(udpConn *net.UDPConn, srcAddr *net.UDPAddr, data []byte) {
clientIP := srcAddr.IP.String()
if _, ok := allowedClients.Load(clientIP); !ok {
log.Println("来自未授权客户端的 UDP 数据包: ", clientIP)
return
}
// 判断是否为客户端发送的 SOCKS5 UDP 数据包
if len(data) < 3 || binary.BigEndian.Uint16(data[0:2]) != 0 || data[2] != 0 {
// 来自目标服务器的响应
destKey := srcAddr.String()
if val, ok := destToClient.Load(destKey); ok {
clientUDPAddrStr := val.(string)
clientUDPAddr, err := net.ResolveUDPAddr("udp", clientUDPAddrStr)
if err != nil {
log.Println("解析客户端 UDP 地址失败: ", err)
return
}
// 封装 SOCKS5 头部
response := []byte{0x00, 0x00, 0x00} // RSV 和 FRAG
if ip := srcAddr.IP.To4(); ip != nil {
response = append(response, 0x01)
response = append(response, ip...)
} else if ip := srcAddr.IP.To16(); ip != nil {
response = append(response, 0x04)
response = append(response, ip...)
} else {
log.Println("不支持的 IP 类型")
return
}
response = append(response, byte(srcAddr.Port>>8), byte(srcAddr.Port&0xff))
response = append(response, data...)
_, err = udpConn.WriteToUDP(response, clientUDPAddr)
if err != nil {
log.Println("发送响应到客户端失败: ", err)
}
}
return
}
// 来自客户端的请求
atyp := data[3]
var destAddr string
var destPort uint16
var offset int
switch atyp {
case 0x01: // IPv4
if len(data) < 10 {
log.Println("无效的 UDP 数据包")
return
}
destAddr = net.IP(data[4:8]).String()
destPort = binary.BigEndian.Uint16(data[8:10])
offset = 10
case 0x03: // 域名
if len(data) < 5 {
log.Println("无效的 UDP 数据包")
return
}
domainLen := data[4]
if len(data) < int(5+domainLen+2) {
log.Println("无效的 UDP 数据包")
return
}
domain := string(data[5 : 5+domainLen])
destAddr = domain
destPort = binary.BigEndian.Uint16(data[5+domainLen : 7+domainLen])
offset = int(7 + domainLen)
case 0x04: // IPv6
if len(data) < 22 {
log.Println("无效的 UDP 数据包")
return
}
destAddr = net.IP(data[4:20]).String()
destPort = binary.BigEndian.Uint16(data[20:22])
offset = 22
default:
log.Println("不支持的地址类型: ", atyp)
return
}
payload := data[offset:]
// 发送数据到目标
destUDPAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", destAddr, destPort))
if err != nil {
log.Println("解析目标 UDP 地址失败: ", err)
return
}
_, err = udpConn.WriteToUDP(payload, destUDPAddr)
if err != nil {
log.Println("发送数据到目标失败: ", err)
return
}
// 记录目标到客户端的映射
destKey := destUDPAddr.String()
clientKey := srcAddr.String()
destToClient.Store(destKey, clientKey)
}
初始化 mod 并且下载依赖
执行以下命令:
go mod init socks5-server
go mod tidy
Dockerfile 内容
# 使用官方 Golang 镜像作为基础镜像
FROM golang:1.20-alpine
# 设置工作目录
WORKDIR /app
# 将当前目录下的所有文件复制到容器的 /app 目录中
COPY . .
# 编译 Golang 应用程序
RUN go build -o socks5-server
# 运行编译好的二进制文件
CMD ["./socks5-server"]
docker-compose.yml 内容
services:
socks5-server: # 服务名称
build:
context: . # 构建上下文为当前目录
dockerfile: Dockerfile # 指定使用的 Dockerfile
ports:
- "1080:1080" # 将主机的 1080 端口映射到容器的 1080 端口
- "1080:1080/udp"
启动
docker compose up -d