You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

289 lines
8.5 KiB

package client
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/azoic/wormhole-client/internal/config"
"github.com/azoic/wormhole-client/internal/proxy"
"github.com/azoic/wormhole-client/internal/routing"
"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
config *config.Config
socks5Proxy *proxy.SOCKS5Proxy
dnsProxy *dns.DNSProxy
systemProxyMgr *system.SystemProxyManager
routeMatcher *routing.RouteMatcher
httpServer *http.Server
ctx context.Context
cancel context.CancelFunc
}
func NewClient(mode string) *Client {
ctx, cancel := context.WithCancel(context.Background())
return &Client{
mode: mode,
systemProxyMgr: system.NewSystemProxyManager(),
ctx: ctx,
cancel: cancel,
}
}
func (c *Client) Start(configPath string) error {
// 加载配置
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,
)
// 初始化路由匹配器(如果有路由配置)
if c.mode == "global" && len(cfg.GlobalProxy.Routing.BypassDomains) > 0 || len(cfg.GlobalProxy.Routing.ForceDomains) > 0 {
c.routeMatcher, err = routing.NewRouteMatcher(&cfg.GlobalProxy.Routing)
if err != nil {
logger.Warn("Failed to initialize route matcher: %v", err)
} else {
logger.Info("🛣 Route matcher initialized")
}
}
// 设置信号处理
c.setupSignalHandler()
switch c.mode {
case "http":
return c.startHTTPProxy()
case "global":
return c.startGlobalProxy()
case "transparent":
return c.startTransparentProxy()
default:
return fmt.Errorf("unsupported mode: %s", c.mode)
}
}
func (c *Client) startHTTPProxy() error {
logger.Info("🌐 Starting HTTP proxy mode...")
logger.Info("💡 Setting up HTTP proxy on port %d", c.config.Proxy.LocalPort)
c.httpServer = 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)
logger.Info("📊 Statistics available at: http://127.0.0.1:%d/stats", c.config.Proxy.LocalPort)
logger.Info("💚 Health check available at: http://127.0.0.1:%d/health", c.config.Proxy.LocalPort)
return c.httpServer.ListenAndServe()
}
func (c *Client) startGlobalProxy() error {
logger.Info("🌍 Starting global proxy mode...")
logger.Info("💡 This will configure system-wide proxy settings")
logger.Info("⚠ Requires administrator privileges")
// 启动HTTP代理服务器
c.httpServer = c.socks5Proxy.CreateHTTPProxy(c.config.Proxy.LocalPort)
errChan := make(chan error, 1)
go func() {
if err := c.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
errChan <- fmt.Errorf("HTTP proxy server failed: %v", err)
}
}()
// 等待服务器启动
time.Sleep(500 * time.Millisecond)
// 设置系统代理
httpProxy := fmt.Sprintf("127.0.0.1:%d", c.config.Proxy.LocalPort)
httpsProxy := httpProxy
logger.Info("📡 Setting system proxy to %s", 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 instructions:")
logger.Info(" - Set HTTP proxy to: %s", httpProxy)
logger.Info(" - Set HTTPS proxy to: %s", httpsProxy)
} 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)
// 创建DNS代理(通过SOCKS5转发DNS查询)
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("💡 Configure your system DNS to: 127.0.0.1:%d", c.config.GlobalProxy.DNSPort)
}
}
// 显示路由配置信息
if c.routeMatcher != nil {
stats := c.routeMatcher.GetStats()
logger.Info("🛣 Routing configuration:")
logger.Info(" - Bypass domains: %v", stats["bypass_domains_count"])
logger.Info(" - Force domains: %v", stats["force_domains_count"])
logger.Info(" - Bypass local: %v", stats["bypass_local"])
logger.Info(" - Bypass private: %v", stats["bypass_private"])
}
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)
}
logger.Info("📊 Statistics: http://%s/stats", httpProxy)
logger.Info("💚 Health check: http://%s/health", httpProxy)
logger.Info("🛑 Press Ctrl+C to stop")
// 检查是否有错误
select {
case err := <-errChan:
return err
case <-c.ctx.Done():
return nil
}
}
func (c *Client) startTransparentProxy() error {
logger.Info("🔍 Starting transparent proxy mode...")
logger.Info("💡 This will intercept network traffic transparently")
logger.Info("⚠ Requires root privileges and iptables support")
// TODO: 实现透明代理
logger.Error("❌ Transparent proxy mode is not yet implemented")
logger.Info("💡 Available alternatives:")
logger.Info(" - Use global mode: ./bin/wormhole-client -mode global")
logger.Info(" - Use HTTP mode: ./bin/wormhole-client -mode http")
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("🛑 Received shutdown signal...")
c.shutdown()
}()
}
func (c *Client) shutdown() {
logger.Info("🔄 Shutting down gracefully...")
// 停止HTTP服务器
if c.httpServer != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := c.httpServer.Shutdown(ctx); err != nil {
logger.Error("Failed to shutdown HTTP server gracefully: %v", err)
} else {
logger.Info("✅ HTTP server stopped")
}
}
// 恢复系统代理设置
if c.systemProxyMgr != nil && c.systemProxyMgr.IsEnabled() {
logger.Info("🔄 Restoring system proxy settings...")
if err := c.systemProxyMgr.RestoreProxy(); err != nil {
logger.Error("Failed to restore system proxy: %v", err)
}
}
// 停止DNS代理
if c.dnsProxy != nil {
logger.Info("🔄 Stopping DNS proxy...")
if err := c.dnsProxy.Stop(); err != nil {
logger.Error("Failed to stop DNS proxy: %v", err)
} else {
logger.Info("✅ DNS proxy stopped")
}
}
// 显示统计信息
if c.socks5Proxy != nil {
stats := c.socks5Proxy.GetStats()
logger.Info("📊 Final statistics:")
logger.Info(" - Total connections: %d", stats.TotalConnections)
logger.Info(" - Successful requests: %d", stats.SuccessfulRequests)
logger.Info(" - Failed requests: %d", stats.FailedRequests)
logger.Info(" - Success rate: %.2f%%", stats.GetSuccessRate())
logger.Info(" - Total bytes: %d", stats.GetTotalBytes())
logger.Info(" - Uptime: %v", stats.Uptime)
}
logger.Info("✅ Cleanup completed")
// 取消上下文
c.cancel()
// 给一点时间让goroutines优雅退出
time.Sleep(100 * time.Millisecond)
os.Exit(0)
}
// GetStats 获取客户端统计信息
func (c *Client) GetStats() map[string]interface{} {
stats := make(map[string]interface{})
if c.socks5Proxy != nil {
proxyStats := c.socks5Proxy.GetStats()
stats["proxy"] = proxyStats
}
if c.systemProxyMgr != nil {
stats["system_proxy_enabled"] = c.systemProxyMgr.IsEnabled()
if currentProxy, err := c.systemProxyMgr.GetCurrentProxy(); err == nil {
stats["current_system_proxy"] = currentProxy
}
}
if c.routeMatcher != nil {
stats["routing"] = c.routeMatcher.GetStats()
}
stats["mode"] = c.mode
stats["config_file"] = c.config
return stats
}