feat: add SOCKS5 proxy support and enhance configuration

- Introduced SOCKS5 proxy functionality with separate ports for random rotation and lowest latency.
- Updated `.env.example` and `docker-compose.yml` to include SOCKS5 port configurations.
- Enhanced `main.go` to initialize and start SOCKS5 servers alongside existing HTTP proxies.
- Revised README to document new SOCKS5 features, including usage examples and testing scripts.
- Improved proxy selection logic in the storage layer to support SOCKS5 protocol.
This commit is contained in:
isboyjc
2026-03-30 01:02:00 +08:00
parent cede441015
commit b1555a702f
11 changed files with 1052 additions and 93 deletions

440
proxy/socks5_server.go Normal file
View File

@@ -0,0 +1,440 @@
package proxy
import (
"encoding/binary"
"fmt"
"io"
"log"
"net"
"time"
"goproxy/config"
"goproxy/storage"
)
// SOCKS5Server SOCKS5 协议服务器
type SOCKS5Server struct {
storage *storage.Storage
cfg *config.Config
mode string // "random" 或 "lowest-latency"
port string
}
// NewSOCKS5 创建 SOCKS5 服务器
func NewSOCKS5(s *storage.Storage, cfg *config.Config, mode string, port string) *SOCKS5Server {
return &SOCKS5Server{
storage: s,
cfg: cfg,
mode: mode,
port: port,
}
}
// Start 启动 SOCKS5 服务器
func (s *SOCKS5Server) Start() error {
modeDesc := "随机轮换"
if s.mode == "lowest-latency" {
modeDesc = "最低延迟"
}
authStatus := "无认证"
if s.cfg.ProxyAuthEnabled {
authStatus = fmt.Sprintf("需认证 (用户: %s)", s.cfg.ProxyAuthUsername)
}
log.Printf("socks5 server listening on %s [%s] [%s]", s.port, modeDesc, authStatus)
listener, err := net.Listen("tcp", s.port)
if err != nil {
return err
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go s.handleConnection(conn)
}
}
// handleConnection 处理 SOCKS5 连接
func (s *SOCKS5Server) handleConnection(clientConn net.Conn) {
defer clientConn.Close()
// SOCKS5 握手
if err := s.socks5Handshake(clientConn); err != nil {
log.Printf("[socks5] handshake failed: %v", err)
return
}
// 读取请求
target, err := s.readSOCKS5Request(clientConn)
if err != nil {
log.Printf("[socks5] read request failed: %v", err)
return
}
// 带重试的连接上游代理
// 重试机制:只使用 SOCKS5 协议的上游代理(天然支持 HTTPS
tried := []string{}
maxRetries := s.cfg.MaxRetry + 2 // 增加重试次数以应对质量差的代理
for attempt := 0; attempt <= maxRetries; attempt++ {
var p *storage.Proxy
var err error
// SOCKS5 服务只使用 SOCKS5 上游代理
if s.mode == "lowest-latency" {
p, err = s.storage.GetLowestLatencyByProtocolExclude("socks5", tried)
} else {
p, err = s.storage.GetRandomByProtocolExclude("socks5", tried)
}
if err != nil {
log.Printf("[socks5] no available socks5 upstream proxy: %v", err)
s.sendSOCKS5Reply(clientConn, 0x01) // General failure
return
}
tried = append(tried, p.Address)
// 连接上游代理
upstreamConn, err := s.dialViaProxy(p, target)
if err != nil {
log.Printf("[socks5] dial %s via %s (%s) failed: %v, removing", target, p.Address, p.Protocol, err)
s.storage.Delete(p.Address)
continue
}
// 发送成功响应
if err := s.sendSOCKS5Reply(clientConn, 0x00); err != nil {
upstreamConn.Close()
return
}
log.Printf("[socks5] %s via %s established", target, p.Address)
// 双向转发数据
go io.Copy(upstreamConn, clientConn)
io.Copy(clientConn, upstreamConn)
// 转发完成,关闭连接
upstreamConn.Close()
return
}
// 所有重试都失败
s.sendSOCKS5Reply(clientConn, 0x01) // General failure
log.Printf("[socks5] all proxies failed for %s", target)
}
// socks5Handshake 处理 SOCKS5 握手
func (s *SOCKS5Server) socks5Handshake(conn net.Conn) error {
buf := make([]byte, 257)
// 读取客户端问候: [VER(1), NMETHODS(1), METHODS(1-255)]
n, err := io.ReadAtLeast(conn, buf, 2)
if err != nil {
return err
}
version := buf[0]
if version != 0x05 {
return fmt.Errorf("unsupported SOCKS version: %d", version)
}
nmethods := int(buf[1])
if n < 2+nmethods {
if _, err := io.ReadFull(conn, buf[n:2+nmethods]); err != nil {
return err
}
}
// 检查是否需要认证
needAuth := s.cfg.ProxyAuthEnabled
methods := buf[2 : 2+nmethods]
// 选择认证方式
var selectedMethod byte = 0xFF // No acceptable methods
if needAuth {
// 需要用户名/密码认证 (0x02)
for _, method := range methods {
if method == 0x02 {
selectedMethod = 0x02
break
}
}
} else {
// 无需认证 (0x00)
for _, method := range methods {
if method == 0x00 {
selectedMethod = 0x00
break
}
}
}
// 发送方法选择: [VER(1), METHOD(1)]
if _, err := conn.Write([]byte{0x05, selectedMethod}); err != nil {
return err
}
if selectedMethod == 0xFF {
return fmt.Errorf("no acceptable authentication method")
}
// 如果需要认证,进行用户名/密码认证
if selectedMethod == 0x02 {
if err := s.socks5Auth(conn); err != nil {
return err
}
}
return nil
}
// socks5Auth 处理 SOCKS5 用户名/密码认证
func (s *SOCKS5Server) socks5Auth(conn net.Conn) error {
buf := make([]byte, 513)
// 读取认证请求: [VER(1), ULEN(1), UNAME(1-255), PLEN(1), PASSWD(1-255)]
n, err := io.ReadAtLeast(conn, buf, 2)
if err != nil {
return err
}
if buf[0] != 0x01 {
return fmt.Errorf("unsupported auth version: %d", buf[0])
}
ulen := int(buf[1])
if n < 2+ulen {
if _, err := io.ReadFull(conn, buf[n:2+ulen]); err != nil {
return err
}
n = 2 + ulen
}
username := string(buf[2 : 2+ulen])
// 读取密码长度和密码
if n < 2+ulen+1 {
if _, err := io.ReadFull(conn, buf[n:2+ulen+1]); err != nil {
return err
}
n = 2 + ulen + 1
}
plen := int(buf[2+ulen])
if n < 2+ulen+1+plen {
if _, err := io.ReadFull(conn, buf[n:2+ulen+1+plen]); err != nil {
return err
}
}
password := string(buf[2+ulen+1 : 2+ulen+1+plen])
// 验证用户名和密码
if username != s.cfg.ProxyAuthUsername || password != s.cfg.ProxyAuthPassword {
// 认证失败: [VER(1), STATUS(1)]
conn.Write([]byte{0x01, 0x01})
return fmt.Errorf("authentication failed")
}
// 认证成功: [VER(1), STATUS(1)]
if _, err := conn.Write([]byte{0x01, 0x00}); err != nil {
return err
}
return nil
}
// readSOCKS5Request 读取 SOCKS5 请求
func (s *SOCKS5Server) readSOCKS5Request(conn net.Conn) (string, error) {
buf := make([]byte, 262)
// 读取请求: [VER(1), CMD(1), RSV(1), ATYP(1), DST.ADDR(variable), DST.PORT(2)]
n, err := io.ReadAtLeast(conn, buf, 4)
if err != nil {
return "", err
}
if buf[0] != 0x05 {
return "", fmt.Errorf("invalid version: %d", buf[0])
}
cmd := buf[1]
if cmd != 0x01 { // 只支持 CONNECT
s.sendSOCKS5Reply(conn, 0x07) // Command not supported
return "", fmt.Errorf("unsupported command: %d", cmd)
}
atyp := buf[3]
var host string
var addrLen int
switch atyp {
case 0x01: // IPv4
addrLen = 4
if n < 4+addrLen+2 {
if _, err := io.ReadFull(conn, buf[n:4+addrLen+2]); err != nil {
return "", err
}
}
host = fmt.Sprintf("%d.%d.%d.%d", buf[4], buf[5], buf[6], buf[7])
case 0x03: // Domain name
addrLen = int(buf[4])
if n < 4+1+addrLen+2 {
if _, err := io.ReadFull(conn, buf[n:4+1+addrLen+2]); err != nil {
return "", err
}
}
host = string(buf[5 : 5+addrLen])
case 0x04: // IPv6
addrLen = 16
if n < 4+addrLen+2 {
if _, err := io.ReadFull(conn, buf[n:4+addrLen+2]); err != nil {
return "", err
}
}
// 简化处理,直接转换
host = net.IP(buf[4 : 4+addrLen]).String()
default:
s.sendSOCKS5Reply(conn, 0x08) // Address type not supported
return "", fmt.Errorf("unsupported address type: %d", atyp)
}
// 读取端口
portOffset := 4
if atyp == 0x03 {
portOffset = 5 + addrLen
} else {
portOffset = 4 + addrLen
}
port := binary.BigEndian.Uint16(buf[portOffset : portOffset+2])
return fmt.Sprintf("%s:%d", host, port), nil
}
// sendSOCKS5Reply 发送 SOCKS5 响应
func (s *SOCKS5Server) sendSOCKS5Reply(conn net.Conn, rep byte) error {
// [VER(1), REP(1), RSV(1), ATYP(1), BND.ADDR(variable), BND.PORT(2)]
// 简化:使用 0.0.0.0:0
reply := []byte{
0x05, // VER
rep, // REP: 0x00=成功, 0x01=一般失败, 0x07=命令不支持, 0x08=地址类型不支持
0x00, // RSV
0x01, // ATYP: IPv4
0, 0, 0, 0, // BND.ADDR: 0.0.0.0
0, 0, // BND.PORT: 0
}
_, err := conn.Write(reply)
return err
}
// dialViaProxy 通过上游代理连接目标
func (s *SOCKS5Server) dialViaProxy(p *storage.Proxy, target string) (net.Conn, error) {
timeout := time.Duration(s.cfg.ValidateTimeout) * time.Second
switch p.Protocol {
case "http":
// 连接到 HTTP 代理
conn, err := net.DialTimeout("tcp", p.Address, timeout)
if err != nil {
return nil, err
}
// 发送 CONNECT 请求
fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", target, target)
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil {
conn.Close()
return nil, err
}
// 检查 HTTP 响应
if n < 12 || string(buf[:12]) != "HTTP/1.1 200" {
conn.Close()
return nil, fmt.Errorf("upstream proxy connect failed")
}
return conn, nil
case "socks5":
// 使用 SOCKS5 代理
dialer := &net.Dialer{Timeout: timeout}
proxyConn, err := dialer.Dial("tcp", p.Address)
if err != nil {
return nil, err
}
// SOCKS5 握手(无认证)
if _, err := proxyConn.Write([]byte{0x05, 0x01, 0x00}); err != nil {
proxyConn.Close()
return nil, err
}
handshake := make([]byte, 2)
if _, err := io.ReadFull(proxyConn, handshake); err != nil {
proxyConn.Close()
return nil, err
}
if handshake[0] != 0x05 || handshake[1] != 0x00 {
proxyConn.Close()
return nil, fmt.Errorf("socks5 handshake failed")
}
// 发送 CONNECT 请求
host, port, err := net.SplitHostPort(target)
if err != nil {
proxyConn.Close()
return nil, err
}
// 构建请求
req := []byte{0x05, 0x01, 0x00} // VER, CMD=CONNECT, RSV
// 判断是 IP 还是域名
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
req = append(req, 0x01) // IPv4
req = append(req, ip4...)
} else {
req = append(req, 0x04) // IPv6
req = append(req, ip...)
}
} else {
req = append(req, 0x03) // Domain
req = append(req, byte(len(host)))
req = append(req, []byte(host)...)
}
// 添加端口
portNum := uint16(0)
fmt.Sscanf(port, "%d", &portNum)
portBytes := make([]byte, 2)
binary.BigEndian.PutUint16(portBytes, portNum)
req = append(req, portBytes...)
if _, err := proxyConn.Write(req); err != nil {
proxyConn.Close()
return nil, err
}
// 读取响应
reply := make([]byte, 10)
if _, err := io.ReadAtLeast(proxyConn, reply, 10); err != nil {
proxyConn.Close()
return nil, err
}
if reply[1] != 0x00 {
proxyConn.Close()
return nil, fmt.Errorf("socks5 connect failed, code: %d", reply[1])
}
return proxyConn, nil
default:
return nil, fmt.Errorf("unsupported protocol: %s", p.Protocol)
}
}