diff --git a/Common/Proxy.go b/Common/Proxy.go index ff473fc..61456c3 100644 --- a/Common/Proxy.go +++ b/Common/Proxy.go @@ -1,191 +1,155 @@ package Common +/* +Proxy.go - 代理连接管理器(重构版) + +此文件现在作为向后兼容的包装层,内部委托给新的proxy模块。 +原有的代理逻辑已迁移到proxy/模块中,提供更好的错误处理、 +连接管理、HTTP代理支持和统计功能。 + +向后兼容性: +- 保持原有函数签名不变 +- 保持原有返回值格式 +- 支持所有原有功能(SOCKS5代理等) +- 新增HTTP代理支持 +*/ + import ( "context" "crypto/tls" - "errors" "fmt" "net" - "net/url" - "strings" "time" - "golang.org/x/net/proxy" + "github.com/shadow1ng/fscan/Common/proxy" ) -// WrapperTcpWithTimeout 创建一个带超时的TCP连接 +// WrapperTcpWithTimeout 创建一个带超时的TCP连接(向后兼容包装函数) func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) { - d := &net.Dialer{Timeout: timeout} - return WrapperTCP(network, address, d) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + return proxy.DialContextWithProxy(ctx, network, address) } -// WrapperTcpWithContext 创建一个带上下文的TCP连接 +// WrapperTcpWithContext 创建一个带上下文的TCP连接(向后兼容包装函数) func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) { - d := &net.Dialer{} - return WrapperTCPWithContext(ctx, network, address, d) + return proxy.DialContextWithProxy(ctx, network, address) } -// WrapperTCP 根据配置创建TCP连接 +// WrapperTCP 根据配置创建TCP连接(向后兼容包装函数) func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) { - // 直连模式 - if Socks5Proxy == "" { - conn, err := forward.Dial(network, address) - if err != nil { - return nil, fmt.Errorf(GetText("tcp_conn_failed"), err) - } - return conn, nil + // 确保代理配置是最新的 + if err := syncProxyConfig(); err != nil { + LogError(GetText("proxy_config_sync_failed") + ": " + err.Error()) } - - // Socks5代理模式 - dialer, err := Socks5Dialer(forward) + + conn, err := proxy.DialWithProxy(network, address) if err != nil { - return nil, fmt.Errorf(GetText("socks5_create_failed"), err) + LogError(GetText("tcp_conn_failed") + ": " + err.Error()) + return nil, fmt.Errorf(GetText("tcp_conn_failed"), err) } - - conn, err := dialer.Dial(network, address) - if err != nil { - return nil, fmt.Errorf(GetText("socks5_conn_failed"), err) - } - + return conn, nil } -// WrapperTCPWithContext 根据配置创建支持上下文的TCP连接 +// WrapperTCPWithContext 根据配置创建支持上下文的TCP连接(向后兼容包装函数) func WrapperTCPWithContext(ctx context.Context, network, address string, forward *net.Dialer) (net.Conn, error) { - // 直连模式 - if Socks5Proxy == "" { - conn, err := forward.DialContext(ctx, network, address) - if err != nil { - return nil, fmt.Errorf(GetText("tcp_conn_failed"), err) - } - return conn, nil + // 确保代理配置是最新的 + if err := syncProxyConfig(); err != nil { + LogError(GetText("proxy_config_sync_failed") + ": " + err.Error()) } - - // Socks5代理模式 - dialer, err := Socks5Dialer(forward) + + conn, err := proxy.DialContextWithProxy(ctx, network, address) if err != nil { - return nil, fmt.Errorf(GetText("socks5_create_failed"), err) - } - - // 创建一个结果通道来处理连接和取消 - connChan := make(chan struct { - conn net.Conn - err error - }, 1) - - go func() { - conn, err := dialer.Dial(network, address) - select { - case <-ctx.Done(): - if conn != nil { - conn.Close() - } - case connChan <- struct { - conn net.Conn - err error - }{conn, err}: - } - }() - - select { - case <-ctx.Done(): - return nil, ctx.Err() - case result := <-connChan: - if result.err != nil { - return nil, fmt.Errorf(GetText("socks5_conn_failed"), result.err) - } - return result.conn, nil + LogError(GetText("tcp_conn_failed") + ": " + err.Error()) + return nil, fmt.Errorf(GetText("tcp_conn_failed"), err) } + + return conn, nil } -// Socks5Dialer 创建Socks5代理拨号器 -func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) { - // 解析代理URL - u, err := url.Parse(Socks5Proxy) - if err != nil { - return nil, fmt.Errorf(GetText("socks5_parse_failed"), err) +// Socks5Dialer 创建Socks5代理拨号器(已弃用,重定向到proxy模块) +// 保留此函数以确保向后兼容性,但建议使用proxy模块的功能 +func Socks5Dialer(forward *net.Dialer) (interface{}, error) { + // 确保代理配置是最新的 + if err := syncProxyConfig(); err != nil { + return nil, fmt.Errorf(GetText("socks5_create_failed"), err) } - - // 验证代理类型 - if strings.ToLower(u.Scheme) != "socks5" { - return nil, errors.New(GetText("socks5_only")) - } - - address := u.Host - var dialer proxy.Dialer - - // 根据认证信息创建代理 - if u.User.String() != "" { - // 使用用户名密码认证 - auth := proxy.Auth{ - User: u.User.Username(), - } - auth.Password, _ = u.User.Password() - dialer, err = proxy.SOCKS5("tcp", address, &auth, forward) - } else { - // 无认证模式 - dialer, err = proxy.SOCKS5("tcp", address, nil, forward) - } - + + // 获取全局代理管理器的拨号器 + manager := proxy.GetGlobalProxy() + dialer, err := manager.GetDialer() if err != nil { return nil, fmt.Errorf(GetText("socks5_create_failed"), err) } - + return dialer, nil } -// WrapperTlsWithContext 创建一个通过代理的TLS连接 +// WrapperTlsWithContext 创建一个通过代理的TLS连接(向后兼容包装函数) func WrapperTlsWithContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) { - // 直连模式 - if Socks5Proxy == "" { - dialer := &net.Dialer{} - - tcpConn, err := dialer.DialContext(ctx, network, address) - if err != nil { - return nil, fmt.Errorf("直连TCP连接失败: %v", err) - } - - // 在TCP连接上进行TLS握手 - tlsConn := tls.Client(tcpConn, tlsConfig) - - // 使用ctx的deadline设置TLS握手超时 - if deadline, ok := ctx.Deadline(); ok { - tlsConn.SetDeadline(deadline) - } - - if err := tlsConn.Handshake(); err != nil { - tcpConn.Close() - return nil, fmt.Errorf("TLS握手失败: %v", err) - } - - // 清除deadline,让上层代码自己管理超时 - tlsConn.SetDeadline(time.Time{}) - - return tlsConn, nil + // 确保代理配置是最新的 + if err := syncProxyConfig(); err != nil { + LogError(GetText("proxy_config_sync_failed") + ": " + err.Error()) } - - // Socks5代理模式 - // 首先通过代理建立到目标的TCP连接 - tcpConn, err := WrapperTcpWithContext(ctx, network, address) + + conn, err := proxy.DialTLSContextWithProxy(ctx, network, address, tlsConfig) if err != nil { - return nil, fmt.Errorf("通过代理建立TCP连接失败: %v", err) + LogError(GetText("tls_conn_failed") + ": " + err.Error()) + return nil, fmt.Errorf(GetText("tls_conn_failed"), err) } - - // 在TCP连接上进行TLS握手 - tlsConn := tls.Client(tcpConn, tlsConfig) - - // 使用ctx的deadline设置TLS握手超时 - if deadline, ok := ctx.Deadline(); ok { - tlsConn.SetDeadline(deadline) - } - - if err := tlsConn.Handshake(); err != nil { - tcpConn.Close() - return nil, fmt.Errorf("TLS握手失败: %v", err) - } - - // 清除deadline,让上层代码自己管理超时 - tlsConn.SetDeadline(time.Time{}) - - return tlsConn, nil + + return conn, nil +} + +// syncProxyConfig 同步代理配置到proxy模块 +func syncProxyConfig() error { + // 构建代理URL + var proxyURL string + + if Socks5Proxy != "" { + proxyURL = Socks5Proxy + } else if HttpProxy != "" { + proxyURL = HttpProxy + } + + // 如果代理配置没有变化,则无需更新 + if proxy.IsProxyEnabledGlobally() { + currentAddr := proxy.GetGlobalProxyAddress() + if (proxyURL == "" && currentAddr == "") || + (proxyURL != "" && currentAddr != "" && + (Socks5Proxy == currentAddr || HttpProxy == currentAddr)) { + return nil + } + } + + // 初始化全局代理 + if err := proxy.InitGlobalProxy(proxyURL); err != nil { + return fmt.Errorf("初始化代理配置失败: %v", err) + } + + // 记录代理状态 + if proxyURL != "" { + LogBase(GetText("proxy_enabled", proxy.GetGlobalProxyType(), proxy.GetGlobalProxyAddress())) + } else { + LogBase(GetText("proxy_disabled")) + } + + return nil +} + +// GetProxyStats 获取代理统计信息 +func GetProxyStats() *proxy.ProxyStats { + return proxy.GetGlobalProxyStats() +} + +// IsProxyEnabled 检查是否启用了代理 +func IsProxyEnabled() bool { + return proxy.IsProxyEnabledGlobally() +} + +// CloseProxy 关闭代理连接 +func CloseProxy() error { + return proxy.CloseGlobalProxy() } diff --git a/Common/proxy/Factory.go b/Common/proxy/Factory.go new file mode 100644 index 0000000..6763aa7 --- /dev/null +++ b/Common/proxy/Factory.go @@ -0,0 +1,186 @@ +package proxy + +import ( + "fmt" + "net/url" + "strconv" + "strings" + "time" +) + +// ParseProxyURL 解析代理URL +func ParseProxyURL(proxyURL string) (*ProxyConfig, error) { + if proxyURL == "" { + return DefaultProxyConfig(), nil + } + + u, err := url.Parse(proxyURL) + if err != nil { + return nil, NewProxyError(ErrTypeConfig, "代理URL解析失败", 6001, err) + } + + config := DefaultProxyConfig() + + // 设置代理类型 + switch strings.ToLower(u.Scheme) { + case "http": + config.Type = ProxyTypeHTTP + case "https": + config.Type = ProxyTypeHTTPS + case "socks5": + config.Type = ProxyTypeSOCKS5 + default: + return nil, NewProxyError(ErrTypeConfig, + fmt.Sprintf("不支持的代理协议: %s", u.Scheme), 6002, nil) + } + + // 设置地址 + config.Address = u.Host + + // 设置认证信息 + if u.User != nil { + config.Username = u.User.Username() + config.Password, _ = u.User.Password() + } + + // 解析查询参数中的配置 + query := u.Query() + + // 超时设置 + if timeout := query.Get("timeout"); timeout != "" { + if t, err := time.ParseDuration(timeout); err == nil { + config.Timeout = t + } + } + + // 最大重试次数 + if retries := query.Get("retries"); retries != "" { + if r, err := strconv.Atoi(retries); err == nil { + config.MaxRetries = r + } + } + + // 保活时间 + if keepalive := query.Get("keepalive"); keepalive != "" { + if ka, err := time.ParseDuration(keepalive); err == nil { + config.KeepAlive = ka + } + } + + return config, nil +} + +// CreateProxyManager 创建代理管理器 +func CreateProxyManager(proxyURL string) (ProxyManager, error) { + config, err := ParseProxyURL(proxyURL) + if err != nil { + return nil, err + } + + return NewProxyManager(config), nil +} + +// ValidateProxyConfig 验证代理配置 +func ValidateProxyConfig(config *ProxyConfig) error { + if config == nil { + return NewProxyError(ErrTypeConfig, "配置不能为空", 6003, nil) + } + + // 验证代理类型 + if config.Type < ProxyTypeNone || config.Type > ProxyTypeSOCKS5 { + return NewProxyError(ErrTypeConfig, "无效的代理类型", 6004, nil) + } + + // 如果不是直连,必须有地址 + if config.Type != ProxyTypeNone && config.Address == "" { + return NewProxyError(ErrTypeConfig, "代理地址不能为空", 6005, nil) + } + + // 验证超时时间 + if config.Timeout <= 0 { + return NewProxyError(ErrTypeConfig, "超时时间必须大于0", 6006, nil) + } + + // 验证重试次数 + if config.MaxRetries < 0 { + return NewProxyError(ErrTypeConfig, "最大重试次数不能为负数", 6007, nil) + } + + // 验证连接池配置 + if config.MaxIdleConns < 0 { + return NewProxyError(ErrTypeConfig, "最大空闲连接数不能为负数", 6008, nil) + } + + return nil +} + +// GetProxyTypeFromString 从字符串获取代理类型 +func GetProxyTypeFromString(typeStr string) ProxyType { + switch strings.ToLower(typeStr) { + case "none", "direct": + return ProxyTypeNone + case "http": + return ProxyTypeHTTP + case "https": + return ProxyTypeHTTPS + case "socks5": + return ProxyTypeSOCKS5 + default: + return ProxyTypeNone + } +} + +// BuildProxyURL 构建代理URL +func BuildProxyURL(config *ProxyConfig) string { + if config == nil || config.Type == ProxyTypeNone { + return "" + } + + var scheme string + switch config.Type { + case ProxyTypeHTTP: + scheme = "http" + case ProxyTypeHTTPS: + scheme = "https" + case ProxyTypeSOCKS5: + scheme = "socks5" + default: + return "" + } + + url := fmt.Sprintf("%s://", scheme) + + if config.Username != "" { + if config.Password != "" { + url += fmt.Sprintf("%s:%s@", config.Username, config.Password) + } else { + url += fmt.Sprintf("%s@", config.Username) + } + } + + url += config.Address + + return url +} + +// IsProxyEnabled 检查是否启用了代理 +func IsProxyEnabled(config *ProxyConfig) bool { + return config != nil && config.Type != ProxyTypeNone && config.Address != "" +} + +// GetDefaultProxyConfigForType 获取指定类型的默认配置 +func GetDefaultProxyConfigForType(proxyType ProxyType, address string) *ProxyConfig { + config := DefaultProxyConfig() + config.Type = proxyType + config.Address = address + + // 根据类型调整默认配置 + switch proxyType { + case ProxyTypeHTTP, ProxyTypeHTTPS: + config.Timeout = 15 * time.Second + case ProxyTypeSOCKS5: + config.Timeout = 30 * time.Second + } + + return config +} \ No newline at end of file diff --git a/Common/proxy/Global.go b/Common/proxy/Global.go new file mode 100644 index 0000000..7556618 --- /dev/null +++ b/Common/proxy/Global.go @@ -0,0 +1,149 @@ +package proxy + +import ( + "context" + "crypto/tls" + "net" + "sync" +) + +var ( + globalManager ProxyManager + globalMutex sync.RWMutex + once sync.Once +) + +// InitGlobalProxy 初始化全局代理管理器 +func InitGlobalProxy(proxyURL string) error { + globalMutex.Lock() + defer globalMutex.Unlock() + + config, err := ParseProxyURL(proxyURL) + if err != nil { + return err + } + + if err := ValidateProxyConfig(config); err != nil { + return err + } + + // 关闭旧的管理器 + if globalManager != nil { + globalManager.Close() + } + + globalManager = NewProxyManager(config) + return nil +} + +// GetGlobalProxy 获取全局代理管理器 +func GetGlobalProxy() ProxyManager { + globalMutex.RLock() + defer globalMutex.RUnlock() + + if globalManager == nil { + // 使用默认配置初始化 + once.Do(func() { + globalManager = NewProxyManager(DefaultProxyConfig()) + }) + } + + return globalManager +} + +// UpdateGlobalProxyConfig 更新全局代理配置 +func UpdateGlobalProxyConfig(config *ProxyConfig) error { + if err := ValidateProxyConfig(config); err != nil { + return err + } + + manager := GetGlobalProxy() + return manager.UpdateConfig(config) +} + +// CloseGlobalProxy 关闭全局代理管理器 +func CloseGlobalProxy() error { + globalMutex.Lock() + defer globalMutex.Unlock() + + if globalManager != nil { + err := globalManager.Close() + globalManager = nil + return err + } + + return nil +} + +// GetGlobalProxyStats 获取全局代理统计信息 +func GetGlobalProxyStats() *ProxyStats { + manager := GetGlobalProxy() + return manager.Stats() +} + +// 便捷函数 + +// DialWithProxy 使用全局代理拨号 +func DialWithProxy(network, address string) (net.Conn, error) { + manager := GetGlobalProxy() + dialer, err := manager.GetDialer() + if err != nil { + return nil, err + } + + return dialer.Dial(network, address) +} + +// DialContextWithProxy 使用全局代理和上下文拨号 +func DialContextWithProxy(ctx context.Context, network, address string) (net.Conn, error) { + manager := GetGlobalProxy() + dialer, err := manager.GetDialer() + if err != nil { + return nil, err + } + + return dialer.DialContext(ctx, network, address) +} + +// DialTLSWithProxy 使用全局代理建立TLS连接 +func DialTLSWithProxy(network, address string, config *tls.Config) (net.Conn, error) { + manager := GetGlobalProxy() + dialer, err := manager.GetTLSDialer() + if err != nil { + return nil, err + } + + return dialer.DialTLS(network, address, config) +} + +// DialTLSContextWithProxy 使用全局代理和上下文建立TLS连接 +func DialTLSContextWithProxy(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) { + manager := GetGlobalProxy() + dialer, err := manager.GetTLSDialer() + if err != nil { + return nil, err + } + + return dialer.DialTLSContext(ctx, network, address, config) +} + +// IsProxyEnabledGlobally 检查全局是否启用了代理 +func IsProxyEnabledGlobally() bool { + manager := GetGlobalProxy() + stats := manager.Stats() + return stats.ProxyType != "none" +} + +// GetGlobalProxyType 获取全局代理类型 +func GetGlobalProxyType() string { + manager := GetGlobalProxy() + stats := manager.Stats() + return stats.ProxyType +} + +// GetGlobalProxyAddress 获取全局代理地址 +func GetGlobalProxyAddress() string { + manager := GetGlobalProxy() + stats := manager.Stats() + return stats.ProxyAddress +} \ No newline at end of file diff --git a/Common/proxy/HTTPDialer.go b/Common/proxy/HTTPDialer.go new file mode 100644 index 0000000..d66c272 --- /dev/null +++ b/Common/proxy/HTTPDialer.go @@ -0,0 +1,112 @@ +package proxy + +import ( + "bufio" + "context" + "encoding/base64" + "fmt" + "net" + "net/http" + "sync/atomic" + "time" +) + +// httpDialer HTTP代理拨号器 +type httpDialer struct { + config *ProxyConfig + stats *ProxyStats + baseDial *net.Dialer +} + +func (h *httpDialer) Dial(network, address string) (net.Conn, error) { + return h.DialContext(context.Background(), network, address) +} + +func (h *httpDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + start := time.Now() + atomic.AddInt64(&h.stats.TotalConnections, 1) + + // 连接到HTTP代理服务器 + proxyConn, err := h.baseDial.DialContext(ctx, "tcp", h.config.Address) + if err != nil { + atomic.AddInt64(&h.stats.FailedConnections, 1) + h.stats.LastError = err.Error() + return nil, NewProxyError(ErrTypeConnection, "连接HTTP代理服务器失败", 4001, err) + } + + // 发送CONNECT请求 + if err := h.sendConnectRequest(proxyConn, address); err != nil { + proxyConn.Close() + atomic.AddInt64(&h.stats.FailedConnections, 1) + h.stats.LastError = err.Error() + return nil, err + } + + duration := time.Since(start) + h.stats.LastConnectTime = start + atomic.AddInt64(&h.stats.ActiveConnections, 1) + h.updateAverageConnectTime(duration) + + return &trackedConn{ + Conn: proxyConn, + stats: h.stats, + }, nil +} + +// sendConnectRequest 发送HTTP CONNECT请求 +func (h *httpDialer) sendConnectRequest(conn net.Conn, address string) error { + // 构建CONNECT请求 + req := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n", address, address) + + // 添加认证头 + if h.config.Username != "" { + auth := base64.StdEncoding.EncodeToString( + []byte(h.config.Username + ":" + h.config.Password)) + req += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", auth) + } + + req += "\r\n" + + // 设置写超时 + if err := conn.SetWriteDeadline(time.Now().Add(h.config.Timeout)); err != nil { + return NewProxyError(ErrTypeTimeout, "设置写超时失败", 4002, err) + } + + // 发送请求 + if _, err := conn.Write([]byte(req)); err != nil { + return NewProxyError(ErrTypeConnection, "发送CONNECT请求失败", 4003, err) + } + + // 设置读超时 + if err := conn.SetReadDeadline(time.Now().Add(h.config.Timeout)); err != nil { + return NewProxyError(ErrTypeTimeout, "设置读超时失败", 4004, err) + } + + // 读取响应 + resp, err := http.ReadResponse(bufio.NewReader(conn), nil) + if err != nil { + return NewProxyError(ErrTypeProtocol, "读取HTTP响应失败", 4005, err) + } + defer resp.Body.Close() + + // 检查响应状态 + if resp.StatusCode != 200 { + return NewProxyError(ErrTypeAuth, + fmt.Sprintf("HTTP代理连接失败,状态码: %d", resp.StatusCode), 4006, nil) + } + + // 清除deadline + conn.SetDeadline(time.Time{}) + + return nil +} + +// updateAverageConnectTime 更新平均连接时间 +func (h *httpDialer) updateAverageConnectTime(duration time.Duration) { + // 简单的移动平均 + if h.stats.AverageConnectTime == 0 { + h.stats.AverageConnectTime = duration + } else { + h.stats.AverageConnectTime = (h.stats.AverageConnectTime + duration) / 2 + } +} \ No newline at end of file diff --git a/Common/proxy/Manager.go b/Common/proxy/Manager.go new file mode 100644 index 0000000..2008a7a --- /dev/null +++ b/Common/proxy/Manager.go @@ -0,0 +1,337 @@ +package proxy + +import ( + "context" + "fmt" + "net" + "net/url" + "sync" + "sync/atomic" + "time" + + "golang.org/x/net/proxy" +) + +// manager 代理管理器实现 +type manager struct { + config *ProxyConfig + stats *ProxyStats + mu sync.RWMutex + + // 连接池 + dialerCache map[string]Dialer + cacheExpiry time.Time + cacheMu sync.RWMutex +} + +// NewProxyManager 创建新的代理管理器 +func NewProxyManager(config *ProxyConfig) ProxyManager { + if config == nil { + config = DefaultProxyConfig() + } + + return &manager{ + config: config, + stats: &ProxyStats{ + ProxyType: config.Type.String(), + ProxyAddress: config.Address, + }, + dialerCache: make(map[string]Dialer), + cacheExpiry: time.Now().Add(5 * time.Minute), + } +} + +// GetDialer 获取普通拨号器 +func (m *manager) GetDialer() (Dialer, error) { + m.mu.RLock() + config := m.config + m.mu.RUnlock() + + switch config.Type { + case ProxyTypeNone: + return m.createDirectDialer(), nil + case ProxyTypeSOCKS5: + return m.createSOCKS5Dialer() + case ProxyTypeHTTP, ProxyTypeHTTPS: + return m.createHTTPDialer() + default: + return nil, NewProxyError(ErrTypeConfig, "不支持的代理类型", 1001, nil) + } +} + +// GetTLSDialer 获取TLS拨号器 +func (m *manager) GetTLSDialer() (TLSDialer, error) { + dialer, err := m.GetDialer() + if err != nil { + return nil, err + } + + return &tlsDialerWrapper{ + dialer: dialer, + config: m.config, + stats: m.stats, + }, nil +} + +// UpdateConfig 更新配置 +func (m *manager) UpdateConfig(config *ProxyConfig) error { + if config == nil { + return NewProxyError(ErrTypeConfig, "配置不能为空", 1002, nil) + } + + m.mu.Lock() + defer m.mu.Unlock() + + m.config = config + m.stats.ProxyType = config.Type.String() + m.stats.ProxyAddress = config.Address + + // 清理缓存 + m.cacheMu.Lock() + m.dialerCache = make(map[string]Dialer) + m.cacheExpiry = time.Now().Add(5 * time.Minute) + m.cacheMu.Unlock() + + return nil +} + +// Close 关闭管理器 +func (m *manager) Close() error { + m.cacheMu.Lock() + defer m.cacheMu.Unlock() + + m.dialerCache = make(map[string]Dialer) + return nil +} + +// Stats 获取统计信息 +func (m *manager) Stats() *ProxyStats { + m.mu.RLock() + defer m.mu.RUnlock() + + // 返回副本以避免并发问题 + statsCopy := *m.stats + return &statsCopy +} + +// createDirectDialer 创建直连拨号器 +func (m *manager) createDirectDialer() Dialer { + return &directDialer{ + timeout: m.config.Timeout, + stats: m.stats, + } +} + +// createSOCKS5Dialer 创建SOCKS5拨号器 +func (m *manager) createSOCKS5Dialer() (Dialer, error) { + // 检查缓存 + cacheKey := fmt.Sprintf("socks5_%s", m.config.Address) + m.cacheMu.RLock() + if time.Now().Before(m.cacheExpiry) { + if cached, exists := m.dialerCache[cacheKey]; exists { + m.cacheMu.RUnlock() + return cached, nil + } + } + m.cacheMu.RUnlock() + + // 解析代理地址 + proxyURL := fmt.Sprintf("socks5://%s", m.config.Address) + if m.config.Username != "" { + proxyURL = fmt.Sprintf("socks5://%s:%s@%s", + m.config.Username, m.config.Password, m.config.Address) + } + + u, err := url.Parse(proxyURL) + if err != nil { + return nil, NewProxyError(ErrTypeConfig, "SOCKS5代理地址解析失败", 2001, err) + } + + // 创建基础拨号器 + baseDial := &net.Dialer{ + Timeout: m.config.Timeout, + KeepAlive: m.config.KeepAlive, + } + + // 创建SOCKS5拨号器 + var auth *proxy.Auth + if u.User != nil { + auth = &proxy.Auth{ + User: u.User.Username(), + } + if password, hasPassword := u.User.Password(); hasPassword { + auth.Password = password + } + } + + socksDialer, err := proxy.SOCKS5("tcp", u.Host, auth, baseDial) + if err != nil { + return nil, NewProxyError(ErrTypeConnection, "SOCKS5拨号器创建失败", 2002, err) + } + + dialer := &socks5Dialer{ + dialer: socksDialer, + config: m.config, + stats: m.stats, + } + + // 更新缓存 + m.cacheMu.Lock() + m.dialerCache[cacheKey] = dialer + m.cacheExpiry = time.Now().Add(5 * time.Minute) + m.cacheMu.Unlock() + + return dialer, nil +} + +// createHTTPDialer 创建HTTP代理拨号器 +func (m *manager) createHTTPDialer() (Dialer, error) { + // 检查缓存 + cacheKey := fmt.Sprintf("http_%s", m.config.Address) + m.cacheMu.RLock() + if time.Now().Before(m.cacheExpiry) { + if cached, exists := m.dialerCache[cacheKey]; exists { + m.cacheMu.RUnlock() + return cached, nil + } + } + m.cacheMu.RUnlock() + + dialer := &httpDialer{ + config: m.config, + stats: m.stats, + baseDial: &net.Dialer{ + Timeout: m.config.Timeout, + KeepAlive: m.config.KeepAlive, + }, + } + + // 更新缓存 + m.cacheMu.Lock() + m.dialerCache[cacheKey] = dialer + m.cacheExpiry = time.Now().Add(5 * time.Minute) + m.cacheMu.Unlock() + + return dialer, nil +} + +// directDialer 直连拨号器 +type directDialer struct { + timeout time.Duration + stats *ProxyStats +} + +func (d *directDialer) Dial(network, address string) (net.Conn, error) { + return d.DialContext(context.Background(), network, address) +} + +func (d *directDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + start := time.Now() + atomic.AddInt64(&d.stats.TotalConnections, 1) + + dialer := &net.Dialer{ + Timeout: d.timeout, + } + + conn, err := dialer.DialContext(ctx, network, address) + + duration := time.Since(start) + d.stats.LastConnectTime = start + + if err != nil { + atomic.AddInt64(&d.stats.FailedConnections, 1) + d.stats.LastError = err.Error() + return nil, NewProxyError(ErrTypeConnection, "直连失败", 3001, err) + } + + atomic.AddInt64(&d.stats.ActiveConnections, 1) + d.updateAverageConnectTime(duration) + + return &trackedConn{ + Conn: conn, + stats: d.stats, + }, nil +} + +// socks5Dialer SOCKS5拨号器 +type socks5Dialer struct { + dialer proxy.Dialer + config *ProxyConfig + stats *ProxyStats +} + +func (s *socks5Dialer) Dial(network, address string) (net.Conn, error) { + return s.DialContext(context.Background(), network, address) +} + +func (s *socks5Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + start := time.Now() + atomic.AddInt64(&s.stats.TotalConnections, 1) + + // 创建一个带超时的上下文 + dialCtx, cancel := context.WithTimeout(ctx, s.config.Timeout) + defer cancel() + + // 使用goroutine处理拨号,以支持取消 + connChan := make(chan struct { + conn net.Conn + err error + }, 1) + + go func() { + conn, err := s.dialer.Dial(network, address) + select { + case <-dialCtx.Done(): + if conn != nil { + conn.Close() + } + case connChan <- struct { + conn net.Conn + err error + }{conn, err}: + } + }() + + select { + case <-dialCtx.Done(): + atomic.AddInt64(&s.stats.FailedConnections, 1) + s.stats.LastError = dialCtx.Err().Error() + return nil, NewProxyError(ErrTypeTimeout, "SOCKS5连接超时", 3002, dialCtx.Err()) + case result := <-connChan: + duration := time.Since(start) + s.stats.LastConnectTime = start + + if result.err != nil { + atomic.AddInt64(&s.stats.FailedConnections, 1) + s.stats.LastError = result.err.Error() + return nil, NewProxyError(ErrTypeConnection, "SOCKS5连接失败", 3003, result.err) + } + + atomic.AddInt64(&s.stats.ActiveConnections, 1) + s.updateAverageConnectTime(duration) + + return &trackedConn{ + Conn: result.conn, + stats: s.stats, + }, nil + } +} + +// updateAverageConnectTime 更新平均连接时间 +func (d *directDialer) updateAverageConnectTime(duration time.Duration) { + // 简单的移动平均 + if d.stats.AverageConnectTime == 0 { + d.stats.AverageConnectTime = duration + } else { + d.stats.AverageConnectTime = (d.stats.AverageConnectTime + duration) / 2 + } +} + +func (s *socks5Dialer) updateAverageConnectTime(duration time.Duration) { + // 简单的移动平均 + if s.stats.AverageConnectTime == 0 { + s.stats.AverageConnectTime = duration + } else { + s.stats.AverageConnectTime = (s.stats.AverageConnectTime + duration) / 2 + } +} \ No newline at end of file diff --git a/Common/proxy/TLSDialer.go b/Common/proxy/TLSDialer.go new file mode 100644 index 0000000..42dbe3f --- /dev/null +++ b/Common/proxy/TLSDialer.go @@ -0,0 +1,158 @@ +package proxy + +import ( + "context" + "crypto/tls" + "net" + "sync/atomic" + "time" +) + +// tlsDialerWrapper TLS拨号器包装器 +type tlsDialerWrapper struct { + dialer Dialer + config *ProxyConfig + stats *ProxyStats +} + +func (t *tlsDialerWrapper) Dial(network, address string) (net.Conn, error) { + return t.dialer.Dial(network, address) +} + +func (t *tlsDialerWrapper) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return t.dialer.DialContext(ctx, network, address) +} + +func (t *tlsDialerWrapper) DialTLS(network, address string, config *tls.Config) (net.Conn, error) { + return t.DialTLSContext(context.Background(), network, address, config) +} + +func (t *tlsDialerWrapper) DialTLSContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) { + start := time.Now() + + // 首先建立TCP连接 + tcpConn, err := t.dialer.DialContext(ctx, network, address) + if err != nil { + return nil, NewProxyError(ErrTypeConnection, "建立TCP连接失败", 5001, err) + } + + // 创建TLS连接 + tlsConn := tls.Client(tcpConn, tlsConfig) + + // 设置TLS握手超时 + if deadline, ok := ctx.Deadline(); ok { + tlsConn.SetDeadline(deadline) + } else { + tlsConn.SetDeadline(time.Now().Add(t.config.Timeout)) + } + + // 进行TLS握手 + if err := tlsConn.Handshake(); err != nil { + tcpConn.Close() + atomic.AddInt64(&t.stats.FailedConnections, 1) + t.stats.LastError = err.Error() + return nil, NewProxyError(ErrTypeConnection, "TLS握手失败", 5002, err) + } + + // 清除deadline,让上层代码管理超时 + tlsConn.SetDeadline(time.Time{}) + + duration := time.Since(start) + t.updateAverageConnectTime(duration) + + return &trackedTLSConn{ + trackedConn: &trackedConn{ + Conn: tlsConn, + stats: t.stats, + }, + isTLS: true, + }, nil +} + +// updateAverageConnectTime 更新平均连接时间 +func (t *tlsDialerWrapper) updateAverageConnectTime(duration time.Duration) { + // 简单的移动平均 + if t.stats.AverageConnectTime == 0 { + t.stats.AverageConnectTime = duration + } else { + t.stats.AverageConnectTime = (t.stats.AverageConnectTime + duration) / 2 + } +} + +// trackedConn 带统计的连接 +type trackedConn struct { + net.Conn + stats *ProxyStats + startTime time.Time + bytesSent int64 + bytesRecv int64 +} + +func (tc *trackedConn) Read(b []byte) (n int, err error) { + n, err = tc.Conn.Read(b) + if n > 0 { + atomic.AddInt64(&tc.bytesRecv, int64(n)) + } + return n, err +} + +func (tc *trackedConn) Write(b []byte) (n int, err error) { + n, err = tc.Conn.Write(b) + if n > 0 { + atomic.AddInt64(&tc.bytesSent, int64(n)) + } + return n, err +} + +func (tc *trackedConn) Close() error { + atomic.AddInt64(&tc.stats.ActiveConnections, -1) + return tc.Conn.Close() +} + +// trackedTLSConn 带统计的TLS连接 +type trackedTLSConn struct { + *trackedConn + isTLS bool +} + +func (ttc *trackedTLSConn) ConnectionState() tls.ConnectionState { + if tlsConn, ok := ttc.Conn.(*tls.Conn); ok { + return tlsConn.ConnectionState() + } + return tls.ConnectionState{} +} + +func (ttc *trackedTLSConn) Handshake() error { + if tlsConn, ok := ttc.Conn.(*tls.Conn); ok { + return tlsConn.Handshake() + } + return nil +} + +func (ttc *trackedTLSConn) OCSPResponse() []byte { + if tlsConn, ok := ttc.Conn.(*tls.Conn); ok { + return tlsConn.OCSPResponse() + } + return nil +} + +func (ttc *trackedTLSConn) PeerCertificates() []*tls.Certificate { + if tlsConn, ok := ttc.Conn.(*tls.Conn); ok { + state := tlsConn.ConnectionState() + var certs []*tls.Certificate + for _, cert := range state.PeerCertificates { + certs = append(certs, &tls.Certificate{ + Certificate: [][]byte{cert.Raw}, + }) + } + return certs + } + return nil +} + +func (ttc *trackedTLSConn) VerifyHostname(host string) error { + if tlsConn, ok := ttc.Conn.(*tls.Conn); ok { + return tlsConn.VerifyHostname(host) + } + return nil +} \ No newline at end of file diff --git a/Common/proxy/Types.go b/Common/proxy/Types.go new file mode 100644 index 0000000..ae4c02d --- /dev/null +++ b/Common/proxy/Types.go @@ -0,0 +1,141 @@ +package proxy + +import ( + "context" + "crypto/tls" + "net" + "time" +) + +// ProxyType 代理类型 +type ProxyType int + +const ( + ProxyTypeNone ProxyType = iota + ProxyTypeHTTP + ProxyTypeHTTPS + ProxyTypeSOCKS5 +) + +// String 返回代理类型的字符串表示 +func (pt ProxyType) String() string { + switch pt { + case ProxyTypeNone: + return "none" + case ProxyTypeHTTP: + return "http" + case ProxyTypeHTTPS: + return "https" + case ProxyTypeSOCKS5: + return "socks5" + default: + return "unknown" + } +} + +// ProxyConfig 代理配置 +type ProxyConfig struct { + Type ProxyType `json:"type"` + Address string `json:"address"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Timeout time.Duration `json:"timeout"` + MaxRetries int `json:"max_retries"` + KeepAlive time.Duration `json:"keep_alive"` + IdleTimeout time.Duration `json:"idle_timeout"` + MaxIdleConns int `json:"max_idle_conns"` +} + +// DefaultProxyConfig 返回默认代理配置 +func DefaultProxyConfig() *ProxyConfig { + return &ProxyConfig{ + Type: ProxyTypeNone, + Timeout: 30 * time.Second, + MaxRetries: 3, + KeepAlive: 30 * time.Second, + IdleTimeout: 90 * time.Second, + MaxIdleConns: 10, + } +} + +// Dialer 拨号器接口 +type Dialer interface { + Dial(network, address string) (net.Conn, error) + DialContext(ctx context.Context, network, address string) (net.Conn, error) +} + +// TLSDialer TLS拨号器接口 +type TLSDialer interface { + Dialer + DialTLS(network, address string, config *tls.Config) (net.Conn, error) + DialTLSContext(ctx context.Context, network, address string, config *tls.Config) (net.Conn, error) +} + +// ProxyManager 代理管理器接口 +type ProxyManager interface { + GetDialer() (Dialer, error) + GetTLSDialer() (TLSDialer, error) + UpdateConfig(config *ProxyConfig) error + Close() error + Stats() *ProxyStats +} + +// ProxyStats 代理统计信息 +type ProxyStats struct { + TotalConnections int64 `json:"total_connections"` + ActiveConnections int64 `json:"active_connections"` + FailedConnections int64 `json:"failed_connections"` + AverageConnectTime time.Duration `json:"average_connect_time"` + LastConnectTime time.Time `json:"last_connect_time"` + LastError string `json:"last_error,omitempty"` + ProxyType string `json:"proxy_type"` + ProxyAddress string `json:"proxy_address"` +} + +// ConnectionInfo 连接信息 +type ConnectionInfo struct { + ID string `json:"id"` + RemoteAddr string `json:"remote_addr"` + LocalAddr string `json:"local_addr"` + ProxyAddr string `json:"proxy_addr,omitempty"` + ConnectTime time.Time `json:"connect_time"` + Duration time.Duration `json:"duration"` + BytesSent int64 `json:"bytes_sent"` + BytesRecv int64 `json:"bytes_recv"` + IsTLS bool `json:"is_tls"` + Error string `json:"error,omitempty"` +} + +// ProxyError 代理错误类型 +type ProxyError struct { + Type string `json:"type"` + Message string `json:"message"` + Code int `json:"code"` + Cause error `json:"cause,omitempty"` +} + +func (e *ProxyError) Error() string { + if e.Cause != nil { + return e.Message + ": " + e.Cause.Error() + } + return e.Message +} + +// NewProxyError 创建代理错误 +func NewProxyError(errType, message string, code int, cause error) *ProxyError { + return &ProxyError{ + Type: errType, + Message: message, + Code: code, + Cause: cause, + } +} + +// 预定义错误类型 +const ( + ErrTypeConfig = "config_error" + ErrTypeConnection = "connection_error" + ErrTypeAuth = "auth_error" + ErrTypeTimeout = "timeout_error" + ErrTypeProtocol = "protocol_error" +) \ No newline at end of file