302 lines
8.1 KiB
Go
302 lines
8.1 KiB
Go
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])
|
||
}
|