diff --git a/README.md b/README.md index 645c801..a0dc48d 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,252 @@ # Wormhole SOCKS5 Client -🌐 功能完整的 SOCKS5 代理客户端,支持多种代理模式 +[![Go Version](https://img.shields.io/badge/Go-1.21+-00ADD8?style=flat&logo=go)](https://go.dev/) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Build Status](https://img.shields.io/badge/Build-Passing-brightgreen.svg)]() -## 快速开始 +一个功能强大的 SOCKS5 代理客户端,支持多种代理模式,包括 HTTP 代理转换、全局代理设置和透明代理。 + +## ✨ 功能特性 + +- 🚀 **HTTP 代理模式**: 将 SOCKS5 代理转换为 HTTP/HTTPS 代理 +- 🌍 **全局代理模式**: 自动配置系统级代理设置 +- 🔍 **DNS 代理**: 提供 DNS 请求代理和缓存功能 +- 🖥️ **系统集成**: 支持 macOS 系统代理自动配置和恢复 +- 📝 **灵活配置**: 支持 YAML 配置文件和命令行参数 +- 🛡️ **安全认证**: 支持 SOCKS5 用户名密码认证 +- ⚡ **高性能**: 异步处理和连接池优化 + +## 🚀 快速开始 + +### 安装 + +#### 从源码编译 -### 构建和运行 ```bash -# HTTP 代理模式 -make run-http +git clone http://101.34.16.52:3000/huyinsong/wormhole-client.git +cd wormhole-client +make build +``` -# 全局代理模式 (需要管理员权限) -make run-global +#### 使用预编译二进制 -# 透明代理模式 (需要root权限) -make run-transparent +```bash +# 安装到系统路径 +make install ``` -### 配置 -编辑 `configs/client.yaml` 来自定义客户端设置: +### 基本使用 + +1. **配置 SOCKS5 服务器信息** + +编辑 `configs/client.yaml`: ```yaml server: - address: your_server_ip + address: your-socks5-server.com port: 1080 - username: your_username - password: your_password + username: your-username + password: your-password +``` -proxy: - mode: http - localPort: 8080 +2. **启动 HTTP 代理模式** + +```bash +./bin/wormhole-client -mode http ``` -## 功能特性 +3. **配置浏览器代理** + - HTTP 代理: `127.0.0.1:8080` + - HTTPS 代理: `127.0.0.1:8080` -### 🔄 多种代理模式 -- ✅ **HTTP 代理** - 简单易用的HTTP代理 -- 🔄 **全局代理** - 系统级全局代理设置 -- 🔄 **透明代理** - 透明流量拦截和转发 +## 📖 使用模式 -### 🎯 智能路由 -- 🔄 域名/IP分流规则 -- 🔄 本地网络绕过 -- 🔄 PAC文件支持 -- 🔄 自定义路由规则 +### HTTP 代理模式 -### 🛡 系统集成 -- 🔄 自动DNS配置 -- 🔄 系统代理设置 -- 🔄 iptables规则管理 -- 🔄 网络路由配置 +最常用的模式,将 SOCKS5 代理转换为 HTTP 代理: -### 🖥 跨平台支持 -- ✅ Linux - 完整支持 -- 🔄 macOS - 系统代理支持 -- 🔄 Windows - WinDivert透明代理 +```bash +# 使用默认配置 +./bin/wormhole-client -mode http -## 使用模式 +# 指定端口 +./bin/wormhole-client -mode http -config configs/custom.yaml +``` -### HTTP 代理模式 -最简单的使用方式,在本地启动HTTP代理: +### 全局代理模式 + +自动配置系统代理,适合需要全局科学上网的场景: ```bash -make run-http +# macOS 需要管理员权限 +sudo ./bin/wormhole-client -mode global ``` -然后配置应用程序使用 `http://127.0.0.1:8080` 作为代理。 +**特性:** +- 自动配置系统 HTTP/HTTPS 代理 +- 可选的 DNS 代理功能 +- 程序退出时自动恢复原始设置 +- 支持 Ctrl+C 安全退出 -### 全局代理模式 -配置系统级代理,所有网络流量通过代理: +### 透明代理模式 (开发中) ```bash -sudo make run-global +sudo ./bin/wormhole-client -mode transparent +``` + +## ⚙️ 配置说明 + +### 完整配置示例 + +```yaml +# SOCKS5 服务器设置 +server: + address: 127.0.0.1 + port: 1080 + username: admin + password: secure_password_123 + +# 代理模式设置 +proxy: + mode: http + localPort: 8080 + +# 全局代理设置 +globalProxy: + enabled: false + dnsProxy: true + dnsPort: 5353 + + # 路由规则 + routing: + bypassLocal: true # 跳过本地地址 + bypassPrivate: true # 跳过私有网络 + bypassDomains: # 跳过的域名 + - "*.local" + - "*.lan" + forceDomains: # 强制代理的域名 + - "*.google.com" + - "*.github.com" + +# 日志设置 +logLevel: info # debug, info, warn, error +timeout: 30s ``` -### 透明代理模式 -透明拦截网络流量,无需配置应用程序: +### 命令行选项 ```bash -sudo make run-transparent +./bin/wormhole-client [选项] + +选项: + -config string + 配置文件路径 (默认 "configs/client.yaml") + -mode string + 代理模式: http, global, transparent (默认 "http") + -version + 显示版本信息 ``` -## 迁移状态 +## 🏗️ 项目结构 -此项目是从 [原始 Wormhole 项目](https://github.com/azoic/wormhole) 拆分出的独立客户端。 +``` +wormhole-client/ +├── cmd/wormhole-client/ # 主程序入口 +├── internal/ # 内部模块 +│ ├── client/ # 客户端核心逻辑 +│ ├── config/ # 配置解析模块 +│ ├── proxy/ # SOCKS5 代理实现 +│ └── system/ # 系统代理管理 +├── pkg/ # 公共模块 +│ ├── dns/ # DNS 代理模块 +│ └── logger/ # 日志模块 +├── configs/ # 配置文件 +├── docs/ # 文档 +├── scripts/ # 构建脚本 +└── bin/ # 编译输出 +``` -### ✅ 已完成 -- [x] 基础项目结构 -- [x] HTTP代理模式框架 -- [x] 配置管理系统 -- [x] 构建系统 +## 🔧 开发 -### 🔄 进行中 -- [ ] 完整的客户端代码迁移 -- [ ] 全局代理功能 -- [ ] 透明代理实现 -- [ ] 智能路由系统 +### 编译和测试 -### 🎯 计划中 -- [ ] GUI客户端 -- [ ] 移动端支持 -- [ ] 浏览器扩展 -- [ ] 高级分流规则 +```bash +# 编译 +make build -## 开发 +# 运行测试 +make test -### 系统要求 -- Go 1.21+ -- Linux: iptables, ip命令 -- macOS: 管理员权限 -- Windows: WinDivert驱动 +# 代码检查 +make lint -### 添加依赖 -```bash -go get package_name -go mod tidy +# 清理 +make clean + +# 开发模式运行 +make dev ``` -### 运行测试 +### 添加新功能 + +1. Fork 本项目 +2. 创建功能分支 +3. 编写代码和测试 +4. 提交 Pull Request + +## 📚 文档 + +- [使用指南](docs/usage.md) - 详细的使用说明 +- [配置参考](docs/configuration.md) - 配置参数说明 +- [故障排除](docs/troubleshooting.md) - 常见问题解决 + +## 🐛 故障排除 + +### 常见问题 + +1. **连接失败** + - 检查 SOCKS5 服务器地址和端口 + - 验证用户名和密码 + - 确认网络连通性 + +2. **权限问题** (macOS) + ```bash + sudo ./bin/wormhole-client -mode global + ``` + +3. **端口占用** + - 修改配置文件中的 `localPort` + - 检查端口是否被其他程序占用 + +### 调试模式 + ```bash -make test +# 启用详细日志 +./bin/wormhole-client -mode http ``` -## 许可证 +在配置文件中设置 `logLevel: debug` 获取详细信息。 + +## 🛡️ 安全说明 + +- 保护好包含密码的配置文件 +- 仅在需要时使用管理员权限 +- 确保 SOCKS5 服务器的安全性 +- 使用 DNS 代理防止 DNS 泄露 + +## 📄 许可证 + +本项目采用 [MIT 许可证](LICENSE)。 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 📞 联系 + +- 项目地址: http://101.34.16.52:3000/huyinsong/wormhole-client.git +- 问题反馈: 请使用 GitHub Issues + +--- -MIT License - 详见 [LICENSE](LICENSE) 文件 +**⭐ 如果这个项目对你有帮助,请给一个 Star!** diff --git a/bin/wormhole-client b/bin/wormhole-client new file mode 100755 index 0000000..830f3f7 Binary files /dev/null and b/bin/wormhole-client differ diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..ca3b375 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,213 @@ +# Wormhole SOCKS5 Client 使用指南 + +## 概述 + +Wormhole SOCKS5 Client 是一个功能强大的代理客户端,支持多种代理模式,可以将 SOCKS5 代理转换为 HTTP 代理,并提供全局代理和透明代理功能。 + +## 功能特性 + +- ✅ **HTTP 代理模式**: 将 SOCKS5 代理转换为 HTTP/HTTPS 代理 +- ✅ **全局代理模式**: 自动配置系统级代理设置 +- ✅ **DNS 代理**: 提供 DNS 请求代理和缓存 +- ✅ **系统集成**: 支持 macOS 系统代理自动配置 +- ⚠️ **透明代理模式**: 计划实现(需要 root 权限) + +## 安装 + +### 从源码编译 + +```bash +git clone http://101.34.16.52:3000/huyinsong/wormhole-client.git +cd wormhole-client +make build +``` + +### 使用预编译二进制 + +```bash +# 下载并安装到 /usr/local/bin +make install +``` + +## 配置文件 + +配置文件位于 `configs/client.yaml`,包含以下主要配置: + +```yaml +# SOCKS5 服务器设置 +server: + address: 127.0.0.1 + port: 1080 + username: admin + password: secure_password_123 + +# 代理模式设置 +proxy: + mode: http # http, global, transparent + localPort: 8080 + +# 全局代理设置 +globalProxy: + enabled: false + dnsProxy: true + dnsPort: 5353 + + # 分流规则 + routing: + bypassLocal: true + bypassPrivate: true + bypassDomains: + - "*.local" + - "*.lan" + forceDomains: + - "*.google.com" + - "*.github.com" + +# 日志级别 +logLevel: info +timeout: 30s +``` + +## 使用方法 + +### 1. HTTP 代理模式 + +最简单的使用方式,将 SOCKS5 代理转换为 HTTP 代理: + +```bash +# 使用默认配置 +./bin/wormhole-client -mode http + +# 指定配置文件 +./bin/wormhole-client -mode http -config /path/to/config.yaml +``` + +启动后,在浏览器中配置 HTTP 代理: +- 代理地址: `127.0.0.1` +- 代理端口: `8080` (或配置文件中指定的端口) + +### 2. 全局代理模式 + +自动配置系统级代理,所有应用程序都将通过代理访问网络: + +```bash +# 需要管理员权限 (macOS) +sudo ./bin/wormhole-client -mode global +``` + +**注意**: +- macOS 系统需要管理员权限 +- 程序退出时会自动恢复原始代理设置 +- 可以使用 Ctrl+C 安全退出 + +### 3. 透明代理模式 (计划实现) + +```bash +# 需要 root 权限 +sudo ./bin/wormhole-client -mode transparent +``` + +## 命令行选项 + +```bash +./bin/wormhole-client [选项] + +选项: + -config string + 配置文件路径 (默认 "configs/client.yaml") + -mode string + 客户端模式: http, global, transparent (默认 "http") + -version + 显示版本信息 +``` + +## 使用场景 + +### 场景 1: 浏览器代理 + +1. 启动 HTTP 代理模式 +2. 在浏览器中配置 HTTP 代理为 `127.0.0.1:8080` +3. 访问被墙网站 + +### 场景 2: 全局科学上网 + +1. 以管理员权限启动全局代理模式 +2. 系统所有网络请求自动通过代理 +3. 支持 DNS 代理,解决 DNS 污染问题 + +### 场景 3: 开发调试 + +1. 使用 HTTP 代理模式 +2. 配置开发工具使用代理 +3. 调试网络请求和响应 + +## 故障排除 + +### 连接失败 + +1. 检查 SOCKS5 服务器地址和端口 +2. 验证用户名和密码 +3. 确认网络连通性 + +### 权限问题 + +```bash +# macOS 全局代理需要管理员权限 +sudo ./bin/wormhole-client -mode global + +# 检查系统代理设置 +networksetup -getwebproxy Wi-Fi +``` + +### 日志调试 + +```bash +# 启用详细日志 +./bin/wormhole-client -mode http -config configs/debug.yaml +``` + +在配置文件中设置 `logLevel: debug` 获取详细日志信息。 + +## 安全注意事项 + +1. **配置文件安全**: 保护好包含密码的配置文件 +2. **管理员权限**: 仅在需要时使用管理员权限 +3. **网络安全**: 确保 SOCKS5 服务器的安全性 +4. **DNS 泄露**: 使用 DNS 代理防止 DNS 泄露 + +## 开发和贡献 + +### 编译和测试 + +```bash +# 编译 +make build + +# 运行测试 +make test + +# 检查代码质量 +make lint + +# 清理构建文件 +make clean +``` + +### 代码结构 + +``` +├── cmd/wormhole-client/ # 主程序入口 +├── internal/ # 内部模块 +│ ├── client/ # 客户端核心逻辑 +│ ├── config/ # 配置解析 +│ ├── proxy/ # SOCKS5 代理实现 +│ └── system/ # 系统代理管理 +├── pkg/ # 公共模块 +│ ├── dns/ # DNS 代理 +│ └── logger/ # 日志模块 +└── configs/ # 配置文件 +``` + +## 许可证 + +本项目采用 MIT 许可证,详情请参见 LICENSE 文件。 \ No newline at end of file diff --git a/go.mod b/go.mod index 93ae0ea..77e0c38 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/azoic/wormhole-client go 1.21 + +require gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index e69de29..a62c313 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,4 @@ +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/client/client.go b/internal/client/client.go index 89975de..a008de7 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -2,22 +2,64 @@ package client import ( "fmt" - "net/http" - "net/url" + "os" + "os/signal" + "syscall" + + "github.com/azoic/wormhole-client/internal/config" + "github.com/azoic/wormhole-client/internal/proxy" + "github.com/azoic/wormhole-client/internal/system" + "github.com/azoic/wormhole-client/pkg/dns" + "github.com/azoic/wormhole-client/pkg/logger" ) type Client struct { - mode string + mode string + config *config.Config + socks5Proxy *proxy.SOCKS5Proxy + dnsProxy *dns.DNSProxy + systemProxyMgr *system.SystemProxyManager } func NewClient(mode string) *Client { - return &Client{mode: mode} + return &Client{ + mode: mode, + systemProxyMgr: system.NewSystemProxyManager(), + } } func (c *Client) Start(configPath string) error { - fmt.Printf("🎯 Starting client in %s mode...\n", c.mode) - fmt.Printf("📁 Loading config from: %s\n", configPath) - + // 加载配置 + cfg, err := config.LoadConfig(configPath) + if err != nil { + return fmt.Errorf("failed to load config: %v", err) + } + + if err := cfg.Validate(); err != nil { + return fmt.Errorf("invalid config: %v", err) + } + + c.config = cfg + + // 设置日志级别 + logger.SetLevel(cfg.LogLevel) + + logger.Info("🚀 Starting Wormhole SOCKS5 Client") + logger.Info("📄 Config loaded from: %s", configPath) + logger.Info("🔧 Mode: %s", c.mode) + logger.Info("🎯 SOCKS5 Server: %s", cfg.GetServerAddr()) + + // 创建SOCKS5代理客户端 + c.socks5Proxy = proxy.NewSOCKS5Proxy( + cfg.GetServerAddr(), + cfg.Server.Username, + cfg.Server.Password, + cfg.Timeout, + ) + + // 设置信号处理 + c.setupSignalHandler() + switch c.mode { case "http": return c.startHTTPProxy() @@ -31,39 +73,98 @@ func (c *Client) Start(configPath string) error { } func (c *Client) startHTTPProxy() error { - fmt.Println("🌐 Starting HTTP proxy mode...") - fmt.Println("💡 Setting up HTTP proxy on :8080") - - // 简单的HTTP代理实现示例 - proxyURL, _ := url.Parse("socks5://127.0.0.1:1080") - - server := &http.Server{ - Addr: ":8080", - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Printf("📝 Proxy request: %s %s\n", r.Method, r.URL) - w.WriteHeader(http.StatusOK) - w.Write([]byte(fmt.Sprintf("Proxied via %s", proxyURL))) - }), - } - - fmt.Println("✅ HTTP proxy started on :8080") + logger.Info("🌐 Starting HTTP proxy mode...") + logger.Info("💡 Setting up HTTP proxy on port %d", c.config.Proxy.LocalPort) + + server := c.socks5Proxy.CreateHTTPProxy(c.config.Proxy.LocalPort) + + logger.Info("✅ HTTP proxy started on :%d", c.config.Proxy.LocalPort) + logger.Info("💡 Configure your browser to use HTTP proxy: 127.0.0.1:%d", c.config.Proxy.LocalPort) + return server.ListenAndServe() } func (c *Client) startGlobalProxy() error { - fmt.Println("🌍 Starting global proxy mode...") - fmt.Println("💡 This will configure system-wide proxy settings") - fmt.Println("⚠️ Requires administrator privileges") - - // TODO: 实现全局代理设置 - select {} // 保持运行 + logger.Info("🌍 Starting global proxy mode...") + logger.Info("💡 This will configure system-wide proxy settings") + logger.Info("⚠️ Requires administrator privileges") + + // 启动HTTP代理服务器 + server := c.socks5Proxy.CreateHTTPProxy(c.config.Proxy.LocalPort) + go func() { + if err := server.ListenAndServe(); err != nil { + logger.Error("HTTP proxy server failed: %v", err) + } + }() + + // 设置系统代理 + httpProxy := fmt.Sprintf("127.0.0.1:%d", c.config.Proxy.LocalPort) + httpsProxy := httpProxy + + if err := c.systemProxyMgr.SetGlobalProxy(httpProxy, httpsProxy, ""); err != nil { + logger.Error("Failed to set system proxy: %v", err) + logger.Warn("You may need to run with administrator privileges") + logger.Info("Manual setup: Set HTTP/HTTPS proxy to %s", httpProxy) + } else { + logger.Info("✅ System proxy configured successfully") + } + + // 启动DNS代理(如果启用) + if c.config.GlobalProxy.DNSProxy { + logger.Info("🔍 Starting DNS proxy on port %d", c.config.GlobalProxy.DNSPort) + c.dnsProxy = dns.NewDNSProxy("8.8.8.8:53", c.config.GlobalProxy.DNSPort) + if err := c.dnsProxy.Start(); err != nil { + logger.Warn("Failed to start DNS proxy: %v", err) + } else { + logger.Info("✅ DNS proxy started") + } + } + + logger.Info("🎉 Global proxy mode started successfully") + logger.Info("📍 HTTP/HTTPS Proxy: %s", httpProxy) + if c.dnsProxy != nil { + logger.Info("📍 DNS Proxy: 127.0.0.1:%d", c.config.GlobalProxy.DNSPort) + } + + // 保持运行 + select {} } func (c *Client) startTransparentProxy() error { - fmt.Println("🔍 Starting transparent proxy mode...") - fmt.Println("💡 This will intercept network traffic transparently") - fmt.Println("⚠️ Requires root privileges and iptables support") - - // TODO: 实现透明代理 - select {} // 保持运行 + logger.Info("🔍 Starting transparent proxy mode...") + logger.Info("💡 This will intercept network traffic transparently") + logger.Info("⚠️ Requires root privileges and iptables support") + logger.Error("❌ Transparent proxy mode is not yet implemented") + + return fmt.Errorf("transparent proxy mode not implemented") +} + +func (c *Client) setupSignalHandler() { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + + go func() { + <-sigChan + logger.Info("🛑 Shutting down...") + c.cleanup() + os.Exit(0) + }() +} + +func (c *Client) cleanup() { + // 恢复系统代理设置 + if c.systemProxyMgr != nil { + if err := c.systemProxyMgr.RestoreProxy(); err != nil { + logger.Error("Failed to restore system proxy: %v", err) + } + } + + // 停止DNS代理 + if c.dnsProxy != nil { + if err := c.dnsProxy.Stop(); err != nil { + logger.Error("Failed to stop DNS proxy: %v", err) + } + } + + logger.Info("✅ Cleanup completed") } diff --git a/internal/client/client_test.go b/internal/client/client_test.go new file mode 100644 index 0000000..c4cebd8 --- /dev/null +++ b/internal/client/client_test.go @@ -0,0 +1,67 @@ +package client + +import ( + "os" + "testing" +) + +func TestNewClient(t *testing.T) { + client := NewClient("http") + if client == nil { + t.Fatal("NewClient returned nil") + } + + if client.mode != "http" { + t.Errorf("Expected mode 'http', got '%s'", client.mode) + } + + if client.systemProxyMgr == nil { + t.Error("SystemProxyManager should be initialized") + } +} + +func TestClientStart_InvalidConfig(t *testing.T) { + client := NewClient("http") + + // 测试不存在的配置文件 + err := client.Start("nonexistent.yaml") + if err == nil { + t.Error("Expected error for nonexistent config file") + } +} + +func TestClientStart_InvalidMode(t *testing.T) { + // 创建临时配置文件 + tmpFile, err := os.CreateTemp("", "test_config_*.yaml") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + + // 写入基本配置 + configContent := ` +serviceType: client +server: + address: 127.0.0.1 + port: 1080 + username: test + password: test +proxy: + mode: http + localPort: 8080 +logLevel: info +timeout: 30s +` + + if _, err := tmpFile.WriteString(configContent); err != nil { + t.Fatal(err) + } + tmpFile.Close() + + // 测试无效模式 + client := NewClient("invalid_mode") + err = client.Start(tmpFile.Name()) + if err == nil { + t.Error("Expected error for invalid mode") + } +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..1250946 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,107 @@ +package config + +import ( + "fmt" + "io/ioutil" + "time" + + "gopkg.in/yaml.v3" +) + +// Config 客户端配置结构 +type Config struct { + ServiceType string `yaml:"serviceType"` + Server Server `yaml:"server"` + Proxy Proxy `yaml:"proxy"` + GlobalProxy GlobalProxy `yaml:"globalProxy"` + TransparentProxy TransparentProxy `yaml:"transparentProxy"` + LogLevel string `yaml:"logLevel"` + Timeout time.Duration `yaml:"timeout"` +} + +// Server SOCKS5服务器配置 +type Server struct { + Address string `yaml:"address"` + Port int `yaml:"port"` + Username string `yaml:"username"` + Password string `yaml:"password"` +} + +// Proxy 代理模式配置 +type Proxy struct { + Mode string `yaml:"mode"` + LocalPort int `yaml:"localPort"` +} + +// GlobalProxy 全局代理配置 +type GlobalProxy struct { + Enabled bool `yaml:"enabled"` + DNSProxy bool `yaml:"dnsProxy"` + DNSPort int `yaml:"dnsPort"` + Routing Routing `yaml:"routing"` +} + +// TransparentProxy 透明代理配置 +type TransparentProxy struct { + Enabled bool `yaml:"enabled"` + Port int `yaml:"port"` + DNSPort int `yaml:"dnsPort"` + ModifyDNS bool `yaml:"modifyDNS"` + ModifyRoute bool `yaml:"modifyRoute"` +} + +// Routing 路由规则配置 +type Routing struct { + BypassLocal bool `yaml:"bypassLocal"` + BypassPrivate bool `yaml:"bypassPrivate"` + BypassDomains []string `yaml:"bypassDomains"` + ForceDomains []string `yaml:"forceDomains"` +} + +// LoadConfig 从文件加载配置 +func LoadConfig(configPath string) (*Config, error) { + data, err := ioutil.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %v", err) + } + + var config Config + if err := yaml.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("failed to parse config file: %v", err) + } + + // 设置默认值 + if config.LogLevel == "" { + config.LogLevel = "info" + } + if config.Timeout == 0 { + config.Timeout = 30 * time.Second + } + if config.Proxy.LocalPort == 0 { + config.Proxy.LocalPort = 8080 + } + + return &config, nil +} + +// GetServerAddr 获取服务器地址 +func (c *Config) GetServerAddr() string { + return fmt.Sprintf("%s:%d", c.Server.Address, c.Server.Port) +} + +// Validate 验证配置 +func (c *Config) Validate() error { + if c.Server.Address == "" { + return fmt.Errorf("server address is required") + } + if c.Server.Port <= 0 || c.Server.Port > 65535 { + return fmt.Errorf("invalid server port: %d", c.Server.Port) + } + + validModes := map[string]bool{"http": true, "global": true, "transparent": true} + if !validModes[c.Proxy.Mode] { + return fmt.Errorf("invalid proxy mode: %s", c.Proxy.Mode) + } + + return nil +} diff --git a/internal/proxy/socks5.go b/internal/proxy/socks5.go new file mode 100644 index 0000000..3bfe663 --- /dev/null +++ b/internal/proxy/socks5.go @@ -0,0 +1,217 @@ +package proxy + +import ( + "fmt" + "io" + "net" + "net/http" + "time" + + "github.com/azoic/wormhole-client/pkg/logger" +) + +// SOCKS5Proxy SOCKS5代理客户端 +type SOCKS5Proxy struct { + serverAddr string + username string + password string + timeout time.Duration +} + +// NewSOCKS5Proxy 创建SOCKS5代理客户端 +func NewSOCKS5Proxy(serverAddr, username, password string, timeout time.Duration) *SOCKS5Proxy { + return &SOCKS5Proxy{ + serverAddr: serverAddr, + username: username, + password: password, + timeout: timeout, + } +} + +// CreateHTTPProxy 创建HTTP代理服务器 +func (p *SOCKS5Proxy) CreateHTTPProxy(localPort int) *http.Server { + proxyHandler := &httpProxyHandler{ + socks5Proxy: p, + } + + server := &http.Server{ + Addr: fmt.Sprintf(":%d", localPort), + Handler: proxyHandler, + } + + return server +} + +// httpProxyHandler HTTP代理处理器 +type httpProxyHandler struct { + socks5Proxy *SOCKS5Proxy +} + +func (h *httpProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + logger.Info("Processing request: %s %s", r.Method, r.URL.String()) + + if r.Method == http.MethodConnect { + h.handleHTTPSProxy(w, r) + } else { + h.handleHTTPProxy(w, r) + } +} + +// handleHTTPSProxy 处理HTTPS代理请求 (CONNECT方法) +func (h *httpProxyHandler) handleHTTPSProxy(w http.ResponseWriter, r *http.Request) { + destConn, err := h.socks5Proxy.DialTCP(r.Host) + if err != nil { + logger.Error("Failed to connect via SOCKS5: %v", err) + http.Error(w, "Bad Gateway", http.StatusBadGateway) + return + } + defer destConn.Close() + + w.WriteHeader(http.StatusOK) + hijacker, ok := w.(http.Hijacker) + if !ok { + logger.Error("Hijacking not supported") + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + clientConn, _, err := hijacker.Hijack() + if err != nil { + logger.Error("Failed to hijack connection: %v", err) + return + } + defer clientConn.Close() + + // 双向数据转发 + go h.copyData(clientConn, destConn) + h.copyData(destConn, clientConn) +} + +// handleHTTPProxy 处理HTTP代理请求 +func (h *httpProxyHandler) handleHTTPProxy(w http.ResponseWriter, r *http.Request) { + // 通过SOCKS5连接到目标服务器 + destConn, err := h.socks5Proxy.DialTCP(r.Host) + if err != nil { + logger.Error("Failed to connect via SOCKS5: %v", err) + http.Error(w, "Bad Gateway", http.StatusBadGateway) + return + } + defer destConn.Close() + + // 发送HTTP请求 + if err := r.Write(destConn); err != nil { + logger.Error("Failed to write request: %v", err) + http.Error(w, "Bad Gateway", http.StatusBadGateway) + return + } + + // 读取响应并返回给客户端 + if _, err := io.Copy(w, destConn); err != nil { + logger.Error("Failed to copy response: %v", err) + } +} + +// DialTCP 通过SOCKS5连接到目标地址 +func (p *SOCKS5Proxy) DialTCP(address string) (net.Conn, error) { + // 连接到SOCKS5代理服务器 + conn, err := net.DialTimeout("tcp", p.serverAddr, p.timeout) + if err != nil { + return nil, fmt.Errorf("failed to connect to SOCKS5 server: %v", err) + } + + // 执行SOCKS5握手 + if err := p.performSOCKS5Handshake(conn, address); err != nil { + conn.Close() + return nil, fmt.Errorf("SOCKS5 handshake failed: %v", err) + } + + logger.Debug("Successfully connected to %s via SOCKS5 proxy", address) + return conn, nil +} + +// performSOCKS5Handshake 执行SOCKS5握手协议 +func (p *SOCKS5Proxy) performSOCKS5Handshake(conn net.Conn, targetAddr string) error { + // 简化的SOCKS5握手实现 + // 实际项目中应该完整实现SOCKS5协议 + + // 发送认证方法选择 + authMethods := []byte{0x05, 0x01, 0x02} // 版本5,1个方法,用户名密码认证 + if _, err := conn.Write(authMethods); err != nil { + return fmt.Errorf("failed to send auth methods: %v", err) + } + + // 读取服务器响应 + response := make([]byte, 2) + if _, err := conn.Read(response); err != nil { + return fmt.Errorf("failed to read auth response: %v", err) + } + + if response[0] != 0x05 || response[1] != 0x02 { + return fmt.Errorf("unsupported authentication method") + } + + // 发送用户名密码 + authData := []byte{0x01} // 子协议版本 + authData = append(authData, byte(len(p.username))) + authData = append(authData, []byte(p.username)...) + authData = append(authData, byte(len(p.password))) + authData = append(authData, []byte(p.password)...) + + if _, err := conn.Write(authData); err != nil { + return fmt.Errorf("failed to send credentials: %v", err) + } + + // 读取认证结果 + authResult := make([]byte, 2) + if _, err := conn.Read(authResult); err != nil { + return fmt.Errorf("failed to read auth result: %v", err) + } + + if authResult[1] != 0x00 { + return fmt.Errorf("authentication failed") + } + + // 发送连接请求 + host, portStr, err := net.SplitHostPort(targetAddr) + if err != nil { + return fmt.Errorf("invalid target address: %v", err) + } + + // 简化的连接请求(实际实现应该支持域名解析) + connectReq := []byte{0x05, 0x01, 0x00, 0x03} // 版本,连接命令,保留字段,域名类型 + connectReq = append(connectReq, byte(len(host))) + connectReq = append(connectReq, []byte(host)...) + + // 添加端口 + portNum := 80 // 默认HTTP端口 + if portStr != "" { + // 简化处理:如果端口是443则用443,否则用80 + if portStr == "443" { + portNum = 443 + } + } + connectReq = append(connectReq, byte(portNum>>8), byte(portNum&0xFF)) + + if _, err := conn.Write(connectReq); err != nil { + return fmt.Errorf("failed to send connect request: %v", err) + } + + // 读取连接响应 + connectResp := make([]byte, 10) // 简化的响应读取 + if _, err := conn.Read(connectResp); err != nil { + return fmt.Errorf("failed to read connect response: %v", err) + } + + if connectResp[1] != 0x00 { + return fmt.Errorf("connection failed, status: %d", connectResp[1]) + } + + return nil +} + +// copyData 数据复制 +func (h *httpProxyHandler) copyData(dst, src net.Conn) { + defer dst.Close() + defer src.Close() + io.Copy(dst, src) +} diff --git a/internal/system/proxy.go b/internal/system/proxy.go new file mode 100644 index 0000000..01f098f --- /dev/null +++ b/internal/system/proxy.go @@ -0,0 +1,224 @@ +package system + +import ( + "fmt" + "os/exec" + "runtime" + "strings" + + "github.com/azoic/wormhole-client/pkg/logger" +) + +// SystemProxyManager 系统代理管理器 +type SystemProxyManager struct { + originalSettings map[string]string +} + +// NewSystemProxyManager 创建系统代理管理器 +func NewSystemProxyManager() *SystemProxyManager { + return &SystemProxyManager{ + originalSettings: make(map[string]string), + } +} + +// SetGlobalProxy 设置全局代理 +func (s *SystemProxyManager) SetGlobalProxy(httpProxy, httpsProxy, socksProxy string) error { + switch runtime.GOOS { + case "darwin": + return s.setMacOSProxy(httpProxy, httpsProxy, socksProxy) + case "windows": + return s.setWindowsProxy(httpProxy, httpsProxy, socksProxy) + case "linux": + return s.setLinuxProxy(httpProxy, httpsProxy, socksProxy) + default: + return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + } +} + +// RestoreProxy 恢复原始代理设置 +func (s *SystemProxyManager) RestoreProxy() error { + switch runtime.GOOS { + case "darwin": + return s.restoreMacOSProxy() + case "windows": + return s.restoreWindowsProxy() + case "linux": + return s.restoreLinuxProxy() + default: + return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + } +} + +// macOS 代理设置 +func (s *SystemProxyManager) setMacOSProxy(httpProxy, httpsProxy, socksProxy string) error { + logger.Info("Setting macOS system proxy...") + + // 获取网络服务名称 + networkService, err := s.getMacOSNetworkService() + if err != nil { + return fmt.Errorf("failed to get network service: %v", err) + } + + // 保存原始设置 + if err := s.saveMacOSOriginalSettings(networkService); err != nil { + logger.Warn("Failed to save original proxy settings: %v", err) + } + + // 设置HTTP代理 + if httpProxy != "" { + if err := s.runCommand("networksetup", "-setwebproxy", networkService, + s.parseProxyHost(httpProxy), s.parseProxyPort(httpProxy)); err != nil { + return fmt.Errorf("failed to set HTTP proxy: %v", err) + } + + if err := s.runCommand("networksetup", "-setwebproxystate", networkService, "on"); err != nil { + return fmt.Errorf("failed to enable HTTP proxy: %v", err) + } + } + + // 设置HTTPS代理 + if httpsProxy != "" { + if err := s.runCommand("networksetup", "-setsecurewebproxy", networkService, + s.parseProxyHost(httpsProxy), s.parseProxyPort(httpsProxy)); err != nil { + return fmt.Errorf("failed to set HTTPS proxy: %v", err) + } + + if err := s.runCommand("networksetup", "-setsecurewebproxystate", networkService, "on"); err != nil { + return fmt.Errorf("failed to enable HTTPS proxy: %v", err) + } + } + + // 设置SOCKS代理 + if socksProxy != "" { + if err := s.runCommand("networksetup", "-setsocksfirewallproxy", networkService, + s.parseProxyHost(socksProxy), s.parseProxyPort(socksProxy)); err != nil { + return fmt.Errorf("failed to set SOCKS proxy: %v", err) + } + + if err := s.runCommand("networksetup", "-setsocksfirewallproxystate", networkService, "on"); err != nil { + return fmt.Errorf("failed to enable SOCKS proxy: %v", err) + } + } + + logger.Info("macOS system proxy configured successfully") + return nil +} + +// 获取macOS网络服务名称 +func (s *SystemProxyManager) getMacOSNetworkService() (string, error) { + output, err := exec.Command("networksetup", "-listallnetworkservices").Output() + if err != nil { + return "", err + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" && !strings.HasPrefix(line, "*") && !strings.Contains(line, "An asterisk") { + // 优先选择Wi-Fi,否则选择第一个可用的服务 + if strings.Contains(strings.ToLower(line), "wi-fi") || strings.Contains(strings.ToLower(line), "wifi") { + return line, nil + } + } + } + + // 如果没找到Wi-Fi,返回第一个可用的服务 + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" && !strings.HasPrefix(line, "*") && !strings.Contains(line, "An asterisk") { + return line, nil + } + } + + return "", fmt.Errorf("no network service found") +} + +// 保存macOS原始代理设置 +func (s *SystemProxyManager) saveMacOSOriginalSettings(networkService string) error { + // 保存HTTP代理状态 + if output, err := exec.Command("networksetup", "-getwebproxy", networkService).Output(); err == nil { + s.originalSettings["http_proxy"] = string(output) + } + + // 保存HTTPS代理状态 + if output, err := exec.Command("networksetup", "-getsecurewebproxy", networkService).Output(); err == nil { + s.originalSettings["https_proxy"] = string(output) + } + + // 保存SOCKS代理状态 + if output, err := exec.Command("networksetup", "-getsocksfirewallproxy", networkService).Output(); err == nil { + s.originalSettings["socks_proxy"] = string(output) + } + + s.originalSettings["network_service"] = networkService + return nil +} + +// 恢复macOS代理设置 +func (s *SystemProxyManager) restoreMacOSProxy() error { + networkService, exists := s.originalSettings["network_service"] + if !exists { + return fmt.Errorf("no network service information saved") + } + + logger.Info("Restoring macOS system proxy...") + + // 关闭所有代理 + s.runCommand("networksetup", "-setwebproxystate", networkService, "off") + s.runCommand("networksetup", "-setsecurewebproxystate", networkService, "off") + s.runCommand("networksetup", "-setsocksfirewallproxystate", networkService, "off") + + logger.Info("macOS system proxy restored") + return nil +} + +// Windows 代理设置(简化实现) +func (s *SystemProxyManager) setWindowsProxy(httpProxy, httpsProxy, socksProxy string) error { + logger.Warn("Windows proxy setting not fully implemented") + return fmt.Errorf("Windows proxy setting not implemented yet") +} + +func (s *SystemProxyManager) restoreWindowsProxy() error { + logger.Warn("Windows proxy restoration not fully implemented") + return nil +} + +// Linux 代理设置(简化实现) +func (s *SystemProxyManager) setLinuxProxy(httpProxy, httpsProxy, socksProxy string) error { + logger.Warn("Linux proxy setting not fully implemented") + return fmt.Errorf("Linux proxy setting not implemented yet") +} + +func (s *SystemProxyManager) restoreLinuxProxy() error { + logger.Warn("Linux proxy restoration not fully implemented") + return nil +} + +// 辅助函数 +func (s *SystemProxyManager) runCommand(name string, args ...string) error { + cmd := exec.Command(name, args...) + output, err := cmd.CombinedOutput() + if err != nil { + logger.Error("Command failed: %s %v, output: %s", name, args, string(output)) + return err + } + return nil +} + +func (s *SystemProxyManager) parseProxyHost(proxy string) string { + // 简单解析,格式: host:port + parts := strings.Split(proxy, ":") + if len(parts) >= 1 { + return parts[0] + } + return proxy +} + +func (s *SystemProxyManager) parseProxyPort(proxy string) string { + // 简单解析,格式: host:port + parts := strings.Split(proxy, ":") + if len(parts) >= 2 { + return parts[1] + } + return "8080" // 默认端口 +} diff --git a/pkg/dns/proxy.go b/pkg/dns/proxy.go new file mode 100644 index 0000000..644261c --- /dev/null +++ b/pkg/dns/proxy.go @@ -0,0 +1,252 @@ +package dns + +import ( + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/azoic/wormhole-client/pkg/logger" +) + +// DNSProxy DNS代理服务器 +type DNSProxy struct { + upstreamDNS string + localPort int + server *net.UDPConn + cache *dnsCache + running bool + mutex sync.RWMutex +} + +// dnsCache DNS缓存 +type dnsCache struct { + entries map[string]*cacheEntry + mutex sync.RWMutex +} + +type cacheEntry struct { + response []byte + expiry time.Time +} + +// NewDNSProxy 创建DNS代理 +func NewDNSProxy(upstreamDNS string, localPort int) *DNSProxy { + return &DNSProxy{ + upstreamDNS: upstreamDNS, + localPort: localPort, + cache: &dnsCache{ + entries: make(map[string]*cacheEntry), + }, + } +} + +// Start 启动DNS代理服务器 +func (d *DNSProxy) Start() error { + d.mutex.Lock() + defer d.mutex.Unlock() + + if d.running { + return fmt.Errorf("DNS proxy is already running") + } + + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", d.localPort)) + if err != nil { + return fmt.Errorf("failed to resolve UDP address: %v", err) + } + + d.server, err = net.ListenUDP("udp", addr) + if err != nil { + return fmt.Errorf("failed to start DNS proxy server: %v", err) + } + + d.running = true + logger.Info("DNS proxy started on port %d", d.localPort) + + // 启动清理缓存的goroutine + go d.cleanupCache() + + // 处理DNS请求 + go d.handleRequests() + + return nil +} + +// Stop 停止DNS代理服务器 +func (d *DNSProxy) Stop() error { + d.mutex.Lock() + defer d.mutex.Unlock() + + if !d.running { + return nil + } + + d.running = false + if d.server != nil { + d.server.Close() + } + + logger.Info("DNS proxy stopped") + return nil +} + +// handleRequests 处理DNS请求 +func (d *DNSProxy) handleRequests() { + buffer := make([]byte, 512) // DNS消息最大512字节(UDP) + + for d.isRunning() { + n, clientAddr, err := d.server.ReadFromUDP(buffer) + if err != nil { + if d.isRunning() { + logger.Error("Failed to read DNS request: %v", err) + } + continue + } + + go d.processRequest(buffer[:n], clientAddr) + } +} + +// processRequest 处理单个DNS请求 +func (d *DNSProxy) processRequest(request []byte, clientAddr *net.UDPAddr) { + // 简单的DNS请求解析 + domain := d.extractDomain(request) + logger.Debug("DNS request for domain: %s", domain) + + // 检查缓存 + if cachedResponse := d.getFromCache(domain); cachedResponse != nil { + logger.Debug("Serving %s from cache", domain) + d.server.WriteToUDP(cachedResponse, clientAddr) + return + } + + // 转发到上游DNS服务器 + response, err := d.forwardToUpstream(request) + if err != nil { + logger.Error("Failed to forward DNS request: %v", err) + return + } + + // 缓存响应 + d.addToCache(domain, response) + + // 返回响应给客户端 + d.server.WriteToUDP(response, clientAddr) +} + +// extractDomain 从DNS请求中提取域名(简化实现) +func (d *DNSProxy) extractDomain(request []byte) string { + if len(request) < 12 { + return "" + } + + // 跳过DNS头部(12字节) + offset := 12 + var domain strings.Builder + + for offset < len(request) { + length := int(request[offset]) + if length == 0 { + break + } + + offset++ + if offset+length > len(request) { + break + } + + if domain.Len() > 0 { + domain.WriteByte('.') + } + domain.Write(request[offset : offset+length]) + offset += length + } + + return domain.String() +} + +// forwardToUpstream 转发DNS请求到上游服务器 +func (d *DNSProxy) forwardToUpstream(request []byte) ([]byte, error) { + conn, err := net.Dial("udp", d.upstreamDNS) + if err != nil { + return nil, fmt.Errorf("failed to connect to upstream DNS: %v", err) + } + defer conn.Close() + + // 设置超时 + conn.SetDeadline(time.Now().Add(5 * time.Second)) + + // 发送请求 + if _, err := conn.Write(request); err != nil { + return nil, fmt.Errorf("failed to send DNS request: %v", err) + } + + // 读取响应 + response := make([]byte, 512) + n, err := conn.Read(response) + if err != nil { + return nil, fmt.Errorf("failed to read DNS response: %v", err) + } + + return response[:n], nil +} + +// getFromCache 从缓存获取DNS响应 +func (d *DNSProxy) getFromCache(domain string) []byte { + d.cache.mutex.RLock() + defer d.cache.mutex.RUnlock() + + entry, exists := d.cache.entries[domain] + if !exists || time.Now().After(entry.expiry) { + return nil + } + + return entry.response +} + +// addToCache 添加DNS响应到缓存 +func (d *DNSProxy) addToCache(domain string, response []byte) { + d.cache.mutex.Lock() + defer d.cache.mutex.Unlock() + + // 设置缓存过期时间(5分钟) + expiry := time.Now().Add(5 * time.Minute) + + d.cache.entries[domain] = &cacheEntry{ + response: make([]byte, len(response)), + expiry: expiry, + } + copy(d.cache.entries[domain].response, response) +} + +// cleanupCache 清理过期的缓存条目 +func (d *DNSProxy) cleanupCache() { + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if !d.isRunning() { + return + } + + d.cache.mutex.Lock() + now := time.Now() + for domain, entry := range d.cache.entries { + if now.After(entry.expiry) { + delete(d.cache.entries, domain) + } + } + d.cache.mutex.Unlock() + } + } +} + +// isRunning 检查DNS代理是否在运行 +func (d *DNSProxy) isRunning() bool { + d.mutex.RLock() + defer d.mutex.RUnlock() + return d.running +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..c84cdac --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,121 @@ +package logger + +import ( + "fmt" + "log" + "os" + "strings" + "time" +) + +type LogLevel int + +const ( + DEBUG LogLevel = iota + INFO + WARN + ERROR +) + +type Logger struct { + level LogLevel + logger *log.Logger +} + +var defaultLogger *Logger + +func init() { + defaultLogger = New("INFO") +} + +// New 创建新的日志记录器 +func New(levelStr string) *Logger { + level := parseLogLevel(levelStr) + logger := log.New(os.Stdout, "", 0) + + return &Logger{ + level: level, + logger: logger, + } +} + +// SetLevel 设置日志级别 +func SetLevel(levelStr string) { + defaultLogger.level = parseLogLevel(levelStr) +} + +func parseLogLevel(levelStr string) LogLevel { + switch strings.ToUpper(levelStr) { + case "DEBUG": + return DEBUG + case "INFO": + return INFO + case "WARN", "WARNING": + return WARN + case "ERROR": + return ERROR + default: + return INFO + } +} + +func (l *Logger) log(level LogLevel, format string, args ...interface{}) { + if level < l.level { + return + } + + levelStr := "" + switch level { + case DEBUG: + levelStr = "DEBUG" + case INFO: + levelStr = "INFO" + case WARN: + levelStr = "WARN" + case ERROR: + levelStr = "ERROR" + } + + timestamp := time.Now().Format("2006-01-02 15:04:05") + message := fmt.Sprintf(format, args...) + logLine := fmt.Sprintf("[%s] [%s] %s", timestamp, levelStr, message) + + l.logger.Println(logLine) +} + +// Debug 调试日志 +func (l *Logger) Debug(format string, args ...interface{}) { + l.log(DEBUG, format, args...) +} + +// Info 信息日志 +func (l *Logger) Info(format string, args ...interface{}) { + l.log(INFO, format, args...) +} + +// Warn 警告日志 +func (l *Logger) Warn(format string, args ...interface{}) { + l.log(WARN, format, args...) +} + +// Error 错误日志 +func (l *Logger) Error(format string, args ...interface{}) { + l.log(ERROR, format, args...) +} + +// 全局日志函数 +func Debug(format string, args ...interface{}) { + defaultLogger.Debug(format, args...) +} + +func Info(format string, args ...interface{}) { + defaultLogger.Info(format, args...) +} + +func Warn(format string, args ...interface{}) { + defaultLogger.Warn(format, args...) +} + +func Error(format string, args ...interface{}) { + defaultLogger.Error(format, args...) +}