This commit is contained in:
2025-01-10 14:07:07 +08:00
commit 312e30c87b
15 changed files with 1356 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
proxy.log

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/s5.iml" filepath="$PROJECT_DIR$/.idea/s5.iml" />
</modules>
</component>
</project>

9
.idea/s5.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

30
config.yaml Normal file
View File

@@ -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==" # 上游代理认证密码

27
direct.txt Normal file
View File

@@ -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

13
go.mod Normal file
View File

@@ -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
)

18
go.sum Normal file
View File

@@ -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=

60
main.go Normal file
View File

@@ -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)
}
}

79
pkg/config/config.go Normal file
View File

@@ -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
}

124
pkg/logger/logger.go Normal file
View File

@@ -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
}

94
pkg/proxy/directlist.go Normal file
View File

@@ -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
}

679
pkg/proxy/server.go Normal file
View File

@@ -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
}

200
pkg/proxy/upstream.go Normal file
View File

@@ -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)
}
}