package wireguard import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "time" "vpn-client/internal/config" "vpn-client/internal/logger" ) // Connect подключается к WireGuard серверу func Connect(configName string, logsDir string) error { // Загружаем конфигурации configs, err := config.LoadConfigs() if err != nil { return fmt.Errorf("ошибка загрузки конфигураций: %w", err) } // Ищем конфиг var wgConfig *config.WireGuardConfig for i := range configs.WireGuard { if configs.WireGuard[i].Name == configName { wgConfig = &configs.WireGuard[i] break } } if wgConfig == nil { return fmt.Errorf("конфиг '%s' не найден", configName) } // Создаем файл конфига interfaceName := strings.ReplaceAll(configName, " ", "_") configPath := filepath.Join(config.ConfigDir, interfaceName+".conf") if err := os.WriteFile(configPath, []byte(wgConfig.Config), 0600); err != nil { return fmt.Errorf("ошибка создания файла конфига: %w", err) } fmt.Printf("Подключение к WireGuard '%s'...\n", configName) logFile := logger.GetLogPath(logsDir, "wireguard") logger.LogMessage(logFile, fmt.Sprintf("Начало подключения к '%s'", configName)) var cmd *exec.Cmd if runtime.GOOS == "windows" { // Для Windows используем wireguard.exe wgPaths := []string{ `C:\Program Files\WireGuard\wireguard.exe`, `C:\Program Files (x86)\WireGuard\wireguard.exe`, } var wgExe string for _, path := range wgPaths { if _, err := os.Stat(path); err == nil { wgExe = path break } } if wgExe == "" { return fmt.Errorf("WireGuard не найден. Установите WireGuard для Windows\nСкачать: https://www.wireguard.com/install/") } // Импортируем туннель cmd = exec.Command(wgExe, "/installtunnelservice", configPath) output, err := cmd.CombinedOutput() if err != nil { logger.LogMessage(logFile, fmt.Sprintf("Ошибка подключения к '%s': %s", configName, string(output))) return fmt.Errorf("ошибка подключения: %s", string(output)) } // Сохраняем состояние state := &config.ConnectionState{ Connected: true, ConfigName: configName, ConfigType: "wireguard", StartTime: time.Now().Format(time.RFC3339), Interface: interfaceName, ProcessPID: 0, // WireGuard на Windows работает как служба LogFile: "", } if err := config.SaveState(state); err != nil { return fmt.Errorf("ошибка сохранения состояния: %w", err) } logger.LogMessage(logFile, fmt.Sprintf("Успешно подключено к '%s'", configName)) fmt.Printf("✓ Подключено к '%s'\n", configName) } else { // Для Linux/Mac используем wg-quick cmd = exec.Command("sudo", "wg-quick", "up", configPath) output, err := cmd.CombinedOutput() if err != nil { logger.LogMessage(logFile, fmt.Sprintf("Ошибка подключения к '%s': %s", configName, string(output))) return fmt.Errorf("ошибка подключения: %s", string(output)) } // Сохраняем состояние state := &config.ConnectionState{ Connected: true, ConfigName: configName, ConfigType: "wireguard", StartTime: time.Now().Format(time.RFC3339), Interface: interfaceName, ProcessPID: 0, LogFile: "", } if err := config.SaveState(state); err != nil { return fmt.Errorf("ошибка сохранения состояния: %w", err) } logger.LogMessage(logFile, fmt.Sprintf("Успешно подключено к '%s'", configName)) fmt.Printf("✓ Подключено к '%s'\n", configName) } return nil } // Disconnect отключается от WireGuard func Disconnect(interfaceName, logsDir string) error { logFile := logger.GetLogPath(logsDir, "wireguard") logger.LogMessage(logFile, fmt.Sprintf("Начало отключения от '%s'", interfaceName)) var cmd *exec.Cmd if runtime.GOOS == "windows" { wgPaths := []string{ `C:\Program Files\WireGuard\wireguard.exe`, `C:\Program Files (x86)\WireGuard\wireguard.exe`, } var wgExe string for _, path := range wgPaths { if _, err := os.Stat(path); err == nil { wgExe = path break } } if wgExe != "" { cmd = exec.Command(wgExe, "/uninstalltunnelservice", interfaceName) cmd.Run() } } else { configPath := filepath.Join(config.ConfigDir, interfaceName+".conf") cmd = exec.Command("sudo", "wg-quick", "down", configPath) cmd.Run() } logger.LogMessage(logFile, fmt.Sprintf("Отключено от '%s'", interfaceName)) return nil } // GetStats получает статистику WireGuard func GetStats(interfaceName string) (map[string]string, error) { stats := map[string]string{ "rx": "N/A", "tx": "N/A", } if runtime.GOOS == "windows" { // Для Windows пытаемся получить статистику через wg.exe wgPaths := []string{ `C:\Program Files\WireGuard\wg.exe`, `C:\Program Files (x86)\WireGuard\wg.exe`, } var wgExe string for _, path := range wgPaths { if _, err := os.Stat(path); err == nil { wgExe = path break } } if wgExe != "" { cmd := exec.Command(wgExe, "show", interfaceName, "transfer") output, err := cmd.Output() if err == nil && len(output) > 0 { lines := strings.Split(strings.TrimSpace(string(output)), "\n") for _, line := range lines { parts := strings.Fields(line) if len(parts) >= 3 { rx := parseBytes(parts[1]) tx := parseBytes(parts[2]) stats["rx"] = formatBytes(rx) stats["tx"] = formatBytes(tx) break } } } } } else { // Для Linux/Mac cmd := exec.Command("wg", "show", interfaceName, "transfer") output, err := cmd.Output() if err == nil && len(output) > 0 { lines := strings.Split(strings.TrimSpace(string(output)), "\n") if len(lines) > 0 { parts := strings.Fields(lines[0]) if len(parts) >= 2 { rx := parseBytes(parts[0]) tx := parseBytes(parts[1]) stats["rx"] = formatBytes(rx) stats["tx"] = formatBytes(tx) } } } } return stats, nil } // AddConfig добавляет WireGuard конфигурацию func AddConfig(name, configText string) error { configs, err := config.LoadConfigs() if err != nil { return fmt.Errorf("ошибка загрузки конфигураций: %w", err) } configs.WireGuard = append(configs.WireGuard, config.WireGuardConfig{ Name: name, Config: configText, }) if err := config.SaveConfigs(configs); err != nil { return fmt.Errorf("ошибка сохранения конфигураций: %w", err) } return nil } // AddConfigFromFile добавляет WireGuard конфигурацию из файла func AddConfigFromFile(name, filePath string) error { data, err := os.ReadFile(filePath) if err != nil { return fmt.Errorf("ошибка чтения файла: %w", err) } return AddConfig(name, string(data)) } // DeleteConfig удаляет WireGuard конфигурацию func DeleteConfig(name string) error { configs, err := config.LoadConfigs() if err != nil { return fmt.Errorf("ошибка загрузки конфигураций: %w", err) } var filtered []config.WireGuardConfig found := false for _, cfg := range configs.WireGuard { if cfg.Name != name { filtered = append(filtered, cfg) } else { found = true } } if !found { return fmt.Errorf("конфиг '%s' не найден", name) } configs.WireGuard = filtered if err := config.SaveConfigs(configs); err != nil { return fmt.Errorf("ошибка сохранения конфигураций: %w", err) } return nil } func parseBytes(s string) int64 { var val int64 fmt.Sscanf(s, "%d", &val) return val } func formatBytes(bytes int64) string { const unit = 1024 if bytes < unit { return fmt.Sprintf("%d B", bytes) } div, exp := int64(unit), 0 for n := bytes / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) }