|
|
|
|
|
<!DOCTYPE html>
|
|
|
<html>
|
|
|
<head>
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
|
<title>wormhole-client: Go Coverage Report</title>
|
|
|
<style>
|
|
|
body {
|
|
|
background: black;
|
|
|
color: rgb(80, 80, 80);
|
|
|
}
|
|
|
body, pre, #legend span {
|
|
|
font-family: Menlo, monospace;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
#topbar {
|
|
|
background: black;
|
|
|
position: fixed;
|
|
|
top: 0; left: 0; right: 0;
|
|
|
height: 42px;
|
|
|
border-bottom: 1px solid rgb(80, 80, 80);
|
|
|
}
|
|
|
#content {
|
|
|
margin-top: 50px;
|
|
|
}
|
|
|
#nav, #legend {
|
|
|
float: left;
|
|
|
margin-left: 10px;
|
|
|
}
|
|
|
#legend {
|
|
|
margin-top: 12px;
|
|
|
}
|
|
|
#nav {
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
#legend span {
|
|
|
margin: 0 5px;
|
|
|
}
|
|
|
.cov0 { color: rgb(192, 0, 0) }
|
|
|
.cov1 { color: rgb(128, 128, 128) }
|
|
|
.cov2 { color: rgb(116, 140, 131) }
|
|
|
.cov3 { color: rgb(104, 152, 134) }
|
|
|
.cov4 { color: rgb(92, 164, 137) }
|
|
|
.cov5 { color: rgb(80, 176, 140) }
|
|
|
.cov6 { color: rgb(68, 188, 143) }
|
|
|
.cov7 { color: rgb(56, 200, 146) }
|
|
|
.cov8 { color: rgb(44, 212, 149) }
|
|
|
.cov9 { color: rgb(32, 224, 152) }
|
|
|
.cov10 { color: rgb(20, 236, 155) }
|
|
|
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div id="topbar">
|
|
|
<div id="nav">
|
|
|
<select id="files">
|
|
|
|
|
|
<option value="file0">github.com/azoic/wormhole-client/cmd/wormhole-client/main.go (0.0%)</option>
|
|
|
|
|
|
<option value="file1">github.com/azoic/wormhole-client/internal/client/client.go (31.9%)</option>
|
|
|
|
|
|
<option value="file2">github.com/azoic/wormhole-client/internal/config/config.go (90.9%)</option>
|
|
|
|
|
|
<option value="file3">github.com/azoic/wormhole-client/internal/proxy/socks5.go (22.2%)</option>
|
|
|
|
|
|
<option value="file4">github.com/azoic/wormhole-client/internal/proxy/stats.go (96.2%)</option>
|
|
|
|
|
|
<option value="file5">github.com/azoic/wormhole-client/internal/system/proxy.go (0.0%)</option>
|
|
|
|
|
|
<option value="file6">github.com/azoic/wormhole-client/pkg/dns/proxy.go (0.0%)</option>
|
|
|
|
|
|
<option value="file7">github.com/azoic/wormhole-client/pkg/logger/logger.go (0.0%)</option>
|
|
|
|
|
|
</select>
|
|
|
</div>
|
|
|
<div id="legend">
|
|
|
<span>not tracked</span>
|
|
|
|
|
|
<span class="cov0">no coverage</span>
|
|
|
<span class="cov1">low coverage</span>
|
|
|
<span class="cov2">*</span>
|
|
|
<span class="cov3">*</span>
|
|
|
<span class="cov4">*</span>
|
|
|
<span class="cov5">*</span>
|
|
|
<span class="cov6">*</span>
|
|
|
<span class="cov7">*</span>
|
|
|
<span class="cov8">*</span>
|
|
|
<span class="cov9">*</span>
|
|
|
<span class="cov10">high coverage</span>
|
|
|
|
|
|
</div>
|
|
|
</div>
|
|
|
<div id="content">
|
|
|
|
|
|
<pre class="file" id="file0" style="display: none">package main
|
|
|
|
|
|
import (
|
|
|
"flag"
|
|
|
"fmt"
|
|
|
"log"
|
|
|
"os"
|
|
|
|
|
|
"github.com/azoic/wormhole-client/internal/client"
|
|
|
)
|
|
|
|
|
|
var (
|
|
|
version = "v1.0.0"
|
|
|
buildTime = "unknown"
|
|
|
)
|
|
|
|
|
|
func main() <span class="cov0" title="0">{
|
|
|
configPath := flag.String("config", "configs/client.yaml", "Configuration file path")
|
|
|
mode := flag.String("mode", "http", "Client mode: http, global, transparent")
|
|
|
showVersion := flag.Bool("version", false, "Show version information")
|
|
|
flag.Parse()
|
|
|
|
|
|
if *showVersion </span><span class="cov0" title="0">{
|
|
|
fmt.Printf("Wormhole SOCKS5 Client %s\n", version)
|
|
|
fmt.Printf("Build time: %s\n", buildTime)
|
|
|
os.Exit(0)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">fmt.Printf("🚀 Starting Wormhole SOCKS5 Client %s\n", version)
|
|
|
fmt.Printf("📄 Config: %s\n", *configPath)
|
|
|
fmt.Printf("🔧 Mode: %s\n", *mode)
|
|
|
|
|
|
// TODO: 实现完整的客户端逻辑
|
|
|
cli := client.NewClient(*mode)
|
|
|
if err := cli.Start(*configPath); err != nil </span><span class="cov0" title="0">{
|
|
|
log.Fatalf("Client failed: %v", err)
|
|
|
}</span>
|
|
|
}
|
|
|
</pre>
|
|
|
|
|
|
<pre class="file" id="file1" style="display: none">package client
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
"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
|
|
|
config *config.Config
|
|
|
socks5Proxy *proxy.SOCKS5Proxy
|
|
|
dnsProxy *dns.DNSProxy
|
|
|
systemProxyMgr *system.SystemProxyManager
|
|
|
}
|
|
|
|
|
|
func NewClient(mode string) *Client <span class="cov10" title="3">{
|
|
|
return &Client{
|
|
|
mode: mode,
|
|
|
systemProxyMgr: system.NewSystemProxyManager(),
|
|
|
}
|
|
|
}</span>
|
|
|
|
|
|
func (c *Client) Start(configPath string) error <span class="cov6" title="2">{
|
|
|
// 加载配置
|
|
|
cfg, err := config.LoadConfig(configPath)
|
|
|
if err != nil </span><span class="cov1" title="1">{
|
|
|
return fmt.Errorf("failed to load config: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov1" title="1">if err := cfg.Validate(); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("invalid config: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov1" title="1">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 </span>{
|
|
|
case "http":<span class="cov0" title="0">
|
|
|
return c.startHTTPProxy()</span>
|
|
|
case "global":<span class="cov0" title="0">
|
|
|
return c.startGlobalProxy()</span>
|
|
|
case "transparent":<span class="cov0" title="0">
|
|
|
return c.startTransparentProxy()</span>
|
|
|
default:<span class="cov1" title="1">
|
|
|
return fmt.Errorf("unsupported mode: %s", c.mode)</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func (c *Client) startHTTPProxy() error <span class="cov0" title="0">{
|
|
|
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()
|
|
|
}</span>
|
|
|
|
|
|
func (c *Client) startGlobalProxy() error <span class="cov0" title="0">{
|
|
|
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() </span><span class="cov0" title="0">{
|
|
|
if err := server.ListenAndServe(); err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Error("HTTP proxy server failed: %v", err)
|
|
|
}</span>
|
|
|
}()
|
|
|
|
|
|
// 设置系统代理
|
|
|
<span class="cov0" title="0">httpProxy := fmt.Sprintf("127.0.0.1:%d", c.config.Proxy.LocalPort)
|
|
|
httpsProxy := httpProxy
|
|
|
|
|
|
if err := c.systemProxyMgr.SetGlobalProxy(httpProxy, httpsProxy, ""); err != nil </span><span class="cov0" title="0">{
|
|
|
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)
|
|
|
}</span> else<span class="cov0" title="0"> {
|
|
|
logger.Info("✅ System proxy configured successfully")
|
|
|
}</span>
|
|
|
|
|
|
// 启动DNS代理(如果启用)
|
|
|
<span class="cov0" title="0">if c.config.GlobalProxy.DNSProxy </span><span class="cov0" title="0">{
|
|
|
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 </span><span class="cov0" title="0">{
|
|
|
logger.Warn("Failed to start DNS proxy: %v", err)
|
|
|
}</span> else<span class="cov0" title="0"> {
|
|
|
logger.Info("✅ DNS proxy started")
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">logger.Info("🎉 Global proxy mode started successfully")
|
|
|
logger.Info("📍 HTTP/HTTPS Proxy: %s", httpProxy)
|
|
|
if c.dnsProxy != nil </span><span class="cov0" title="0">{
|
|
|
logger.Info("📍 DNS Proxy: 127.0.0.1:%d", c.config.GlobalProxy.DNSPort)
|
|
|
}</span>
|
|
|
|
|
|
// 保持运行
|
|
|
<span class="cov0" title="0">select </span>{}
|
|
|
}
|
|
|
|
|
|
func (c *Client) startTransparentProxy() error <span class="cov0" title="0">{
|
|
|
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")
|
|
|
}</span>
|
|
|
|
|
|
func (c *Client) setupSignalHandler() <span class="cov1" title="1">{
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
|
|
|
|
go func() </span><span class="cov1" title="1">{
|
|
|
<-sigChan
|
|
|
logger.Info("🛑 Shutting down...")
|
|
|
c.cleanup()
|
|
|
os.Exit(0)
|
|
|
}</span>()
|
|
|
}
|
|
|
|
|
|
func (c *Client) cleanup() <span class="cov0" title="0">{
|
|
|
// 恢复系统代理设置
|
|
|
if c.systemProxyMgr != nil </span><span class="cov0" title="0">{
|
|
|
if err := c.systemProxyMgr.RestoreProxy(); err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Error("Failed to restore system proxy: %v", err)
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
// 停止DNS代理
|
|
|
<span class="cov0" title="0">if c.dnsProxy != nil </span><span class="cov0" title="0">{
|
|
|
if err := c.dnsProxy.Stop(); err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Error("Failed to stop DNS proxy: %v", err)
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">logger.Info("✅ Cleanup completed")</span>
|
|
|
}
|
|
|
</pre>
|
|
|
|
|
|
<pre class="file" id="file2" style="display: none">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) <span class="cov5" title="2">{
|
|
|
data, err := ioutil.ReadFile(configPath)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return nil, fmt.Errorf("failed to read config file: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov5" title="2">var config Config
|
|
|
if err := yaml.Unmarshal(data, &config); err != nil </span><span class="cov0" title="0">{
|
|
|
return nil, fmt.Errorf("failed to parse config file: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 设置默认值
|
|
|
<span class="cov5" title="2">if config.LogLevel == "" </span><span class="cov1" title="1">{
|
|
|
config.LogLevel = "info"
|
|
|
}</span>
|
|
|
<span class="cov5" title="2">if config.Timeout == 0 </span><span class="cov1" title="1">{
|
|
|
config.Timeout = 30 * time.Second
|
|
|
}</span>
|
|
|
<span class="cov5" title="2">if config.Proxy.LocalPort == 0 </span><span class="cov1" title="1">{
|
|
|
config.Proxy.LocalPort = 8080
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov5" title="2">return &config, nil</span>
|
|
|
}
|
|
|
|
|
|
// GetServerAddr 获取服务器地址
|
|
|
func (c *Config) GetServerAddr() string <span class="cov1" title="1">{
|
|
|
return fmt.Sprintf("%s:%d", c.Server.Address, c.Server.Port)
|
|
|
}</span>
|
|
|
|
|
|
// Validate 验证配置
|
|
|
func (c *Config) Validate() error <span class="cov10" title="4">{
|
|
|
if c.Server.Address == "" </span><span class="cov1" title="1">{
|
|
|
return fmt.Errorf("server address is required")
|
|
|
}</span>
|
|
|
<span class="cov8" title="3">if c.Server.Port <= 0 || c.Server.Port > 65535 </span><span class="cov1" title="1">{
|
|
|
return fmt.Errorf("invalid server port: %d", c.Server.Port)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov5" title="2">validModes := map[string]bool{"http": true, "global": true, "transparent": true}
|
|
|
if !validModes[c.Proxy.Mode] </span><span class="cov1" title="1">{
|
|
|
return fmt.Errorf("invalid proxy mode: %s", c.Proxy.Mode)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov1" title="1">return nil</span>
|
|
|
}
|
|
|
</pre>
|
|
|
|
|
|
<pre class="file" id="file3" style="display: none">package proxy
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
"io"
|
|
|
"net"
|
|
|
"net/http"
|
|
|
"strconv"
|
|
|
"sync"
|
|
|
"time"
|
|
|
|
|
|
"github.com/azoic/wormhole-client/pkg/logger"
|
|
|
)
|
|
|
|
|
|
// SOCKS5Proxy SOCKS5代理客户端
|
|
|
type SOCKS5Proxy struct {
|
|
|
serverAddr string
|
|
|
username string
|
|
|
password string
|
|
|
timeout time.Duration
|
|
|
connPool *connectionPool
|
|
|
stats *ProxyStats
|
|
|
}
|
|
|
|
|
|
// connectionPool 连接池
|
|
|
type connectionPool struct {
|
|
|
connections chan net.Conn
|
|
|
maxSize int
|
|
|
mutex sync.Mutex
|
|
|
}
|
|
|
|
|
|
// NewSOCKS5Proxy 创建SOCKS5代理客户端
|
|
|
func NewSOCKS5Proxy(serverAddr, username, password string, timeout time.Duration) *SOCKS5Proxy <span class="cov6" title="6">{
|
|
|
return &SOCKS5Proxy{
|
|
|
serverAddr: serverAddr,
|
|
|
username: username,
|
|
|
password: password,
|
|
|
timeout: timeout,
|
|
|
connPool: &connectionPool{
|
|
|
connections: make(chan net.Conn, 10),
|
|
|
maxSize: 10,
|
|
|
},
|
|
|
stats: NewProxyStats(),
|
|
|
}
|
|
|
}</span>
|
|
|
|
|
|
// CreateHTTPProxy 创建HTTP代理服务器
|
|
|
func (p *SOCKS5Proxy) CreateHTTPProxy(localPort int) *http.Server <span class="cov1" title="1">{
|
|
|
proxyHandler := &httpProxyHandler{
|
|
|
socks5Proxy: p,
|
|
|
}
|
|
|
|
|
|
// 创建ServeMux来处理不同的路径
|
|
|
mux := http.NewServeMux()
|
|
|
mux.Handle("/", proxyHandler)
|
|
|
mux.HandleFunc("/stats", p.handleStats)
|
|
|
mux.HandleFunc("/health", p.handleHealth)
|
|
|
|
|
|
server := &http.Server{
|
|
|
Addr: fmt.Sprintf(":%d", localPort),
|
|
|
Handler: mux,
|
|
|
ReadTimeout: 30 * time.Second,
|
|
|
WriteTimeout: 30 * time.Second,
|
|
|
IdleTimeout: 120 * time.Second,
|
|
|
MaxHeaderBytes: 1 << 20, // 1MB
|
|
|
}
|
|
|
|
|
|
return server
|
|
|
}</span>
|
|
|
|
|
|
// httpProxyHandler HTTP代理处理器
|
|
|
type httpProxyHandler struct {
|
|
|
socks5Proxy *SOCKS5Proxy
|
|
|
}
|
|
|
|
|
|
func (h *httpProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{
|
|
|
// 统计连接
|
|
|
h.socks5Proxy.stats.IncrementConnections()
|
|
|
defer h.socks5Proxy.stats.DecrementActiveConnections()
|
|
|
|
|
|
logger.Debug("Processing request: %s %s from %s", r.Method, r.URL.String(), r.RemoteAddr)
|
|
|
|
|
|
if r.Method == http.MethodConnect </span><span class="cov0" title="0">{
|
|
|
h.handleHTTPSProxy(w, r)
|
|
|
}</span> else<span class="cov0" title="0"> {
|
|
|
h.handleHTTPProxy(w, r)
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
// handleHTTPSProxy 处理HTTPS代理请求 (CONNECT方法)
|
|
|
func (h *httpProxyHandler) handleHTTPSProxy(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), h.socks5Proxy.timeout)
|
|
|
defer cancel()
|
|
|
|
|
|
destConn, err := h.socks5Proxy.DialTCPWithContext(ctx, r.Host)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
h.socks5Proxy.stats.IncrementFailedRequests()
|
|
|
h.socks5Proxy.stats.IncrementSOCKS5Error("connection_failed")
|
|
|
logger.Error("Failed to connect via SOCKS5 to %s: %v", r.Host, err)
|
|
|
http.Error(w, "Bad Gateway", http.StatusBadGateway)
|
|
|
return
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">defer destConn.Close()
|
|
|
|
|
|
// 发送200 Connection established响应
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
|
hijacker, ok := w.(http.Hijacker)
|
|
|
if !ok </span><span class="cov0" title="0">{
|
|
|
h.socks5Proxy.stats.IncrementFailedRequests()
|
|
|
logger.Error("Hijacking not supported")
|
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">clientConn, _, err := hijacker.Hijack()
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
h.socks5Proxy.stats.IncrementFailedRequests()
|
|
|
logger.Error("Failed to hijack connection: %v", err)
|
|
|
return
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">defer clientConn.Close()
|
|
|
|
|
|
h.socks5Proxy.stats.IncrementSuccessfulRequests()
|
|
|
logger.Debug("Established HTTPS tunnel to %s", r.Host)
|
|
|
|
|
|
// 双向数据转发
|
|
|
var wg sync.WaitGroup
|
|
|
wg.Add(2)
|
|
|
|
|
|
go func() </span><span class="cov0" title="0">{
|
|
|
defer wg.Done()
|
|
|
written := h.copyData(clientConn, destConn, "client->server")
|
|
|
h.socks5Proxy.stats.AddBytesTransferred(written, 0)
|
|
|
}</span>()
|
|
|
|
|
|
<span class="cov0" title="0">go func() </span><span class="cov0" title="0">{
|
|
|
defer wg.Done()
|
|
|
written := h.copyData(destConn, clientConn, "server->client")
|
|
|
h.socks5Proxy.stats.AddBytesTransferred(0, written)
|
|
|
}</span>()
|
|
|
|
|
|
<span class="cov0" title="0">wg.Wait()
|
|
|
logger.Debug("HTTPS tunnel to %s closed", r.Host)</span>
|
|
|
}
|
|
|
|
|
|
// handleHTTPProxy 处理HTTP代理请求
|
|
|
func (h *httpProxyHandler) handleHTTPProxy(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), h.socks5Proxy.timeout)
|
|
|
defer cancel()
|
|
|
|
|
|
// 确保URL包含Host
|
|
|
if r.URL.Host == "" </span><span class="cov0" title="0">{
|
|
|
r.URL.Host = r.Host
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">if r.URL.Scheme == "" </span><span class="cov0" title="0">{
|
|
|
r.URL.Scheme = "http"
|
|
|
}</span>
|
|
|
|
|
|
// 通过SOCKS5连接到目标服务器
|
|
|
<span class="cov0" title="0">destConn, err := h.socks5Proxy.DialTCPWithContext(ctx, r.Host)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
h.socks5Proxy.stats.IncrementFailedRequests()
|
|
|
h.socks5Proxy.stats.IncrementSOCKS5Error("connection_failed")
|
|
|
logger.Error("Failed to connect via SOCKS5 to %s: %v", r.Host, err)
|
|
|
http.Error(w, "Bad Gateway", http.StatusBadGateway)
|
|
|
return
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">defer destConn.Close()
|
|
|
|
|
|
// 发送HTTP请求
|
|
|
if err := r.Write(destConn); err != nil </span><span class="cov0" title="0">{
|
|
|
h.socks5Proxy.stats.IncrementFailedRequests()
|
|
|
logger.Error("Failed to write request to %s: %v", r.Host, err)
|
|
|
http.Error(w, "Bad Gateway", http.StatusBadGateway)
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
// 设置响应头
|
|
|
<span class="cov0" title="0">w.Header().Set("Via", "1.1 wormhole-proxy")
|
|
|
|
|
|
// 使用自定义ResponseWriter来统计字节数
|
|
|
statsWriter := &statsResponseWriter{
|
|
|
ResponseWriter: w,
|
|
|
stats: h.socks5Proxy.stats,
|
|
|
}
|
|
|
|
|
|
// 读取响应并返回给客户端
|
|
|
written, err := io.Copy(statsWriter, destConn)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
h.socks5Proxy.stats.IncrementFailedRequests()
|
|
|
logger.Error("Failed to copy response from %s: %v", r.Host, err)
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">h.socks5Proxy.stats.IncrementSuccessfulRequests()
|
|
|
h.socks5Proxy.stats.AddBytesTransferred(0, written)
|
|
|
logger.Debug("HTTP request to %s completed, %d bytes", r.Host, written)</span>
|
|
|
}
|
|
|
|
|
|
// statsResponseWriter 带统计功能的ResponseWriter
|
|
|
type statsResponseWriter struct {
|
|
|
http.ResponseWriter
|
|
|
stats *ProxyStats
|
|
|
}
|
|
|
|
|
|
func (w *statsResponseWriter) Write(data []byte) (int, error) <span class="cov0" title="0">{
|
|
|
n, err := w.ResponseWriter.Write(data)
|
|
|
if n > 0 </span><span class="cov0" title="0">{
|
|
|
w.stats.AddBytesTransferred(int64(n), 0)
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">return n, err</span>
|
|
|
}
|
|
|
|
|
|
// copyData 数据复制,带方向标识和字节统计
|
|
|
func (h *httpProxyHandler) copyData(dst, src net.Conn, direction string) int64 <span class="cov0" title="0">{
|
|
|
defer dst.Close()
|
|
|
defer src.Close()
|
|
|
|
|
|
written, err := io.Copy(dst, src)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Debug("Copy %s finished with error: %v, bytes: %d", direction, err, written)
|
|
|
}</span> else<span class="cov0" title="0"> {
|
|
|
logger.Debug("Copy %s finished successfully, bytes: %d", direction, written)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">return written</span>
|
|
|
}
|
|
|
|
|
|
// handleStats 处理统计信息请求
|
|
|
func (p *SOCKS5Proxy) handleStats(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{
|
|
|
if r.Method != http.MethodGet </span><span class="cov0" title="0">{
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">stats := p.stats.GetStats()
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
|
|
if err := json.NewEncoder(w).Encode(stats); err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Error("Failed to encode stats: %v", err)
|
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
|
return
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
// handleHealth 处理健康检查请求
|
|
|
func (p *SOCKS5Proxy) handleHealth(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{
|
|
|
if r.Method != http.MethodGet </span><span class="cov0" title="0">{
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">stats := p.stats.GetStats()
|
|
|
|
|
|
health := map[string]interface{}{
|
|
|
"status": "healthy",
|
|
|
"uptime": stats.Uptime.String(),
|
|
|
"active_connections": stats.ActiveConnections,
|
|
|
"success_rate": stats.GetSuccessRate(),
|
|
|
}
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
|
if err := json.NewEncoder(w).Encode(health); err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Error("Failed to encode health: %v", err)
|
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
|
return
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
// GetStats 获取代理统计信息
|
|
|
func (p *SOCKS5Proxy) GetStats() ProxyStatsSnapshot <span class="cov0" title="0">{
|
|
|
return p.stats.GetStats()
|
|
|
}</span>
|
|
|
|
|
|
// DialTCP 通过SOCKS5连接到目标地址
|
|
|
func (p *SOCKS5Proxy) DialTCP(address string) (net.Conn, error) <span class="cov0" title="0">{
|
|
|
return p.DialTCPWithContext(context.Background(), address)
|
|
|
}</span>
|
|
|
|
|
|
// DialTCPWithContext 通过SOCKS5连接到目标地址(带上下文)
|
|
|
func (p *SOCKS5Proxy) DialTCPWithContext(ctx context.Context, address string) (net.Conn, error) <span class="cov1" title="1">{
|
|
|
// 连接到SOCKS5代理服务器
|
|
|
dialer := &net.Dialer{
|
|
|
Timeout: p.timeout,
|
|
|
}
|
|
|
|
|
|
conn, err := dialer.DialContext(ctx, "tcp", p.serverAddr)
|
|
|
if err != nil </span><span class="cov1" title="1">{
|
|
|
return nil, fmt.Errorf("failed to connect to SOCKS5 server: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 设置连接超时
|
|
|
<span class="cov0" title="0">deadline, ok := ctx.Deadline()
|
|
|
if ok </span><span class="cov0" title="0">{
|
|
|
conn.SetDeadline(deadline)
|
|
|
}</span>
|
|
|
|
|
|
// 执行SOCKS5握手
|
|
|
<span class="cov0" title="0">if err := p.performSOCKS5Handshake(conn, address); err != nil </span><span class="cov0" title="0">{
|
|
|
conn.Close()
|
|
|
return nil, fmt.Errorf("SOCKS5 handshake failed: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 清除deadline,让连接正常使用
|
|
|
<span class="cov0" title="0">conn.SetDeadline(time.Time{})
|
|
|
|
|
|
logger.Debug("Successfully connected to %s via SOCKS5 proxy", address)
|
|
|
return conn, nil</span>
|
|
|
}
|
|
|
|
|
|
// performSOCKS5Handshake 执行SOCKS5握手协议
|
|
|
func (p *SOCKS5Proxy) performSOCKS5Handshake(conn net.Conn, targetAddr string) error <span class="cov0" title="0">{
|
|
|
// 设置握手超时
|
|
|
deadline := time.Now().Add(p.timeout)
|
|
|
conn.SetDeadline(deadline)
|
|
|
|
|
|
// 第一步:发送认证方法选择
|
|
|
authMethods := []byte{0x05, 0x02, 0x00, 0x02} // 版本5,2个方法,无认证+用户名密码认证
|
|
|
if _, err := conn.Write(authMethods); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to send auth methods: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 读取服务器响应
|
|
|
<span class="cov0" title="0">response := make([]byte, 2)
|
|
|
if _, err := io.ReadFull(conn, response); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to read auth response: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if response[0] != 0x05 </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("invalid SOCKS version: %d", response[0])
|
|
|
}</span>
|
|
|
|
|
|
// 第二步:处理认证
|
|
|
<span class="cov0" title="0">switch response[1] </span>{
|
|
|
case 0x00:<span class="cov0" title="0"> // 无认证
|
|
|
logger.Debug("SOCKS5 server requires no authentication")</span>
|
|
|
case 0x02:<span class="cov0" title="0"> // 用户名密码认证
|
|
|
if err := p.performUserPassAuth(conn); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("user/pass authentication failed: %v", err)
|
|
|
}</span>
|
|
|
case 0xFF:<span class="cov0" title="0"> // 无可接受的认证方法
|
|
|
return fmt.Errorf("no acceptable authentication methods")</span>
|
|
|
default:<span class="cov0" title="0">
|
|
|
return fmt.Errorf("unsupported authentication method: %d", response[1])</span>
|
|
|
}
|
|
|
|
|
|
// 第三步:发送连接请求
|
|
|
<span class="cov0" title="0">connectReq, err := p.buildConnectRequest(targetAddr)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to build connect request: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if _, err := conn.Write(connectReq); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to send connect request: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 第四步:读取连接响应
|
|
|
<span class="cov0" title="0">return p.readConnectResponse(conn)</span>
|
|
|
}
|
|
|
|
|
|
// buildConnectRequest 构建连接请求
|
|
|
func (p *SOCKS5Proxy) buildConnectRequest(targetAddr string) ([]byte, error) <span class="cov7" title="8">{
|
|
|
host, portStr, err := net.SplitHostPort(targetAddr)
|
|
|
if err != nil </span><span class="cov1" title="1">{
|
|
|
return nil, fmt.Errorf("invalid target address: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 解析端口号
|
|
|
<span class="cov7" title="7">portNum, err := parsePort(portStr)
|
|
|
if err != nil </span><span class="cov3" title="2">{
|
|
|
return nil, fmt.Errorf("invalid port: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov6" title="5">var connectReq []byte
|
|
|
|
|
|
// 检测地址类型并构建请求
|
|
|
if ip := net.ParseIP(host); ip != nil </span><span class="cov4" title="3">{
|
|
|
if ip4 := ip.To4(); ip4 != nil </span><span class="cov1" title="1">{
|
|
|
// IPv4地址
|
|
|
connectReq = []byte{0x05, 0x01, 0x00, 0x01}
|
|
|
connectReq = append(connectReq, ip4...)
|
|
|
}</span> else<span class="cov3" title="2"> if ip6 := ip.To16(); ip6 != nil </span><span class="cov3" title="2">{
|
|
|
// IPv6地址
|
|
|
connectReq = []byte{0x05, 0x01, 0x00, 0x04}
|
|
|
connectReq = append(connectReq, ip6...)
|
|
|
}</span>
|
|
|
} else<span class="cov3" title="2"> {
|
|
|
// 域名
|
|
|
if len(host) > 255 </span><span class="cov0" title="0">{
|
|
|
return nil, fmt.Errorf("domain name too long: %d", len(host))
|
|
|
}</span>
|
|
|
<span class="cov3" title="2">connectReq = []byte{0x05, 0x01, 0x00, 0x03}
|
|
|
connectReq = append(connectReq, byte(len(host)))
|
|
|
connectReq = append(connectReq, []byte(host)...)</span>
|
|
|
}
|
|
|
|
|
|
// 添加端口
|
|
|
<span class="cov6" title="5">connectReq = append(connectReq, byte(portNum>>8), byte(portNum&0xFF))
|
|
|
|
|
|
return connectReq, nil</span>
|
|
|
}
|
|
|
|
|
|
// readConnectResponse 读取连接响应
|
|
|
func (p *SOCKS5Proxy) readConnectResponse(conn net.Conn) error <span class="cov0" title="0">{
|
|
|
// 读取响应头部
|
|
|
header := make([]byte, 4)
|
|
|
if _, err := io.ReadFull(conn, header); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to read connect response header: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if header[0] != 0x05 </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("invalid SOCKS version in response: %d", header[0])
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if header[1] != 0x00 </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("connection failed, status: %d (%s)", header[1], getSOCKS5ErrorMessage(header[1]))
|
|
|
}</span>
|
|
|
|
|
|
// 读取绑定地址和端口
|
|
|
<span class="cov0" title="0">addrType := header[3]
|
|
|
switch addrType </span>{
|
|
|
case 0x01:<span class="cov0" title="0"> // IPv4
|
|
|
skipBytes := make([]byte, 6) // 4字节IP + 2字节端口
|
|
|
_, err := io.ReadFull(conn, skipBytes)
|
|
|
return err</span>
|
|
|
case 0x03:<span class="cov0" title="0"> // 域名
|
|
|
lenByte := make([]byte, 1)
|
|
|
if _, err := io.ReadFull(conn, lenByte); err != nil </span><span class="cov0" title="0">{
|
|
|
return err
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">skipBytes := make([]byte, int(lenByte[0])+2) // 域名长度 + 2字节端口
|
|
|
_, err := io.ReadFull(conn, skipBytes)
|
|
|
return err</span>
|
|
|
case 0x04:<span class="cov0" title="0"> // IPv6
|
|
|
skipBytes := make([]byte, 18) // 16字节IP + 2字节端口
|
|
|
_, err := io.ReadFull(conn, skipBytes)
|
|
|
return err</span>
|
|
|
default:<span class="cov0" title="0">
|
|
|
return fmt.Errorf("unsupported address type: %d", addrType)</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// performUserPassAuth 执行用户名密码认证
|
|
|
func (p *SOCKS5Proxy) performUserPassAuth(conn net.Conn) error <span class="cov0" title="0">{
|
|
|
// 发送用户名密码
|
|
|
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 </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to send credentials: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 读取认证结果
|
|
|
<span class="cov0" title="0">authResult := make([]byte, 2)
|
|
|
if _, err := io.ReadFull(conn, authResult); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to read auth result: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if authResult[0] != 0x01 </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("invalid auth response version: %d", authResult[0])
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if authResult[1] != 0x00 </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("authentication failed")
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">logger.Debug("SOCKS5 authentication successful")
|
|
|
return nil</span>
|
|
|
}
|
|
|
|
|
|
// getSOCKS5ErrorMessage 获取SOCKS5错误消息
|
|
|
func getSOCKS5ErrorMessage(code byte) string <span class="cov7" title="9">{
|
|
|
switch code </span>{
|
|
|
case 0x01:<span class="cov1" title="1">
|
|
|
return "general SOCKS server failure"</span>
|
|
|
case 0x02:<span class="cov1" title="1">
|
|
|
return "connection not allowed by ruleset"</span>
|
|
|
case 0x03:<span class="cov1" title="1">
|
|
|
return "network unreachable"</span>
|
|
|
case 0x04:<span class="cov1" title="1">
|
|
|
return "host unreachable"</span>
|
|
|
case 0x05:<span class="cov1" title="1">
|
|
|
return "connection refused"</span>
|
|
|
case 0x06:<span class="cov1" title="1">
|
|
|
return "TTL expired"</span>
|
|
|
case 0x07:<span class="cov1" title="1">
|
|
|
return "command not supported"</span>
|
|
|
case 0x08:<span class="cov1" title="1">
|
|
|
return "address type not supported"</span>
|
|
|
default:<span class="cov1" title="1">
|
|
|
return "unknown error"</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// parsePort 解析端口号
|
|
|
func parsePort(portStr string) (int, error) <span class="cov10" title="17">{
|
|
|
if portStr == "" </span><span class="cov1" title="1">{
|
|
|
return 80, nil // 默认HTTP端口
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov9" title="16">port, err := strconv.Atoi(portStr)
|
|
|
if err != nil </span><span class="cov3" title="2">{
|
|
|
return 0, fmt.Errorf("invalid port format: %s", portStr)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov9" title="14">if port < 1 || port > 65535 </span><span class="cov5" title="4">{
|
|
|
return 0, fmt.Errorf("port out of range: %d", port)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov8" title="10">return port, nil</span>
|
|
|
}
|
|
|
</pre>
|
|
|
|
|
|
<pre class="file" id="file4" style="display: none">package proxy
|
|
|
|
|
|
import (
|
|
|
"sync"
|
|
|
"sync/atomic"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
// ProxyStats 代理统计信息
|
|
|
type ProxyStats struct {
|
|
|
StartTime time.Time
|
|
|
TotalConnections int64
|
|
|
ActiveConnections int64
|
|
|
SuccessfulRequests int64
|
|
|
FailedRequests int64
|
|
|
BytesSent int64
|
|
|
BytesReceived int64
|
|
|
SOCKS5Errors map[string]int64
|
|
|
mutex sync.RWMutex
|
|
|
}
|
|
|
|
|
|
// NewProxyStats 创建新的统计实例
|
|
|
func NewProxyStats() *ProxyStats <span class="cov4" title="14">{
|
|
|
return &ProxyStats{
|
|
|
StartTime: time.Now(),
|
|
|
SOCKS5Errors: make(map[string]int64),
|
|
|
}
|
|
|
}</span>
|
|
|
|
|
|
// IncrementConnections 增加连接计数
|
|
|
func (s *ProxyStats) IncrementConnections() <span class="cov9" title="1004">{
|
|
|
atomic.AddInt64(&s.TotalConnections, 1)
|
|
|
atomic.AddInt64(&s.ActiveConnections, 1)
|
|
|
}</span>
|
|
|
|
|
|
// DecrementActiveConnections 减少活跃连接计数
|
|
|
func (s *ProxyStats) DecrementActiveConnections() <span class="cov1" title="1">{
|
|
|
atomic.AddInt64(&s.ActiveConnections, -1)
|
|
|
}</span>
|
|
|
|
|
|
// IncrementSuccessfulRequests 增加成功请求计数
|
|
|
func (s *ProxyStats) IncrementSuccessfulRequests() <span class="cov10" title="1005">{
|
|
|
atomic.AddInt64(&s.SuccessfulRequests, 1)
|
|
|
}</span>
|
|
|
|
|
|
// IncrementFailedRequests 增加失败请求计数
|
|
|
func (s *ProxyStats) IncrementFailedRequests() <span class="cov1" title="2">{
|
|
|
atomic.AddInt64(&s.FailedRequests, 1)
|
|
|
}</span>
|
|
|
|
|
|
// AddBytesTransferred 添加传输字节数
|
|
|
func (s *ProxyStats) AddBytesTransferred(sent, received int64) <span class="cov9" title="1002">{
|
|
|
atomic.AddInt64(&s.BytesSent, sent)
|
|
|
atomic.AddInt64(&s.BytesReceived, received)
|
|
|
}</span>
|
|
|
|
|
|
// IncrementSOCKS5Error 增加SOCKS5错误计数
|
|
|
func (s *ProxyStats) IncrementSOCKS5Error(errorType string) <span class="cov9" title="1003">{
|
|
|
s.mutex.Lock()
|
|
|
defer s.mutex.Unlock()
|
|
|
s.SOCKS5Errors[errorType]++
|
|
|
}</span>
|
|
|
|
|
|
// GetStats 获取统计快照
|
|
|
func (s *ProxyStats) GetStats() ProxyStatsSnapshot <span class="cov3" title="9">{
|
|
|
s.mutex.RLock()
|
|
|
defer s.mutex.RUnlock()
|
|
|
|
|
|
errors := make(map[string]int64)
|
|
|
for k, v := range s.SOCKS5Errors </span><span class="cov2" title="3">{
|
|
|
errors[k] = v
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov3" title="9">return ProxyStatsSnapshot{
|
|
|
StartTime: s.StartTime,
|
|
|
Uptime: time.Since(s.StartTime),
|
|
|
TotalConnections: atomic.LoadInt64(&s.TotalConnections),
|
|
|
ActiveConnections: atomic.LoadInt64(&s.ActiveConnections),
|
|
|
SuccessfulRequests: atomic.LoadInt64(&s.SuccessfulRequests),
|
|
|
FailedRequests: atomic.LoadInt64(&s.FailedRequests),
|
|
|
BytesSent: atomic.LoadInt64(&s.BytesSent),
|
|
|
BytesReceived: atomic.LoadInt64(&s.BytesReceived),
|
|
|
SOCKS5Errors: errors,
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
// ProxyStatsSnapshot 统计快照
|
|
|
type ProxyStatsSnapshot struct {
|
|
|
StartTime time.Time `json:"start_time"`
|
|
|
Uptime time.Duration `json:"uptime"`
|
|
|
TotalConnections int64 `json:"total_connections"`
|
|
|
ActiveConnections int64 `json:"active_connections"`
|
|
|
SuccessfulRequests int64 `json:"successful_requests"`
|
|
|
FailedRequests int64 `json:"failed_requests"`
|
|
|
BytesSent int64 `json:"bytes_sent"`
|
|
|
BytesReceived int64 `json:"bytes_received"`
|
|
|
SOCKS5Errors map[string]int64 `json:"socks5_errors"`
|
|
|
}
|
|
|
|
|
|
// GetSuccessRate 获取成功率
|
|
|
func (s *ProxyStatsSnapshot) GetSuccessRate() float64 <span class="cov1" title="2">{
|
|
|
total := s.SuccessfulRequests + s.FailedRequests
|
|
|
if total == 0 </span><span class="cov1" title="1">{
|
|
|
return 0
|
|
|
}</span>
|
|
|
<span class="cov1" title="1">return float64(s.SuccessfulRequests) / float64(total) * 100</span>
|
|
|
}
|
|
|
|
|
|
// GetTotalBytes 获取总传输字节数
|
|
|
func (s *ProxyStatsSnapshot) GetTotalBytes() int64 <span class="cov1" title="1">{
|
|
|
return s.BytesSent + s.BytesReceived
|
|
|
}</span>
|
|
|
|
|
|
// GetAverageConnectionsPerHour 获取每小时平均连接数
|
|
|
func (s *ProxyStatsSnapshot) GetAverageConnectionsPerHour() float64 <span class="cov1" title="1">{
|
|
|
hours := s.Uptime.Hours()
|
|
|
if hours == 0 </span><span class="cov0" title="0">{
|
|
|
return 0
|
|
|
}</span>
|
|
|
<span class="cov1" title="1">return float64(s.TotalConnections) / hours</span>
|
|
|
}
|
|
|
</pre>
|
|
|
|
|
|
<pre class="file" id="file5" style="display: none">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 <span class="cov0" title="0">{
|
|
|
return &SystemProxyManager{
|
|
|
originalSettings: make(map[string]string),
|
|
|
}
|
|
|
}</span>
|
|
|
|
|
|
// SetGlobalProxy 设置全局代理
|
|
|
func (s *SystemProxyManager) SetGlobalProxy(httpProxy, httpsProxy, socksProxy string) error <span class="cov0" title="0">{
|
|
|
switch runtime.GOOS </span>{
|
|
|
case "darwin":<span class="cov0" title="0">
|
|
|
return s.setMacOSProxy(httpProxy, httpsProxy, socksProxy)</span>
|
|
|
case "windows":<span class="cov0" title="0">
|
|
|
return s.setWindowsProxy(httpProxy, httpsProxy, socksProxy)</span>
|
|
|
case "linux":<span class="cov0" title="0">
|
|
|
return s.setLinuxProxy(httpProxy, httpsProxy, socksProxy)</span>
|
|
|
default:<span class="cov0" title="0">
|
|
|
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// RestoreProxy 恢复原始代理设置
|
|
|
func (s *SystemProxyManager) RestoreProxy() error <span class="cov0" title="0">{
|
|
|
switch runtime.GOOS </span>{
|
|
|
case "darwin":<span class="cov0" title="0">
|
|
|
return s.restoreMacOSProxy()</span>
|
|
|
case "windows":<span class="cov0" title="0">
|
|
|
return s.restoreWindowsProxy()</span>
|
|
|
case "linux":<span class="cov0" title="0">
|
|
|
return s.restoreLinuxProxy()</span>
|
|
|
default:<span class="cov0" title="0">
|
|
|
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// macOS 代理设置
|
|
|
func (s *SystemProxyManager) setMacOSProxy(httpProxy, httpsProxy, socksProxy string) error <span class="cov0" title="0">{
|
|
|
logger.Info("Setting macOS system proxy...")
|
|
|
|
|
|
// 获取网络服务名称
|
|
|
networkService, err := s.getMacOSNetworkService()
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to get network service: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 保存原始设置
|
|
|
<span class="cov0" title="0">if err := s.saveMacOSOriginalSettings(networkService); err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Warn("Failed to save original proxy settings: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 设置HTTP代理
|
|
|
<span class="cov0" title="0">if httpProxy != "" </span><span class="cov0" title="0">{
|
|
|
if err := s.runCommand("networksetup", "-setwebproxy", networkService,
|
|
|
s.parseProxyHost(httpProxy), s.parseProxyPort(httpProxy)); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to set HTTP proxy: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if err := s.runCommand("networksetup", "-setwebproxystate", networkService, "on"); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to enable HTTP proxy: %v", err)
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
// 设置HTTPS代理
|
|
|
<span class="cov0" title="0">if httpsProxy != "" </span><span class="cov0" title="0">{
|
|
|
if err := s.runCommand("networksetup", "-setsecurewebproxy", networkService,
|
|
|
s.parseProxyHost(httpsProxy), s.parseProxyPort(httpsProxy)); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to set HTTPS proxy: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if err := s.runCommand("networksetup", "-setsecurewebproxystate", networkService, "on"); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to enable HTTPS proxy: %v", err)
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
// 设置SOCKS代理
|
|
|
<span class="cov0" title="0">if socksProxy != "" </span><span class="cov0" title="0">{
|
|
|
if err := s.runCommand("networksetup", "-setsocksfirewallproxy", networkService,
|
|
|
s.parseProxyHost(socksProxy), s.parseProxyPort(socksProxy)); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to set SOCKS proxy: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">if err := s.runCommand("networksetup", "-setsocksfirewallproxystate", networkService, "on"); err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to enable SOCKS proxy: %v", err)
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">logger.Info("macOS system proxy configured successfully")
|
|
|
return nil</span>
|
|
|
}
|
|
|
|
|
|
// 获取macOS网络服务名称
|
|
|
func (s *SystemProxyManager) getMacOSNetworkService() (string, error) <span class="cov0" title="0">{
|
|
|
output, err := exec.Command("networksetup", "-listallnetworkservices").Output()
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return "", err
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">lines := strings.Split(string(output), "\n")
|
|
|
for _, line := range lines </span><span class="cov0" title="0">{
|
|
|
line = strings.TrimSpace(line)
|
|
|
if line != "" && !strings.HasPrefix(line, "*") && !strings.Contains(line, "An asterisk") </span><span class="cov0" title="0">{
|
|
|
// 优先选择Wi-Fi,否则选择第一个可用的服务
|
|
|
if strings.Contains(strings.ToLower(line), "wi-fi") || strings.Contains(strings.ToLower(line), "wifi") </span><span class="cov0" title="0">{
|
|
|
return line, nil
|
|
|
}</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没找到Wi-Fi,返回第一个可用的服务
|
|
|
<span class="cov0" title="0">for _, line := range lines </span><span class="cov0" title="0">{
|
|
|
line = strings.TrimSpace(line)
|
|
|
if line != "" && !strings.HasPrefix(line, "*") && !strings.Contains(line, "An asterisk") </span><span class="cov0" title="0">{
|
|
|
return line, nil
|
|
|
}</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">return "", fmt.Errorf("no network service found")</span>
|
|
|
}
|
|
|
|
|
|
// 保存macOS原始代理设置
|
|
|
func (s *SystemProxyManager) saveMacOSOriginalSettings(networkService string) error <span class="cov0" title="0">{
|
|
|
// 保存HTTP代理状态
|
|
|
if output, err := exec.Command("networksetup", "-getwebproxy", networkService).Output(); err == nil </span><span class="cov0" title="0">{
|
|
|
s.originalSettings["http_proxy"] = string(output)
|
|
|
}</span>
|
|
|
|
|
|
// 保存HTTPS代理状态
|
|
|
<span class="cov0" title="0">if output, err := exec.Command("networksetup", "-getsecurewebproxy", networkService).Output(); err == nil </span><span class="cov0" title="0">{
|
|
|
s.originalSettings["https_proxy"] = string(output)
|
|
|
}</span>
|
|
|
|
|
|
// 保存SOCKS代理状态
|
|
|
<span class="cov0" title="0">if output, err := exec.Command("networksetup", "-getsocksfirewallproxy", networkService).Output(); err == nil </span><span class="cov0" title="0">{
|
|
|
s.originalSettings["socks_proxy"] = string(output)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">s.originalSettings["network_service"] = networkService
|
|
|
return nil</span>
|
|
|
}
|
|
|
|
|
|
// 恢复macOS代理设置
|
|
|
func (s *SystemProxyManager) restoreMacOSProxy() error <span class="cov0" title="0">{
|
|
|
networkService, exists := s.originalSettings["network_service"]
|
|
|
if !exists </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("no network service information saved")
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">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</span>
|
|
|
}
|
|
|
|
|
|
// Windows 代理设置(简化实现)
|
|
|
func (s *SystemProxyManager) setWindowsProxy(httpProxy, httpsProxy, socksProxy string) error <span class="cov0" title="0">{
|
|
|
logger.Warn("Windows proxy setting not fully implemented")
|
|
|
return fmt.Errorf("Windows proxy setting not implemented yet")
|
|
|
}</span>
|
|
|
|
|
|
func (s *SystemProxyManager) restoreWindowsProxy() error <span class="cov0" title="0">{
|
|
|
logger.Warn("Windows proxy restoration not fully implemented")
|
|
|
return nil
|
|
|
}</span>
|
|
|
|
|
|
// Linux 代理设置(简化实现)
|
|
|
func (s *SystemProxyManager) setLinuxProxy(httpProxy, httpsProxy, socksProxy string) error <span class="cov0" title="0">{
|
|
|
logger.Warn("Linux proxy setting not fully implemented")
|
|
|
return fmt.Errorf("Linux proxy setting not implemented yet")
|
|
|
}</span>
|
|
|
|
|
|
func (s *SystemProxyManager) restoreLinuxProxy() error <span class="cov0" title="0">{
|
|
|
logger.Warn("Linux proxy restoration not fully implemented")
|
|
|
return nil
|
|
|
}</span>
|
|
|
|
|
|
// 辅助函数
|
|
|
func (s *SystemProxyManager) runCommand(name string, args ...string) error <span class="cov0" title="0">{
|
|
|
cmd := exec.Command(name, args...)
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Error("Command failed: %s %v, output: %s", name, args, string(output))
|
|
|
return err
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">return nil</span>
|
|
|
}
|
|
|
|
|
|
func (s *SystemProxyManager) parseProxyHost(proxy string) string <span class="cov0" title="0">{
|
|
|
// 简单解析,格式: host:port
|
|
|
parts := strings.Split(proxy, ":")
|
|
|
if len(parts) >= 1 </span><span class="cov0" title="0">{
|
|
|
return parts[0]
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">return proxy</span>
|
|
|
}
|
|
|
|
|
|
func (s *SystemProxyManager) parseProxyPort(proxy string) string <span class="cov0" title="0">{
|
|
|
// 简单解析,格式: host:port
|
|
|
parts := strings.Split(proxy, ":")
|
|
|
if len(parts) >= 2 </span><span class="cov0" title="0">{
|
|
|
return parts[1]
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">return "8080"</span> // 默认端口
|
|
|
}
|
|
|
</pre>
|
|
|
|
|
|
<pre class="file" id="file6" style="display: none">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 <span class="cov0" title="0">{
|
|
|
return &DNSProxy{
|
|
|
upstreamDNS: upstreamDNS,
|
|
|
localPort: localPort,
|
|
|
cache: &dnsCache{
|
|
|
entries: make(map[string]*cacheEntry),
|
|
|
},
|
|
|
}
|
|
|
}</span>
|
|
|
|
|
|
// Start 启动DNS代理服务器
|
|
|
func (d *DNSProxy) Start() error <span class="cov0" title="0">{
|
|
|
d.mutex.Lock()
|
|
|
defer d.mutex.Unlock()
|
|
|
|
|
|
if d.running </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("DNS proxy is already running")
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", d.localPort))
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to resolve UDP address: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">d.server, err = net.ListenUDP("udp", addr)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return fmt.Errorf("failed to start DNS proxy server: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">d.running = true
|
|
|
logger.Info("DNS proxy started on port %d", d.localPort)
|
|
|
|
|
|
// 启动清理缓存的goroutine
|
|
|
go d.cleanupCache()
|
|
|
|
|
|
// 处理DNS请求
|
|
|
go d.handleRequests()
|
|
|
|
|
|
return nil</span>
|
|
|
}
|
|
|
|
|
|
// Stop 停止DNS代理服务器
|
|
|
func (d *DNSProxy) Stop() error <span class="cov0" title="0">{
|
|
|
d.mutex.Lock()
|
|
|
defer d.mutex.Unlock()
|
|
|
|
|
|
if !d.running </span><span class="cov0" title="0">{
|
|
|
return nil
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">d.running = false
|
|
|
if d.server != nil </span><span class="cov0" title="0">{
|
|
|
d.server.Close()
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">logger.Info("DNS proxy stopped")
|
|
|
return nil</span>
|
|
|
}
|
|
|
|
|
|
// handleRequests 处理DNS请求
|
|
|
func (d *DNSProxy) handleRequests() <span class="cov0" title="0">{
|
|
|
buffer := make([]byte, 512) // DNS消息最大512字节(UDP)
|
|
|
|
|
|
for d.isRunning() </span><span class="cov0" title="0">{
|
|
|
n, clientAddr, err := d.server.ReadFromUDP(buffer)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
if d.isRunning() </span><span class="cov0" title="0">{
|
|
|
logger.Error("Failed to read DNS request: %v", err)
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">continue</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">go d.processRequest(buffer[:n], clientAddr)</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// processRequest 处理单个DNS请求
|
|
|
func (d *DNSProxy) processRequest(request []byte, clientAddr *net.UDPAddr) <span class="cov0" title="0">{
|
|
|
// 简单的DNS请求解析
|
|
|
domain := d.extractDomain(request)
|
|
|
logger.Debug("DNS request for domain: %s", domain)
|
|
|
|
|
|
// 检查缓存
|
|
|
if cachedResponse := d.getFromCache(domain); cachedResponse != nil </span><span class="cov0" title="0">{
|
|
|
logger.Debug("Serving %s from cache", domain)
|
|
|
d.server.WriteToUDP(cachedResponse, clientAddr)
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
// 转发到上游DNS服务器
|
|
|
<span class="cov0" title="0">response, err := d.forwardToUpstream(request)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
logger.Error("Failed to forward DNS request: %v", err)
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
// 缓存响应
|
|
|
<span class="cov0" title="0">d.addToCache(domain, response)
|
|
|
|
|
|
// 返回响应给客户端
|
|
|
d.server.WriteToUDP(response, clientAddr)</span>
|
|
|
}
|
|
|
|
|
|
// extractDomain 从DNS请求中提取域名(简化实现)
|
|
|
func (d *DNSProxy) extractDomain(request []byte) string <span class="cov0" title="0">{
|
|
|
if len(request) < 12 </span><span class="cov0" title="0">{
|
|
|
return ""
|
|
|
}</span>
|
|
|
|
|
|
// 跳过DNS头部(12字节)
|
|
|
<span class="cov0" title="0">offset := 12
|
|
|
var domain strings.Builder
|
|
|
|
|
|
for offset < len(request) </span><span class="cov0" title="0">{
|
|
|
length := int(request[offset])
|
|
|
if length == 0 </span><span class="cov0" title="0">{
|
|
|
break</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">offset++
|
|
|
if offset+length > len(request) </span><span class="cov0" title="0">{
|
|
|
break</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">if domain.Len() > 0 </span><span class="cov0" title="0">{
|
|
|
domain.WriteByte('.')
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">domain.Write(request[offset : offset+length])
|
|
|
offset += length</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">return domain.String()</span>
|
|
|
}
|
|
|
|
|
|
// forwardToUpstream 转发DNS请求到上游服务器
|
|
|
func (d *DNSProxy) forwardToUpstream(request []byte) ([]byte, error) <span class="cov0" title="0">{
|
|
|
conn, err := net.Dial("udp", d.upstreamDNS)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return nil, fmt.Errorf("failed to connect to upstream DNS: %v", err)
|
|
|
}</span>
|
|
|
<span class="cov0" title="0">defer conn.Close()
|
|
|
|
|
|
// 设置超时
|
|
|
conn.SetDeadline(time.Now().Add(5 * time.Second))
|
|
|
|
|
|
// 发送请求
|
|
|
if _, err := conn.Write(request); err != nil </span><span class="cov0" title="0">{
|
|
|
return nil, fmt.Errorf("failed to send DNS request: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
// 读取响应
|
|
|
<span class="cov0" title="0">response := make([]byte, 512)
|
|
|
n, err := conn.Read(response)
|
|
|
if err != nil </span><span class="cov0" title="0">{
|
|
|
return nil, fmt.Errorf("failed to read DNS response: %v", err)
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">return response[:n], nil</span>
|
|
|
}
|
|
|
|
|
|
// getFromCache 从缓存获取DNS响应
|
|
|
func (d *DNSProxy) getFromCache(domain string) []byte <span class="cov0" title="0">{
|
|
|
d.cache.mutex.RLock()
|
|
|
defer d.cache.mutex.RUnlock()
|
|
|
|
|
|
entry, exists := d.cache.entries[domain]
|
|
|
if !exists || time.Now().After(entry.expiry) </span><span class="cov0" title="0">{
|
|
|
return nil
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">return entry.response</span>
|
|
|
}
|
|
|
|
|
|
// addToCache 添加DNS响应到缓存
|
|
|
func (d *DNSProxy) addToCache(domain string, response []byte) <span class="cov0" title="0">{
|
|
|
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)
|
|
|
}</span>
|
|
|
|
|
|
// cleanupCache 清理过期的缓存条目
|
|
|
func (d *DNSProxy) cleanupCache() <span class="cov0" title="0">{
|
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
for </span><span class="cov0" title="0">{
|
|
|
select </span>{
|
|
|
case <-ticker.C:<span class="cov0" title="0">
|
|
|
if !d.isRunning() </span><span class="cov0" title="0">{
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">d.cache.mutex.Lock()
|
|
|
now := time.Now()
|
|
|
for domain, entry := range d.cache.entries </span><span class="cov0" title="0">{
|
|
|
if now.After(entry.expiry) </span><span class="cov0" title="0">{
|
|
|
delete(d.cache.entries, domain)
|
|
|
}</span>
|
|
|
}
|
|
|
<span class="cov0" title="0">d.cache.mutex.Unlock()</span>
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// isRunning 检查DNS代理是否在运行
|
|
|
func (d *DNSProxy) isRunning() bool <span class="cov0" title="0">{
|
|
|
d.mutex.RLock()
|
|
|
defer d.mutex.RUnlock()
|
|
|
return d.running
|
|
|
}</span>
|
|
|
</pre>
|
|
|
|
|
|
<pre class="file" id="file7" style="display: none">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() <span class="cov0" title="0">{
|
|
|
defaultLogger = New("INFO")
|
|
|
}</span>
|
|
|
|
|
|
// New 创建新的日志记录器
|
|
|
func New(levelStr string) *Logger <span class="cov0" title="0">{
|
|
|
level := parseLogLevel(levelStr)
|
|
|
logger := log.New(os.Stdout, "", 0)
|
|
|
|
|
|
return &Logger{
|
|
|
level: level,
|
|
|
logger: logger,
|
|
|
}
|
|
|
}</span>
|
|
|
|
|
|
// SetLevel 设置日志级别
|
|
|
func SetLevel(levelStr string) <span class="cov0" title="0">{
|
|
|
defaultLogger.level = parseLogLevel(levelStr)
|
|
|
}</span>
|
|
|
|
|
|
func parseLogLevel(levelStr string) LogLevel <span class="cov0" title="0">{
|
|
|
switch strings.ToUpper(levelStr) </span>{
|
|
|
case "DEBUG":<span class="cov0" title="0">
|
|
|
return DEBUG</span>
|
|
|
case "INFO":<span class="cov0" title="0">
|
|
|
return INFO</span>
|
|
|
case "WARN", "WARNING":<span class="cov0" title="0">
|
|
|
return WARN</span>
|
|
|
case "ERROR":<span class="cov0" title="0">
|
|
|
return ERROR</span>
|
|
|
default:<span class="cov0" title="0">
|
|
|
return INFO</span>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func (l *Logger) log(level LogLevel, format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
if level < l.level </span><span class="cov0" title="0">{
|
|
|
return
|
|
|
}</span>
|
|
|
|
|
|
<span class="cov0" title="0">levelStr := ""
|
|
|
switch level </span>{
|
|
|
case DEBUG:<span class="cov0" title="0">
|
|
|
levelStr = "DEBUG"</span>
|
|
|
case INFO:<span class="cov0" title="0">
|
|
|
levelStr = "INFO"</span>
|
|
|
case WARN:<span class="cov0" title="0">
|
|
|
levelStr = "WARN"</span>
|
|
|
case ERROR:<span class="cov0" title="0">
|
|
|
levelStr = "ERROR"</span>
|
|
|
}
|
|
|
|
|
|
<span class="cov0" title="0">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)</span>
|
|
|
}
|
|
|
|
|
|
// Debug 调试日志
|
|
|
func (l *Logger) Debug(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
l.log(DEBUG, format, args...)
|
|
|
}</span>
|
|
|
|
|
|
// Info 信息日志
|
|
|
func (l *Logger) Info(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
l.log(INFO, format, args...)
|
|
|
}</span>
|
|
|
|
|
|
// Warn 警告日志
|
|
|
func (l *Logger) Warn(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
l.log(WARN, format, args...)
|
|
|
}</span>
|
|
|
|
|
|
// Error 错误日志
|
|
|
func (l *Logger) Error(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
l.log(ERROR, format, args...)
|
|
|
}</span>
|
|
|
|
|
|
// 全局日志函数
|
|
|
func Debug(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
defaultLogger.Debug(format, args...)
|
|
|
}</span>
|
|
|
|
|
|
func Info(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
defaultLogger.Info(format, args...)
|
|
|
}</span>
|
|
|
|
|
|
func Warn(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
defaultLogger.Warn(format, args...)
|
|
|
}</span>
|
|
|
|
|
|
func Error(format string, args ...interface{}) <span class="cov0" title="0">{
|
|
|
defaultLogger.Error(format, args...)
|
|
|
}</span>
|
|
|
</pre>
|
|
|
|
|
|
</div>
|
|
|
</body>
|
|
|
<script>
|
|
|
(function() {
|
|
|
var files = document.getElementById('files');
|
|
|
var visible;
|
|
|
files.addEventListener('change', onChange, false);
|
|
|
function select(part) {
|
|
|
if (visible)
|
|
|
visible.style.display = 'none';
|
|
|
visible = document.getElementById(part);
|
|
|
if (!visible)
|
|
|
return;
|
|
|
files.value = part;
|
|
|
visible.style.display = 'block';
|
|
|
location.hash = part;
|
|
|
}
|
|
|
function onChange() {
|
|
|
select(files.value);
|
|
|
window.scrollTo(0, 0);
|
|
|
}
|
|
|
if (location.hash != "") {
|
|
|
select(location.hash.substr(1));
|
|
|
}
|
|
|
if (!visible) {
|
|
|
select("file0");
|
|
|
}
|
|
|
})();
|
|
|
</script>
|
|
|
</html>
|
|
|
|