Complete wormhole client implementation with SOCKS5 proxy, HTTP proxy mode, global proxy mode, DNS proxy, system integration, configuration management, logging, documentation and tests
parent
a5b72efcf3
commit
ea09a07490
Binary file not shown.
@ -1,3 +1,5 @@ |
||||
module github.com/azoic/wormhole-client |
||||
|
||||
go 1.21 |
||||
|
||||
require gopkg.in/yaml.v3 v3.0.1 |
||||
|
@ -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= |
@ -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") |
||||
} |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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" // 默认端口
|
||||
} |
@ -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 |
||||
} |
@ -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...) |
||||
} |
Loading…
Reference in new issue