mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
refactor: 重构代理系统为模块化架构
将Proxy.go的代理连接逻辑重构为完整的模块化系统, 提供更好的性能、可维护性和功能扩展。 主要变更: - Proxy.go: 从192行单体实现重构为简洁包装层 - 新增proxy模块包含5个核心文件:Types.go、Manager.go等 - 支持多种代理类型:HTTP、HTTPS、SOCKS5、直连 - 实现连接池、缓存机制和智能资源管理 - 添加详细的连接统计和性能监控 - 提供线程安全的配置管理和动态更新 - 完全保持API向后兼容性,无需修改调用代码 新增功能: - HTTP/HTTPS代理支持(原来仅支持SOCKS5) - 连接跟踪和统计分析 - 错误分类和详细上下文信息 - 配置验证和URL解析 - 全局代理管理实例 测试验证: - 编译无错误 ✓ - 基础连接功能正常 ✓ - 向后兼容性验证通过 ✓
This commit is contained in:
parent
f09bfdc346
commit
879293e680
252
Common/Proxy.go
252
Common/Proxy.go
@ -1,132 +1,85 @@
|
|||||||
package Common
|
package Common
|
||||||
|
|
||||||
|
/*
|
||||||
|
Proxy.go - 代理连接管理器(重构版)
|
||||||
|
|
||||||
|
此文件现在作为向后兼容的包装层,内部委托给新的proxy模块。
|
||||||
|
原有的代理逻辑已迁移到proxy/模块中,提供更好的错误处理、
|
||||||
|
连接管理、HTTP代理支持和统计功能。
|
||||||
|
|
||||||
|
向后兼容性:
|
||||||
|
- 保持原有函数签名不变
|
||||||
|
- 保持原有返回值格式
|
||||||
|
- 支持所有原有功能(SOCKS5代理等)
|
||||||
|
- 新增HTTP代理支持
|
||||||
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"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) {
|
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||||
d := &net.Dialer{Timeout: timeout}
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
return WrapperTCP(network, address, d)
|
defer cancel()
|
||||||
|
|
||||||
|
return proxy.DialContextWithProxy(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapperTcpWithContext 创建一个带上下文的TCP连接
|
// WrapperTcpWithContext 创建一个带上下文的TCP连接(向后兼容包装函数)
|
||||||
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
|
func WrapperTcpWithContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
d := &net.Dialer{}
|
return proxy.DialContextWithProxy(ctx, network, address)
|
||||||
return WrapperTCPWithContext(ctx, network, address, d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapperTCP 根据配置创建TCP连接
|
// WrapperTCP 根据配置创建TCP连接(向后兼容包装函数)
|
||||||
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
|
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
|
||||||
// 直连模式
|
// 确保代理配置是最新的
|
||||||
if Socks5Proxy == "" {
|
if err := syncProxyConfig(); err != nil {
|
||||||
conn, err := forward.Dial(network, address)
|
LogError(GetText("proxy_config_sync_failed") + ": " + err.Error())
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(GetText("tcp_conn_failed"), err)
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Socks5代理模式
|
conn, err := proxy.DialWithProxy(network, address)
|
||||||
dialer, err := Socks5Dialer(forward)
|
|
||||||
if err != nil {
|
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
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapperTCPWithContext 根据配置创建支持上下文的TCP连接
|
// WrapperTCPWithContext 根据配置创建支持上下文的TCP连接(向后兼容包装函数)
|
||||||
func WrapperTCPWithContext(ctx context.Context, network, address string, forward *net.Dialer) (net.Conn, error) {
|
func WrapperTCPWithContext(ctx context.Context, network, address string, forward *net.Dialer) (net.Conn, error) {
|
||||||
// 直连模式
|
// 确保代理配置是最新的
|
||||||
if Socks5Proxy == "" {
|
if err := syncProxyConfig(); err != nil {
|
||||||
conn, err := forward.DialContext(ctx, network, address)
|
LogError(GetText("proxy_config_sync_failed") + ": " + err.Error())
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(GetText("tcp_conn_failed"), err)
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Socks5代理模式
|
conn, err := proxy.DialContextWithProxy(ctx, network, address)
|
||||||
dialer, err := Socks5Dialer(forward)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
LogError(GetText("tcp_conn_failed") + ": " + err.Error())
|
||||||
|
return nil, fmt.Errorf(GetText("tcp_conn_failed"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建一个结果通道来处理连接和取消
|
// 获取全局代理管理器的拨号器
|
||||||
connChan := make(chan struct {
|
manager := proxy.GetGlobalProxy()
|
||||||
conn net.Conn
|
dialer, err := manager.GetDialer()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证代理类型
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
|
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
|
||||||
}
|
}
|
||||||
@ -134,58 +87,69 @@ func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) {
|
|||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapperTlsWithContext 创建一个通过代理的TLS连接
|
// WrapperTlsWithContext 创建一个通过代理的TLS连接(向后兼容包装函数)
|
||||||
func WrapperTlsWithContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
|
func WrapperTlsWithContext(ctx context.Context, network, address string, tlsConfig *tls.Config) (net.Conn, error) {
|
||||||
// 直连模式
|
// 确保代理配置是最新的
|
||||||
if Socks5Proxy == "" {
|
if err := syncProxyConfig(); err != nil {
|
||||||
dialer := &net.Dialer{}
|
LogError(GetText("proxy_config_sync_failed") + ": " + err.Error())
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Socks5代理模式
|
conn, err := proxy.DialTLSContextWithProxy(ctx, network, address, tlsConfig)
|
||||||
// 首先通过代理建立到目标的TCP连接
|
|
||||||
tcpConn, err := WrapperTcpWithContext(ctx, network, address)
|
|
||||||
if err != nil {
|
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握手
|
return conn, nil
|
||||||
tlsConn := tls.Client(tcpConn, tlsConfig)
|
}
|
||||||
|
|
||||||
// 使用ctx的deadline设置TLS握手超时
|
// syncProxyConfig 同步代理配置到proxy模块
|
||||||
if deadline, ok := ctx.Deadline(); ok {
|
func syncProxyConfig() error {
|
||||||
tlsConn.SetDeadline(deadline)
|
// 构建代理URL
|
||||||
}
|
var proxyURL string
|
||||||
|
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
if Socks5Proxy != "" {
|
||||||
tcpConn.Close()
|
proxyURL = Socks5Proxy
|
||||||
return nil, fmt.Errorf("TLS握手失败: %v", err)
|
} else if HttpProxy != "" {
|
||||||
}
|
proxyURL = HttpProxy
|
||||||
|
}
|
||||||
// 清除deadline,让上层代码自己管理超时
|
|
||||||
tlsConn.SetDeadline(time.Time{})
|
// 如果代理配置没有变化,则无需更新
|
||||||
|
if proxy.IsProxyEnabledGlobally() {
|
||||||
return tlsConn, nil
|
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()
|
||||||
}
|
}
|
||||||
|
186
Common/proxy/Factory.go
Normal file
186
Common/proxy/Factory.go
Normal file
@ -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
|
||||||
|
}
|
149
Common/proxy/Global.go
Normal file
149
Common/proxy/Global.go
Normal file
@ -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
|
||||||
|
}
|
112
Common/proxy/HTTPDialer.go
Normal file
112
Common/proxy/HTTPDialer.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
337
Common/proxy/Manager.go
Normal file
337
Common/proxy/Manager.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
158
Common/proxy/TLSDialer.go
Normal file
158
Common/proxy/TLSDialer.go
Normal file
@ -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
|
||||||
|
}
|
141
Common/proxy/Types.go
Normal file
141
Common/proxy/Types.go
Normal file
@ -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"
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user