From 312e30c87bc0ebd621c301d474bdf068b249c25a Mon Sep 17 00:00:00 2001
From: iuu <2167162990@qq.com>
Date: Fri, 10 Jan 2025 14:07:07 +0800
Subject: [PATCH] init
---
.gitignore | 1 +
.idea/.gitignore | 8 +
.idea/modules.xml | 8 +
.idea/s5.iml | 9 +
.idea/vcs.xml | 6 +
config.yaml | 30 ++
direct.txt | 27 ++
go.mod | 13 +
go.sum | 18 ++
main.go | 60 ++++
pkg/config/config.go | 79 +++++
pkg/logger/logger.go | 124 ++++++++
pkg/proxy/directlist.go | 94 ++++++
pkg/proxy/server.go | 679 ++++++++++++++++++++++++++++++++++++++++
pkg/proxy/upstream.go | 200 ++++++++++++
15 files changed, 1356 insertions(+)
create mode 100644 .gitignore
create mode 100644 .idea/.gitignore
create mode 100644 .idea/modules.xml
create mode 100644 .idea/s5.iml
create mode 100644 .idea/vcs.xml
create mode 100644 config.yaml
create mode 100644 direct.txt
create mode 100644 go.mod
create mode 100644 go.sum
create mode 100644 main.go
create mode 100644 pkg/config/config.go
create mode 100644 pkg/logger/logger.go
create mode 100644 pkg/proxy/directlist.go
create mode 100644 pkg/proxy/server.go
create mode 100644 pkg/proxy/upstream.go
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