init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
proxy.log
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
8
.idea/modules.xml
generated
Normal 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
9
.idea/s5.iml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
30
config.yaml
Normal 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
27
direct.txt
Normal 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
13
go.mod
Normal 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
18
go.sum
Normal 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
60
main.go
Normal 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
79
pkg/config/config.go
Normal 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
124
pkg/logger/logger.go
Normal 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
94
pkg/proxy/directlist.go
Normal 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
679
pkg/proxy/server.go
Normal 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
200
pkg/proxy/upstream.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user