mirror of
https://github.com/isboyjc/GoProxy.git
synced 2026-05-06 20:02:54 +08:00
- 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.
441 lines
10 KiB
Go
441 lines
10 KiB
Go
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)
|
||
}
|
||
}
|