Files
GoProxy/proxy/socks5_server.go
isboyjc f03c3300b4 feat: implement custom proxy subscription management and enhance configuration
- Added support for importing Clash/V2ray subscriptions, including automatic format detection and integration with sing-box for protocol conversion.
- Introduced five proxy usage modes in the configuration, allowing flexible selection between mixed, custom-only, and free-only modes.
- Enhanced `.env.example` and `docker-compose.yml` to include new environment variables for custom proxy settings.
- Updated `CHANGELOG.md` to document new features and improvements related to subscription management.
- Improved WebUI for managing subscriptions and displaying proxy statistics.
- Implemented a background process for refreshing subscriptions and probing disabled proxies for reactivation.
2026-04-04 22:25:54 +08:00

468 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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++ {
p, err := s.selectSOCKS5Proxy(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.RecordProxyUse(p.Address, false)
removeOrDisableProxy(s.storage, p)
continue
}
// 发送成功响应
if err := s.sendSOCKS5Reply(clientConn, 0x00); err != nil {
upstreamConn.Close()
return
}
s.storage.RecordProxyUse(p.Address, true)
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)
}
// selectSOCKS5Proxy 根据使用模式选择 SOCKS5 上游代理
func (s *SOCKS5Server) selectSOCKS5Proxy(tried []string) (*storage.Proxy, error) {
cfg := config.Get()
sourceFilter := sourceFilterFromMode(cfg.CustomProxyMode)
// 混用 + 优先模式
if cfg.CustomProxyMode == "mixed" && (cfg.CustomPriority || cfg.CustomFreePriority) {
preferSource := "custom"
if cfg.CustomFreePriority {
preferSource = "free"
}
var p *storage.Proxy
var err error
if s.mode == "lowest-latency" {
p, err = s.storage.GetLowestLatencyByProtocolExcludeFiltered("socks5", tried, preferSource)
} else {
p, err = s.storage.GetRandomByProtocolExcludeFiltered("socks5", tried, preferSource)
}
if err == nil {
return p, nil
}
// fallback
if s.mode == "lowest-latency" {
return s.storage.GetLowestLatencyByProtocolExcludeFiltered("socks5", tried, "")
}
return s.storage.GetRandomByProtocolExcludeFiltered("socks5", tried, "")
}
if s.mode == "lowest-latency" {
return s.storage.GetLowestLatencyByProtocolExcludeFiltered("socks5", tried, sourceFilter)
}
return s.storage.GetRandomByProtocolExcludeFiltered("socks5", tried, sourceFilter)
}
// 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)
}
}