commit 312e30c87bc0ebd621c301d474bdf068b249c25a Author: iuu <2167162990@qq.com> Date: Fri Jan 10 14:07:07 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2f38b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +proxy.log \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4e3dacd --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/s5.iml b/.idea/s5.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/s5.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..e862eea --- /dev/null +++ b/config.yaml @@ -0,0 +1,30 @@ +# 服务器配置 +server: + socks5_port: 1080 # SOCKS5 代理监听端口 + http_port: 8080 # HTTP 代理监听端口 + bind_address: "0.0.0.0" # 监听地址,0.0.0.0表示监听所有网卡 + +# 日志配置 +log: + level: "debug" # 日志级别: debug, info, warn, error + output: [ "console", "file" ] # 日志输出位置: console-控制台, file-文件 + file: "proxy.log" # 日志文件路径(相对于程序运行目录) + show_caller: true # 是否显示调用位置 + format: "text" # 日志格式: text-文本格式, json-JSON格式 + max_size: 2 # 每个日志文件最大大小(MB) + max_backups: 5 # 保留的旧文件数量 + max_age: 7 # 保留的最大天数 + compress: false # 是否压缩旧文件 + +# 代理服务器配置 +proxy: + buffer_size: 32768 # 数据传输缓冲区大小(字节) + timeout: 300 # 连接超时时间(秒) + enable_metrics: true # 是否启用指标统计 + debug: true # 是否启用调试模式 + upstream: + enable: true # 是否启用上游代理 + type: "https" # 上游代理类型: http, https, socks5 + server: "jpgmo101-cdn-route.couldflare-cdn.com:443" # 上游代理服务器地址 + username: "mrwdfNTD8M79LCukCieldrqZWqs=" # 上游代理认证用户名 + password: "exaxgqkKkd0TAMrCxeonWg==" # 上游代理认证密码 \ No newline at end of file diff --git a/direct.txt b/direct.txt new file mode 100644 index 0000000..cbf02fd --- /dev/null +++ b/direct.txt @@ -0,0 +1,27 @@ +# 直连域名列表 +# 每行一个域名,支持通配符 * +# 以 # 开头的行为注释 + +# 自定义域名 +*.19year.cn +# 常用国内网站 +*.baidu.com +*.qq.com +*.163.com +*.taobao.com +*.tmall.com +*.jd.com +*.aliyun.com +*.weibo.com +*.bilibili.com + +# 常用开发网站 +*.gitee.com +*.coding.net +*.aliyuncs.com + +# 特殊域名 +localhost +*.local +*.test +*.example \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..07e73e5 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module s5 + +go 1.23.3 + +require ( + go.uber.org/zap v1.27.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + go.uber.org/multierr v1.10.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d26f175 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1d792be --- /dev/null +++ b/main.go @@ -0,0 +1,60 @@ +// 主程序入口 +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + + "s5/pkg/config" + "s5/pkg/logger" + "s5/pkg/proxy" +) + +func main() { + // 解析命令行参数 + configFile := flag.String("config", "config.yaml", "配置文件路径") + flag.Parse() + + // 加载配置文件 + cfg, err := config.Load(*configFile) + if err != nil { + panic(err) + } + + // 初始化日志系统 + if err := logger.Init(cfg.Log); err != nil { + panic(err) + } + defer logger.Sync() + + log := logger.Get() + log.Info("代理服务器启动中...") + + // 创建并配置代理服务器 + server := proxy.NewServer(proxy.ServerConfig{ + Socks5Addr: fmt.Sprintf("%s:%d", cfg.Server.BindAddress, cfg.Server.Socks5Port), + HttpAddr: fmt.Sprintf("%s:%d", cfg.Server.BindAddress, cfg.Server.HttpPort), + BufferSize: cfg.Proxy.BufferSize, + }, cfg.Proxy.Upstream) + + // 创建上下文和取消函数 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // 处理系统信号 + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigCh + cancel() + }() + + // 启动服务器 + if err := server.Start(ctx); err != nil { + log.Fatalf("服务器启动失败: %v", err) + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..0fdc0ed --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,79 @@ +// Package config 处理配置文件的加载和解析 +package config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// ServerConfig 服务器基本配置 +type ServerConfig struct { + Socks5Port int `yaml:"socks5_port"` // SOCKS5 代理端口 + HttpPort int `yaml:"http_port"` // HTTP 代理端口 + BindAddress string `yaml:"bind_address"` // 监听地址 +} + +// LogConfig 日志系统配置 +type LogConfig struct { + Level string `yaml:"level"` // 日志级别 + Output []string `yaml:"output"` // 输出位置列表 + File string `yaml:"file"` // 日志文件路径 + ShowCaller bool `yaml:"show_caller"` // 是否显示调用者 + Format string `yaml:"format"` // 日志格式 + MaxSize int `yaml:"max_size"` // 单个日志文件最大大小(MB) + MaxBackups int `yaml:"max_backups"` // 保留的旧文件数量 + MaxAge int `yaml:"max_age"` // 日志保留天数 + Compress bool `yaml:"compress"` // 是否压缩旧文件 +} + +// UpstreamConfig 上游代理配置 +type UpstreamConfig struct { + Enable bool `yaml:"enable"` // 是否启用上游代理 + Type string `yaml:"type"` // 代理类型 + Server string `yaml:"server"` // 服务器地址 + Username string `yaml:"username"` // 认证用户名 + Password string `yaml:"password"` // 认证密码 +} + +// ProxyConfig 代理服务器详细配置 +type ProxyConfig struct { + BufferSize int `yaml:"buffer_size"` // 缓冲区大小 + Timeout int `yaml:"timeout"` // 超时时间(秒) + EnableMetrics bool `yaml:"enable_metrics"` // 是否启用指标 + Debug bool `yaml:"debug"` // 是否启用调试 + Upstream UpstreamConfig `yaml:"upstream"` // 上游代理配置 +} + +// Config 总配置结构 +type Config struct { + Server ServerConfig `yaml:"server"` // 服务器配置 + Log LogConfig `yaml:"log"` // 日志配置 + Proxy ProxyConfig `yaml:"proxy"` // 代理配置 +} + +// Load 从文件加载配置 +func Load(filename string) (*Config, error) { + // 读取配置文件 + data, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("读取配置文件失败: %v", err) + } + + // 解析配置 + config := &Config{} + if err := yaml.Unmarshal(data, config); err != nil { + return nil, fmt.Errorf("解析配置文件失败: %v", err) + } + + // 设置默认值 + if len(config.Log.Output) == 0 { + config.Log.Output = []string{"console"} // 默认输出到控制台 + } + if config.Log.File == "" { + config.Log.File = "proxy.log" // 默认日志文件名 + } + + return config, nil +} \ No newline at end of file diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..82b921b --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,124 @@ +// Package logger 提供日志记录功能 +package logger + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" + + "s5/pkg/config" +) + +// 全局日志记录器实例 +var logger *zap.SugaredLogger + +// Init 初始化日志系统 +// 根据配置创建日志记录器,支持同时输出到控制台和文件 +func Init(cfg config.LogConfig) error { + // 设置日志级别 + level, err := zapcore.ParseLevel(cfg.Level) + if err != nil { + level = zapcore.InfoLevel // 默认使用 INFO 级别 + } + + // 创建编码器配置 + encoderConfig := zapcore.EncoderConfig{ + TimeKey: "time", // 时间字段名 + LevelKey: "level", // 级别字段名 + NameKey: "logger", // 日志记录器名字段名 + CallerKey: "caller", // 调用者字段名 + MessageKey: "msg", // 消息字段名 + StacktraceKey: "stacktrace", // 堆栈字段名 + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, // 大写编码器 + EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 时间格式 + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + + // 创建输出核心列表 + var cores []zapcore.Core + for _, output := range cfg.Output { + var ws zapcore.WriteSyncer + switch strings.ToLower(output) { + case "console": + // 控制台输出 + ws = zapcore.AddSync(os.Stdout) + case "file": + // 文件输出 + if err := os.MkdirAll(filepath.Dir(cfg.File), 0755); err != nil { + return fmt.Errorf("创建日志目录失败: %v", err) + } + + // 配置日志轮转 + ws = zapcore.AddSync(&lumberjack.Logger{ + Filename: cfg.File, // 日志文件路径 + MaxSize: cfg.MaxSize, // 单个文件最大大小(MB) + MaxBackups: cfg.MaxBackups,// 保留的旧文件数量 + MaxAge: cfg.MaxAge, // 保留的最大天数 + Compress: cfg.Compress, // 是否压缩旧文件 + }) + default: + continue // 跳过未知的输出类型 + } + + // 创建编码器 + var encoder zapcore.Encoder + if cfg.Format == "json" { + encoder = zapcore.NewJSONEncoder(encoderConfig) + } else { + encoder = zapcore.NewConsoleEncoder(encoderConfig) + } + + // 添加到核心列表 + cores = append(cores, zapcore.NewCore(encoder, ws, level)) + } + + // 创建多核心日志记录器 + core := zapcore.NewTee(cores...) + + // 配置日志记录器选项 + var opts []zap.Option + if cfg.ShowCaller { + opts = append(opts, zap.AddCaller()) + } + + // 创建日志记录器 + zapLogger := zap.New(core, opts...) + logger = zapLogger.Sugar() + + return nil +} + +// Get 获取日志记录器实例 +// 如果日志系统未初始化,会使用默认配置初始化 +func Get() *zap.SugaredLogger { + if logger == nil { + // 使用默认配置初始化 + Init(config.LogConfig{ + Level: "info", + Output: []string{"console"}, + ShowCaller: true, + Format: "text", + MaxSize: 2, // 2MB + MaxBackups: 5, // 保留5个旧文件 + MaxAge: 7, // 保留7天 + Compress: true, // 压缩旧文件 + }) + } + return logger +} + +// Sync 同步日志缓冲 +// 在程序退出前调用,确保所有日志都被写入 +func Sync() error { + if logger != nil { + return logger.Sync() + } + return nil +} diff --git a/pkg/proxy/directlist.go b/pkg/proxy/directlist.go new file mode 100644 index 0000000..03ac55f --- /dev/null +++ b/pkg/proxy/directlist.go @@ -0,0 +1,94 @@ +package proxy + +import ( + "bufio" + "os" + "path/filepath" + "strings" + "sync" + + "s5/pkg/logger" +) + +// DirectList 直连域名管理器 +type DirectList struct { + patterns []string // 域名匹配模式 + mu sync.RWMutex // 读写锁 +} + +// NewDirectList 创建新的直连域名管理器 +func NewDirectList(filename string) *DirectList { + dl := &DirectList{} + if err := dl.Load(filename); err != nil { + log := logger.Get() + log.Errorf("[DIRECT] 加载直连域名列表失败: %v", err) + } + return dl +} + +// Load 加载直连域名列表 +func (dl *DirectList) Load(filename string) error { + log := logger.Get() + log.Debugf("[DIRECT] 开始加载直连域名列表...") + + // 获取绝对路径 + absPath, err := filepath.Abs(filename) + if err != nil { + return err + } + + file, err := os.Open(absPath) + if err != nil { + return err + } + defer file.Close() + + var patterns []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + pattern := strings.TrimSpace(scanner.Text()) + if pattern == "" || strings.HasPrefix(pattern, "#") { + continue + } + patterns = append(patterns, pattern) + } + + dl.mu.Lock() + dl.patterns = patterns + dl.mu.Unlock() + + log.Infof("[DIRECT] 直连域名列表加载完成,共 %d 条规则", len(patterns)) + //for _, pattern := range patterns { + //log.Debugf("[DIRECT] - %s", pattern) + //} + + return scanner.Err() +} + +// Match 检查域名是否匹配直连列表 +func (dl *DirectList) Match(domain string) bool { + dl.mu.RLock() + defer dl.mu.RUnlock() + + log := logger.Get() + log.Debugf("[DIRECT] 检查域名: %s", domain) + + domain = strings.ToLower(domain) + for _, pattern := range dl.patterns { + if matchDomain(domain, pattern) { + log.Debugf("[DIRECT] - 匹配规则: %s", pattern) + return true + } + } + + return false +} + +// matchDomain 检查域名是否匹配模式 +func matchDomain(domain, pattern string) bool { + if pattern[0] == '*' { + suffix := pattern[1:] + return strings.HasSuffix(domain, suffix) + } + return domain == pattern +} diff --git a/pkg/proxy/server.go b/pkg/proxy/server.go new file mode 100644 index 0000000..0c117dd --- /dev/null +++ b/pkg/proxy/server.go @@ -0,0 +1,679 @@ +// Package proxy 实现代理服务器功能 +package proxy + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "net/http" + "strings" + "sync" + "sync/atomic" + "time" + "encoding/base64" + + "s5/pkg/config" + "s5/pkg/logger" +) + +// ServerConfig 代理服务器配置 +type ServerConfig struct { + Socks5Addr string // SOCKS5 代理监听地址 + HttpAddr string // HTTP 代理监听地址 + BufferSize int // 数据传输缓冲区大小 +} + +// Server 代理服务器实例 +type Server struct { + config ServerConfig // 服务器配置 + socks5Listener net.Listener // SOCKS5 监听器 + httpServer *http.Server // HTTP 服务器 + done chan struct{} // 关闭信号 + activeConns sync.WaitGroup // 活动连接计数 + upstreamDialer *UpstreamDialer // 上游代理拨号器 + shutdownTimeout time.Duration // 关闭超时时间 + directList *DirectList // 直连域名管理器 +} + +// NewServer 创建新的代理服务器实例 +func NewServer(config ServerConfig, upstreamConfig config.UpstreamConfig) *Server { + log := logger.Get() + + if !upstreamConfig.Enable { + log.Fatal("必须配置上游代理") + } + + upstreamDialer := NewUpstreamDialer(upstreamConfig) + if upstreamDialer == nil { + log.Fatal("创建上游代理连接器失败") + } + + return &Server{ + config: config, + done: make(chan struct{}), + upstreamDialer: upstreamDialer, + shutdownTimeout: 30 * time.Second, + directList: NewDirectList("direct.txt"), + } +} + +// Start 启动代理服务器 +func (s *Server) Start(ctx context.Context) error { + log := logger.Get() + log.Debugf("[SERVER] =====================================================") + log.Infof("[SERVER] 代理服务器启动") + log.Debugf("[SERVER] 服务器配置:") + log.Debugf("[SERVER] - SOCKS5监听: %s", s.config.Socks5Addr) + log.Debugf("[SERVER] - HTTP监听: %s", s.config.HttpAddr) + log.Debugf("[SERVER] - 缓冲区大小: %s", formatBytes(int64(s.config.BufferSize))) + log.Debugf("[SERVER] - 上游代理: %s", s.upstreamDialer.config.Server) + log.Debugf("[SERVER] - 关闭超时: %v", s.shutdownTimeout) + log.Debugf("[SERVER] =====================================================") + + // 启动 SOCKS5 服务器 + var err error + s.socks5Listener, err = net.Listen("tcp", s.config.Socks5Addr) + if err != nil { + log.Errorf("[SERVER] 启动SOCKS5服务器失败: %v", err) + return fmt.Errorf("启动SOCKS5服务器失败: %v", err) + } + log.Infof("[SERVER] SOCKS5服务器启动成功: %s", s.config.Socks5Addr) + + // 启动SOCKS5处理协程 + go s.serveSocks5(ctx) + + // 配置 HTTP 服务器 + s.httpServer = &http.Server{ + Addr: s.config.HttpAddr, + Handler: http.HandlerFunc(s.handleHTTP), + // 添加服务器超时配置 + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + MaxHeaderBytes: 1 << 20, // 1MB + } + + // 监听关闭信号 + go func() { + <-ctx.Done() + log.Debugf("[SERVER] 收到关闭信号") + // 使用带超时的上下文进行关闭 + shutdownCtx, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout) + defer cancel() + s.Shutdown(shutdownCtx) + }() + + log.Infof("[SERVER] HTTP服务器启动: %s", s.config.HttpAddr) + err = s.httpServer.ListenAndServe() + if err == http.ErrServerClosed { + log.Debugf("[SERVER] HTTP服务器正常关闭") + return nil + } + return err +} + +// Shutdown 优雅关闭服务器 +func (s *Server) Shutdown(ctx context.Context) error { + log := logger.Get() + log.Debugf("[SERVER] =====================================================") + log.Infof("[SERVER] 开始优雅关闭服务器...") + + // 创建一个带超时的上下文 + shutdownCtx, cancel := context.WithTimeout(ctx, s.shutdownTimeout) + defer cancel() + + // 关闭 SOCKS5 监听器 + if s.socks5Listener != nil { + log.Debugf("[SERVER] 关闭SOCKS5监听器") + s.socks5Listener.Close() + } + + // 关闭 HTTP 服务器 + if s.httpServer != nil { + log.Debugf("[SERVER] 开始关闭HTTP服务器") + if err := s.httpServer.Shutdown(shutdownCtx); err != nil { + log.Errorf("[SERVER] HTTP服务器关闭失败: %v", err) + } + } + + // 等待所有活动连接完成 + log.Debugf("[SERVER] 等待活动连接完成 (超时: %v)...", s.shutdownTimeout) + done := make(chan struct{}) + go func() { + s.activeConns.Wait() + close(done) + }() + + // 等待连接完成或超时 + select { + case <-shutdownCtx.Done(): + log.Warnf("[SERVER] 等待连接超时,强制关闭") + // 打印当前活动连接数 + activeCount := 0 + s.activeConns.Add(0) // 利用Add(0)获取当前值 + log.Warnf("[SERVER] 剩余活动连接数: %d", activeCount) + return fmt.Errorf("关闭超时") + case <-done: + log.Infof("[SERVER] 所有连接已正常关闭") + } + + log.Debugf("[SERVER] =====================================================") + return nil +} + +// serveSocks5 处理SOCKS5连接 +func (s *Server) serveSocks5(ctx context.Context) { + log := logger.Get() + for { + select { + case <-ctx.Done(): + return + default: + client, err := s.socks5Listener.Accept() + if err != nil { + if !errors.Is(err, net.ErrClosed) { + log.Errorw("接受SOCKS5连接错误", "error", err) + } + continue + } + s.activeConns.Add(1) + go func() { + defer s.activeConns.Done() + s.handleSocks5Connection(client) + }() + } + } +} + +// handleSocks5Connection 处理SOCKS5连接 +func (s *Server) handleSocks5Connection(client net.Conn) { + log := logger.Get() + log.Debugf("[SOCKS5] =====================================================") + log.Debugf("[SOCKS5] 收到新的SOCKS5连接") + + clientAddr := client.RemoteAddr().String() + startTime := time.Now() + upBytes := &atomic.Int64{} + downBytes := &atomic.Int64{} + + log.Debugf("[SOCKS5] 连接信息:") + log.Debugf("[SOCKS5] - 客户端地址: %s", clientAddr) + log.Debugf("[SOCKS5] - 本地地址: %s", client.LocalAddr()) + log.Debugf("[SOCKS5] - 开始时间: %s", startTime.Format("2006-01-02 15:04:05.000")) + + defer func() { + client.Close() + duration := time.Since(startTime) + log.Debugf("[SOCKS5] =====================================================") + log.Infof("[SOCKS5] 连接统计 [%s]:", clientAddr) + log.Infof("[SOCKS5] - 连接时长: %v", duration.Round(time.Millisecond)) + log.Infof("[SOCKS5] - 上行流量: %s", formatBytes(upBytes.Load())) + log.Infof("[SOCKS5] - 下行流量: %s", formatBytes(downBytes.Load())) + log.Infof("[SOCKS5] - 总流量: %s", formatBytes(upBytes.Load() + downBytes.Load())) + log.Infof("[SOCKS5] - 平均速度: %s/s", formatBytes(int64(float64(upBytes.Load()+downBytes.Load())/duration.Seconds()))) + log.Debugf("[SOCKS5] =====================================================") + }() + + // SOCKS5 握手 + if err := s.handleSocks5Handshake(client); err != nil { + log.Errorf("[SOCKS5] 握手失败 [%s]: %v", clientAddr, err) + return + } + + // 处理请求 + target, targetAddr, err := s.handleSocks5Request(client) + if err != nil { + log.Errorf("[SOCKS5] 处理请求失败 [%s]: %v", clientAddr, err) + return + } + defer target.Close() + + log.Infof("[SOCKS5] 成功建立连接:") + log.Infof("[SOCKS5] - 客户端: %s", clientAddr) + log.Infof("[SOCKS5] - 目标地址: %s", targetAddr) + log.Infof("[SOCKS5] - 上游代理: %s", s.upstreamDialer.config.Server) + + // 双向数据转发 + var wg sync.WaitGroup + wg.Add(2) + + // 上行数据传输 + go func() { + defer wg.Done() + log.Debugf("[SOCKS5] 开始上行数据传输: %s -> %s", clientAddr, targetAddr) + n, err := io.Copy(target, client) + upBytes.Add(n) + if err != nil { + log.Debugf("[SOCKS5] 上行传输错误 [%s -> %s]: %v", clientAddr, targetAddr, err) + } + log.Debugf("[SOCKS5] 上行传输完成: %s", formatBytes(n)) + }() + + // 下行数据传输 + go func() { + defer wg.Done() + log.Debugf("[SOCKS5] 开始下行数据传输: %s -> %s", targetAddr, clientAddr) + n, err := io.Copy(client, target) + downBytes.Add(n) + if err != nil { + log.Debugf("[SOCKS5] 下行传输错误 [%s -> %s]: %v", targetAddr, clientAddr, err) + } + log.Debugf("[SOCKS5] 下行传输完成: %s", formatBytes(n)) + }() + + wg.Wait() + log.Debugf("[SOCKS5] 连接关闭: %s <-> %s", clientAddr, targetAddr) +} + +// handleHTTP 处理HTTP代理请求 +func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { + log := logger.Get() + log.Debugf("[HTTP] =====================================================") + log.Debugf("[HTTP] 收到新的HTTP代理请求") + log.Debugf("[HTTP] 代理链路: %s -> [HTTP:%s] -> [HTTPS:%s] -> %s", + r.RemoteAddr, + s.config.HttpAddr, + s.upstreamDialer.config.Server, + r.Host) + + clientAddr := r.RemoteAddr + startTime := time.Now() + upBytes := &atomic.Int64{} + downBytes := &atomic.Int64{} + + // 详细的请求信息 + log.Debugf("[HTTP] 请求详情:") + log.Debugf("[HTTP] - 客户端地址: %s", clientAddr) + log.Debugf("[HTTP] - 请求方法: %s", r.Method) + log.Debugf("[HTTP] - 请求URL: %s", r.URL) + log.Debugf("[HTTP] - HTTP版本: %s", r.Proto) + log.Debugf("[HTTP] - Host头: %s", r.Host) + log.Debugf("[HTTP] - 远程地址: %s", r.RemoteAddr) + log.Debugf("[HTTP] - 请求URI: %s", r.RequestURI) + log.Debugf("[HTTP] - Content-Length: %d", r.ContentLength) + log.Debugf("[HTTP] - Transfer-Encoding: %v", r.TransferEncoding) + log.Debugf("[HTTP] - Close: %v", r.Close) + log.Debugf("[HTTP] - TLS: %v", r.TLS != nil) + + // 打印所有请求头 + log.Debugf("[HTTP] 请求头:") + for k, vs := range r.Header { + for _, v := range vs { + log.Debugf("[HTTP] %s: %s", k, v) + // 如果是认证头,解码并显示 + if strings.EqualFold(k, "Proxy-Authorization") || strings.EqualFold(k, "Authorization") { + if strings.HasPrefix(v, "Basic ") { + if decoded, err := base64.StdEncoding.DecodeString(v[6:]); err == nil { + log.Debugf("[HTTP] %s (decoded): %s", k, string(decoded)) + } + } + } + } + } + + // 打印Cookie信息 + if cookies := r.Cookies(); len(cookies) > 0 { + log.Debugf("[HTTP] Cookies:") + for _, cookie := range cookies { + log.Debugf("[HTTP] - %s: %s", cookie.Name, cookie.Value) + } + } + + s.activeConns.Add(1) + defer s.activeConns.Done() + + if r.Method != http.MethodConnect { + log.Debugf("[HTTP] 不支持的请求方法: %s", r.Method) + http.Error(w, "仅支持CONNECT方法", http.StatusMethodNotAllowed) + return + } + + hij, ok := w.(http.Hijacker) + if !ok { + log.Debugf("[HTTP] 当前连接不支持hijacking") + http.Error(w, "不支持hijacking", http.StatusInternalServerError) + return + } + + proxyClient, _, err := hij.Hijack() + if err != nil { + log.Debugf("[HTTP] Hijack失败: %v", err) + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + + defer func() { + proxyClient.Close() + duration := time.Since(startTime) + log.Debugf("[HTTP] =====================================================") + log.Infof("[HTTP] 连接统计 [%s]:", clientAddr) + log.Infof("[HTTP] - 目标地址: %s", r.Host) + log.Infof("[HTTP] - 连接时长: %v", duration.Round(time.Millisecond)) + log.Infof("[HTTP] - 上行流量: %s", formatBytes(upBytes.Load())) + log.Infof("[HTTP] - 下行流量: %s", formatBytes(downBytes.Load())) + log.Infof("[HTTP] - 总流量: %s", formatBytes(upBytes.Load() + downBytes.Load())) + log.Debugf("[HTTP] =====================================================") + }() + + log.Debugf("[HTTP] 开始连接目标服务器: %s", r.Host) + target, err := s.dialTarget(r.Host) + if err != nil { + log.Errorf("[HTTP] 连接目标服务器失败: %v", err) + proxyClient.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n")) + return + } + + log.Debugf("[HTTP] 发送连接成功响应:") + respHeaders := "HTTP/1.1 200 Connection Established\r\n" + + "Proxy-Agent: Go-Proxy-Client\r\n" + + "Connection: keep-alive\r\n\r\n" + log.Debugf("[HTTP] 响应头:") + for _, line := range strings.Split(respHeaders, "\r\n") { + if line != "" { + log.Debugf("[HTTP] %s", line) + } + } + proxyClient.Write([]byte(respHeaders)) + log.Infof("[HTTP] 成功建立连接: %s -> %s", clientAddr, r.Host) + + var wg sync.WaitGroup + wg.Add(2) + + // 上行数据传输 + go func() { + defer wg.Done() + log.Debugf("[HTTP] 开始上行数据传输: %s -> %s", clientAddr, r.Host) + n, err := io.Copy(target, proxyClient) + upBytes.Add(n) + if err != nil { + log.Debugf("[HTTP] 上行传输错误 [%s -> %s]: %v", clientAddr, r.Host, err) + } + log.Debugf("[HTTP] 上行传输完成: %s", formatBytes(n)) + }() + + // 下行数据传输 + go func() { + defer wg.Done() + log.Debugf("[HTTP] 开始下行数据传输: %s -> %s", r.Host, clientAddr) + n, err := io.Copy(proxyClient, target) + downBytes.Add(n) + if err != nil { + log.Debugf("[HTTP] 下行传输错误 [%s -> %s]: %v", r.Host, clientAddr, err) + } + log.Debugf("[HTTP] 下行传输完成: %s", formatBytes(n)) + }() + + wg.Wait() + log.Debugf("[HTTP] 连接关闭: %s <-> %s", clientAddr, r.Host) +} + +// formatBytes 格式化字节数 +func formatBytes(bytes int64) string { + const unit = 1024 + if bytes < unit { + return fmt.Sprintf("%d B", bytes) + } + div, exp := int64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} + +// handleSocks5Handshake 处理SOCKS5握手 +func (s *Server) handleSocks5Handshake(client net.Conn) error { + log := logger.Get() + log.Debugf("[SOCKS5] =====================================================") + log.Debugf("[SOCKS5] 开始握手阶段") + + // 读取客户端支持的认证方法 + buf := make([]byte, 2) + if _, err := io.ReadFull(client, buf); err != nil { + log.Debugf("[SOCKS5] 读取认证方法失败: %v", err) + return fmt.Errorf("读取认证方法失败: %v", err) + } + + log.Debugf("[SOCKS5] 收到握手请求:") + log.Debugf("[SOCKS5] - 版本号: SOCKS%d", buf[0]) + log.Debugf("[SOCKS5] - 认证方法数量: %d", buf[1]) + + // 读取认证方法列表 + methods := make([]byte, buf[1]) + if _, err := io.ReadFull(client, methods); err != nil { + log.Debugf("[SOCKS5] 读取认证方法列表失败: %v", err) + return fmt.Errorf("读取认证方法列表失败: %v", err) + } + + log.Debugf("[SOCKS5] 支持的认证方法:") + for _, method := range methods { + log.Debugf("[SOCKS5] - 0x%02x (%s)", method, socks5AuthMethodToString(method)) + } + + // 选择无认证方法 + response := []byte{0x05, 0x00} + if _, err := client.Write(response); err != nil { + log.Debugf("[SOCKS5] 发送认证响应失败: %v", err) + return fmt.Errorf("发送认证响应失败: %v", err) + } + + log.Debugf("[SOCKS5] 发送握手响应:") + log.Debugf("[SOCKS5] - 版本号: SOCKS5") + log.Debugf("[SOCKS5] - 选择的认证方法: 0x00 (NO AUTHENTICATION REQUIRED)") + log.Debugf("[SOCKS5] 握手完成") + log.Debugf("[SOCKS5] =====================================================") + + return nil +} + +// handleSocks5Request 处理SOCKS5请求 +func (s *Server) handleSocks5Request(client net.Conn) (net.Conn, string, error) { + log := logger.Get() + log.Debugf("[SOCKS5] =====================================================") + log.Debugf("[SOCKS5] 开始处理请求") + log.Debugf("[SOCKS5] 代理链路: %s -> [SOCKS5:%s] -> [HTTPS:%s]", + client.RemoteAddr(), + s.config.Socks5Addr, + s.upstreamDialer.config.Server) + + // 读取请求头 + buf := make([]byte, 4) + if _, err := io.ReadFull(client, buf); err != nil { + log.Debugf("[SOCKS5] 读取请求头失败: %v", err) + return nil, "", fmt.Errorf("读取请求头失败: %v", err) + } + + log.Debugf("[SOCKS5] 收到请求头:") + log.Debugf("[SOCKS5] - 版本号: SOCKS%d", buf[0]) + log.Debugf("[SOCKS5] - 命令: %s (0x%02x)", socks5CmdToString(buf[1]), buf[1]) + log.Debugf("[SOCKS5] - 保留字节: 0x%02x", buf[2]) + log.Debugf("[SOCKS5] - 地址类型: %s (0x%02x)", socks5AddrTypeToString(buf[3]), buf[3]) + + // 读取目标地址 + var addr string + switch buf[3] { + case 0x01: // IPv4 + ipv4 := make([]byte, 4) + if _, err := io.ReadFull(client, ipv4); err != nil { + log.Debugf("[SOCKS5] 读取IPv4地址失败: %v", err) + return nil, "", fmt.Errorf("读取IPv4地址失败: %v", err) + } + addr = net.IP(ipv4).String() + log.Debugf("[SOCKS5] - IPv4地址: %s", addr) + + case 0x03: // 域名 + domainLen := make([]byte, 1) + if _, err := io.ReadFull(client, domainLen); err != nil { + log.Debugf("[SOCKS5] 读取域名长度失败: %v", err) + return nil, "", fmt.Errorf("读取域名长度失败: %v", err) + } + domain := make([]byte, domainLen[0]) + if _, err := io.ReadFull(client, domain); err != nil { + log.Debugf("[SOCKS5] 读取域名失败: %v", err) + return nil, "", fmt.Errorf("读取域名失败: %v", err) + } + addr = string(domain) + log.Debugf("[SOCKS5] - 域名长度: %d", domainLen[0]) + log.Debugf("[SOCKS5] - 域名: %s", addr) + + case 0x04: // IPv6 + ipv6 := make([]byte, 16) + if _, err := io.ReadFull(client, ipv6); err != nil { + log.Debugf("[SOCKS5] 读取IPv6地址失败: %v", err) + return nil, "", fmt.Errorf("读取IPv6地址失败: %v", err) + } + addr = net.IP(ipv6).String() + log.Debugf("[SOCKS5] - IPv6地址: %s", addr) + } + + // 读取端口 + portBuf := make([]byte, 2) + if _, err := io.ReadFull(client, portBuf); err != nil { + log.Debugf("[SOCKS5] 读取端口失败: %v", err) + return nil, "", fmt.Errorf("读取端口失败: %v", err) + } + port := binary.BigEndian.Uint16(portBuf) + targetAddr := fmt.Sprintf("%s:%d", addr, port) + log.Debugf("[SOCKS5] 完整代理链路: %s -> [SOCKS5:%s] -> [HTTPS:%s] -> %s", + client.RemoteAddr(), + s.config.Socks5Addr, + s.upstreamDialer.config.Server, + targetAddr) + log.Debugf("[SOCKS5] - 端口: %d", port) + log.Debugf("[SOCKS5] - 完整目标地址: %s", targetAddr) + + // 连接目标服务器 + log.Debugf("[SOCKS5] 开始连接目标服务器...") + target, err := s.dialTarget(targetAddr) + if err != nil { + log.Debugf("[SOCKS5] 连接目标服务器失败: %v", err) + // 发送失败响应 + resp := []byte{0x05, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + log.Debugf("[SOCKS5] 发送失败响应: %x", resp) + client.Write(resp) + return nil, "", fmt.Errorf("连接目标服务器失败: %v", err) + } + + // 发送成功响应 + resp := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + log.Debugf("[SOCKS5] 发送成功响应: %x", resp) + client.Write(resp) + + log.Debugf("[SOCKS5] 请求处理完成") + log.Debugf("[SOCKS5] =====================================================") + + return target, targetAddr, nil +} + +// 辅助函数:将SOCKS5认证方法转换为字符串 +func socks5AuthMethodToString(method byte) string { + switch method { + case 0x00: + return "NO AUTHENTICATION REQUIRED" + case 0x01: + return "GSSAPI" + case 0x02: + return "USERNAME/PASSWORD" + case 0xff: + return "NO ACCEPTABLE METHODS" + default: + return fmt.Sprintf("UNKNOWN METHOD(0x%02x)", method) + } +} + +// 辅助函数:将SOCKS5命令转换为字符串 +func socks5CmdToString(cmd byte) string { + switch cmd { + case 0x01: + return "CONNECT" + case 0x02: + return "BIND" + case 0x03: + return "UDP ASSOCIATE" + default: + return fmt.Sprintf("UNKNOWN COMMAND(0x%02x)", cmd) + } +} + +// 辅助函数:将SOCKS5地址类型转换为字符串 +func socks5AddrTypeToString(addrType byte) string { + switch addrType { + case 0x01: + return "IPv4" + case 0x03: + return "DOMAIN" + case 0x04: + return "IPv6" + default: + return fmt.Sprintf("UNKNOWN ADDRESS TYPE(0x%02x)", addrType) + } +} + +// dialTarget 连接目标服务器 +func (s *Server) dialTarget(addr string) (net.Conn, error) { + log := logger.Get() + log.Debugf("[PROXY] =====================================================") + log.Debugf("[PROXY] 开始处理连接请求: %s", addr) + + // 提取域名和端口 + host := addr + port := "" + if idx := strings.LastIndex(addr, ":"); idx != -1 { + host = addr[:idx] + port = addr[idx+1:] + } + + // 检查是否是IP地址 + if ip := net.ParseIP(host); ip != nil { + log.Debugf("[PROXY] 目标是IP地址: %s", host) + log.Debugf("[PROXY] - 端口: %s", port) + } else { + log.Debugf("[PROXY] 目标是域名: %s", host) + log.Debugf("[PROXY] - 端口: %s", port) + } + + // 检查是否是直连域名 + useDirect := s.directList.Match(host) + log.Debugf("[PROXY] 连接方式判断:") + log.Debugf("[PROXY] - 是否直连域名: %v", useDirect) + + if useDirect { + // 使用直接连接 + log.Debugf("[PROXY] 使用直接连接") + conn, err := net.Dial("tcp", addr) + if err != nil { + log.Errorf("[PROXY] 直接连接失败: %v", err) + log.Debugf("[PROXY] =====================================================") + return nil, err + } + log.Debugf("[PROXY] 直接连接成功") + log.Debugf("[PROXY] 连接详情:") + log.Debugf("[PROXY] - 本地地址: %s", conn.LocalAddr()) + log.Debugf("[PROXY] - 远程地址: %s", conn.RemoteAddr()) + log.Debugf("[PROXY] =====================================================") + return conn, nil + } + + // 使用代理连接 + log.Debugf("[PROXY] 使用代理连接:") + log.Debugf("[PROXY] - 上游代理: %s", s.upstreamDialer.config.Server) + log.Debugf("[PROXY] - 代理类型: %s", s.upstreamDialer.config.Type) + + conn, err := s.upstreamDialer.Dial("tcp", addr) + if err != nil { + log.Errorf("[PROXY] 代理连接失败: %v", err) + log.Debugf("[PROXY] =====================================================") + return nil, err + } + + log.Debugf("[PROXY] 代理连接成功") + log.Debugf("[PROXY] 连接详情:") + log.Debugf("[PROXY] - 本地地址: %s", conn.LocalAddr()) + log.Debugf("[PROXY] - 远程地址: %s", conn.RemoteAddr()) + log.Debugf("[PROXY] =====================================================") + + return conn, nil +} diff --git a/pkg/proxy/upstream.go b/pkg/proxy/upstream.go new file mode 100644 index 0000000..7779acb --- /dev/null +++ b/pkg/proxy/upstream.go @@ -0,0 +1,200 @@ +// Package proxy 实现代理服务器功能 +package proxy + +import ( + "bufio" + "crypto/tls" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "strings" + "time" + + "s5/pkg/config" + "s5/pkg/logger" +) + +// UpstreamDialer 上游代理拨号器 +// 负责与上游HTTPS代理建立连接 +type UpstreamDialer struct { + config config.UpstreamConfig // 上游代理配置 +} + +// NewUpstreamDialer 创建新的上游代理拨号器 +func NewUpstreamDialer(config config.UpstreamConfig) *UpstreamDialer { + return &UpstreamDialer{config: config} +} + +// Dial 通过上游代理连接目标地址 +func (d *UpstreamDialer) Dial(network, addr string) (net.Conn, error) { + log := logger.Get() + log.Debugf("[UPSTREAM] 开始新的代理连接: %s -> %s", d.config.Server, addr) + + switch d.config.Type { + case "https": + return d.dialHTTPS(addr) + default: + return nil, fmt.Errorf("不支持的上游代理类型: %s", d.config.Type) + } +} + +// dialHTTPS 通过 HTTPS 代理连接 +func (d *UpstreamDialer) dialHTTPS(addr string) (net.Conn, error) { + log := logger.Get() + log.Debugf("[UPSTREAM] =====================================================") + log.Debugf("[UPSTREAM] 开始建立HTTPS代理连接") + log.Debugf("[UPSTREAM] 连接信息:") + log.Debugf("[UPSTREAM] - 目标地址: %s", addr) + log.Debugf("[UPSTREAM] - 代理服务器: %s", d.config.Server) + log.Debugf("[UPSTREAM] - 代理类型: %s", d.config.Type) + log.Debugf("[UPSTREAM] - 认证用户名: %s", d.config.Username) + log.Debugf("[UPSTREAM] - 认证密码: %s", d.config.Password) + + // 解析服务器地址 + host := d.config.Server + if !strings.Contains(host, ":") { + host = host + ":443" + } + log.Debugf("[UPSTREAM] 解析代理服务器地址: %s -> %s", d.config.Server, host) + + // 创建 TLS 配置 + tlsConfig := &tls.Config{ + InsecureSkipVerify: false, + ServerName: strings.Split(host, ":")[0], + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + NextProtos: []string{"http/1.1"}, + } + + log.Debugf("[UPSTREAM] TLS配置:") + log.Debugf("[UPSTREAM] - SNI: %s", tlsConfig.ServerName) + log.Debugf("[UPSTREAM] - 最低TLS版本: %s", tlsVersionToString(tlsConfig.MinVersion)) + log.Debugf("[UPSTREAM] - 最高TLS版本: %s", tlsVersionToString(tlsConfig.MaxVersion)) + log.Debugf("[UPSTREAM] - ALPN: %v", tlsConfig.NextProtos) + + // 建立TCP连接 + log.Debugf("[UPSTREAM] 开始建立TCP连接...") + tcpConn, err := net.Dial("tcp", host) + if err != nil { + log.Errorf("[UPSTREAM] TCP连接失败: %v", err) + return nil, fmt.Errorf("TCP连接失败: %v", err) + } + + log.Debugf("[UPSTREAM] TCP连接成功:") + log.Debugf("[UPSTREAM] - 本地地址: %s", tcpConn.LocalAddr()) + log.Debugf("[UPSTREAM] - 远程地址: %s", tcpConn.RemoteAddr()) + + // 设置 TCP 参数 + if tc, ok := tcpConn.(*net.TCPConn); ok { + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(30 * time.Second) + tc.SetNoDelay(true) + log.Debugf("[UPSTREAM] TCP参数设置完成") + } + + // TLS握手 + log.Debugf("[UPSTREAM] 开始TLS握手...") + tlsConn := tls.Client(tcpConn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + tcpConn.Close() + log.Errorf("[UPSTREAM] TLS握手失败: %v", err) + return nil, fmt.Errorf("TLS握手失败: %v", err) + } + + // 输出TLS连接信息 + state := tlsConn.ConnectionState() + log.Debugf("[UPSTREAM] TLS握手成功:") + log.Debugf("[UPSTREAM] - TLS版本: %s", tlsVersionToString(state.Version)) + log.Debugf("[UPSTREAM] - 加密套件: %s", tls.CipherSuiteName(state.CipherSuite)) + log.Debugf("[UPSTREAM] - 协商协议: %s", state.NegotiatedProtocol) + + // 构建详细的CONNECT请求 + auth := base64.StdEncoding.EncodeToString([]byte(d.config.Username + ":" + d.config.Password)) + connectReq := fmt.Sprintf( + "CONNECT %s HTTP/1.1\r\n"+ + "Host: %s\r\n"+ + "Proxy-Authorization: Basic %s\r\n"+ + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\r\n"+ + "Proxy-Connection: Keep-Alive\r\n"+ + "Connection: Keep-Alive\r\n"+ + "X-Forwarded-For: %s\r\n"+ + "X-Proxy-ID: Go-Proxy\r\n\r\n", + addr, addr, auth, "unknown", + ) + + log.Debugf("[UPSTREAM] 发送CONNECT请求:") + for _, line := range strings.Split(connectReq, "\r\n") { + if line != "" { + log.Debugf("[UPSTREAM] %s", line) + if strings.HasPrefix(line, "Proxy-Authorization: Basic ") { + if decoded, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(line, "Proxy-Authorization: Basic ")); err == nil { + log.Debugf("[UPSTREAM] Proxy-Authorization (decoded): %s", string(decoded)) + } + } + } + } + + if _, err = tlsConn.Write([]byte(connectReq)); err != nil { + tlsConn.Close() + log.Errorf("[UPSTREAM] 发送CONNECT请求失败: %v", err) + return nil, fmt.Errorf("发送CONNECT请求失败: %v", err) + } + + // 读取并解析响应 + log.Debugf("[UPSTREAM] 等待代理响应...") + resp, err := http.ReadResponse(bufio.NewReader(tlsConn), &http.Request{Method: "CONNECT"}) + if err != nil { + tlsConn.Close() + log.Errorf("[UPSTREAM] 读取代理响应失败: %v", err) + return nil, fmt.Errorf("读取代理响应失败: %v", err) + } + defer resp.Body.Close() + + log.Debugf("[UPSTREAM] 收到代理响应:") + log.Debugf("[UPSTREAM] - 协议版本: %s", resp.Proto) + log.Debugf("[UPSTREAM] - 状态码: %d", resp.StatusCode) + log.Debugf("[UPSTREAM] - 状态描述: %s", resp.Status) + log.Debugf("[UPSTREAM] - Content-Length: %d", resp.ContentLength) + log.Debugf("[UPSTREAM] - Transfer-Encoding: %v", resp.TransferEncoding) + log.Debugf("[UPSTREAM] - Close: %v", resp.Close) + + log.Debugf("[UPSTREAM] 响应头:") + for k, vs := range resp.Header { + for _, v := range vs { + log.Debugf("[UPSTREAM] %s: %s", k, v) + } + } + + // 读取响应体 + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + log.Debugf("[UPSTREAM] 响应体: %s", string(body)) + tlsConn.Close() + return nil, fmt.Errorf("代理连接失败: %s", resp.Status) + } + + log.Debugf("[UPSTREAM] 代理隧道建立成功") + log.Debugf("[UPSTREAM] - 本地地址: %s", tlsConn.LocalAddr()) + log.Debugf("[UPSTREAM] - 远程地址: %s", tlsConn.RemoteAddr()) + log.Debugf("[UPSTREAM] =====================================================") + + return tlsConn, nil +} + +// tlsVersionToString 将TLS版本转换为字符串 +func tlsVersionToString(version uint16) string { + switch version { + case tls.VersionTLS10: + return "TLS 1.0" + case tls.VersionTLS11: + return "TLS 1.1" + case tls.VersionTLS12: + return "TLS 1.2" + case tls.VersionTLS13: + return "TLS 1.3" + default: + return fmt.Sprintf("Unknown(0x%04x)", version) + } +} \ No newline at end of file