package vless import ( "encoding/json" "fmt" "net" "net/url" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "syscall" "time" "vpn-client/internal/config" "vpn-client/internal/logger" "vpn-client/internal/proxy" "vpn-client/internal/security" ) // XrayConfig представляет конфигурацию Xray type XrayConfig struct { Log LogConfig `json:"log"` Inbounds []InboundConfig `json:"inbounds"` Outbounds []OutboundConfig `json:"outbounds"` Stats interface{} `json:"stats"` Policy PolicyConfig `json:"policy"` } type LogConfig struct { Loglevel string `json:"loglevel"` Access string `json:"access"` Error string `json:"error"` DNSLog bool `json:"dnsLog"` } type InboundConfig struct { Port int `json:"port"` Protocol string `json:"protocol"` Settings InboundSettings `json:"settings"` Sniffing SniffingConfig `json:"sniffing"` Tag string `json:"tag"` } type InboundSettings struct { UDP bool `json:"udp"` Auth string `json:"auth"` } type SniffingConfig struct { Enabled bool `json:"enabled"` DestOverride []string `json:"destOverride"` MetadataOnly bool `json:"metadataOnly"` } type OutboundConfig struct { Protocol string `json:"protocol"` Tag string `json:"tag"` Settings OutboundSettings `json:"settings"` StreamSettings StreamSettings `json:"streamSettings"` } type OutboundSettings struct { Vnext []VnextConfig `json:"vnext"` } type VnextConfig struct { Address string `json:"address"` Port int `json:"port"` Users []UserConfig `json:"users"` } type UserConfig struct { ID string `json:"id"` Encryption string `json:"encryption"` Flow string `json:"flow,omitempty"` } type StreamSettings struct { Network string `json:"network"` Security string `json:"security,omitempty"` TLSSettings *TLSSettings `json:"tlsSettings,omitempty"` RealitySettings *RealitySettings `json:"realitySettings,omitempty"` WSSettings *WSSettings `json:"wsSettings,omitempty"` GRPCSettings *GRPCSettings `json:"grpcSettings,omitempty"` HTTPSettings *HTTPSettings `json:"httpSettings,omitempty"` } type TLSSettings struct { ServerName string `json:"serverName"` AllowInsecure bool `json:"allowInsecure"` Fingerprint string `json:"fingerprint,omitempty"` } type RealitySettings struct { ServerName string `json:"serverName"` Fingerprint string `json:"fingerprint"` Show bool `json:"show"` PublicKey string `json:"publicKey,omitempty"` ShortID string `json:"shortId,omitempty"` SpiderX string `json:"spiderX,omitempty"` } type WSSettings struct { Path string `json:"path"` Headers map[string]string `json:"headers"` } type GRPCSettings struct { ServiceName string `json:"serviceName"` MultiMode bool `json:"multiMode"` } type HTTPSettings struct { Path []string `json:"path"` Host []string `json:"host"` } type PolicyConfig struct { Levels map[string]LevelPolicy `json:"levels"` System SystemPolicy `json:"system"` } type LevelPolicy struct { StatsUserUplink bool `json:"statsUserUplink"` StatsUserDownlink bool `json:"statsUserDownlink"` } type SystemPolicy struct { StatsInboundUplink bool `json:"statsInboundUplink"` StatsInboundDownlink bool `json:"statsInboundDownlink"` StatsOutboundUplink bool `json:"statsOutboundUplink"` StatsOutboundDownlink bool `json:"statsOutboundDownlink"` } // ParseVLESSURL парсит VLESS URL и создает конфигурацию Xray func ParseVLESSURL(vlessURL, logsDir string) (*XrayConfig, error) { // Убираем префикс vless:// urlStr := strings.TrimPrefix(vlessURL, "vless://") // Разделяем на части var name string if idx := strings.Index(urlStr, "#"); idx != -1 { name = urlStr[idx+1:] urlStr = urlStr[:idx] } // Парсим параметры var paramsStr string var connection string if idx := strings.Index(urlStr, "?"); idx != -1 { connection = urlStr[:idx] paramsStr = urlStr[idx+1:] } else { connection = urlStr } // Парсим connection (uuid@server:port) parts := strings.Split(connection, "@") if len(parts) != 2 { return nil, fmt.Errorf("неверный формат VLESS URL") } uuid := parts[0] serverPort := parts[1] // Парсим server:port var server string var port int if strings.Contains(serverPort, "[") { // IPv6 endIdx := strings.Index(serverPort, "]") server = serverPort[1:endIdx] portStr := strings.TrimPrefix(serverPort[endIdx+1:], ":") port, _ = strconv.Atoi(portStr) } else { lastColon := strings.LastIndex(serverPort, ":") server = serverPort[:lastColon] portStr := serverPort[lastColon+1:] port, _ = strconv.Atoi(portStr) } if port == 0 { port = 443 } // Парсим параметры params := make(map[string]string) if paramsStr != "" { for _, param := range strings.Split(paramsStr, "&") { kv := strings.SplitN(param, "=", 2) if len(kv) == 2 { key, _ := url.QueryUnescape(kv[0]) value, _ := url.QueryUnescape(kv[1]) params[key] = value } } } // Создаем базовую конфигурацию cfg := &XrayConfig{ Log: LogConfig{ Loglevel: "debug", Access: filepath.Join(logsDir, "vless_access.log"), Error: filepath.Join(logsDir, "vless_error.log"), DNSLog: true, }, Inbounds: []InboundConfig{ { Port: 10808, Protocol: "socks", Settings: InboundSettings{ UDP: true, Auth: "noauth", }, Sniffing: SniffingConfig{ Enabled: true, DestOverride: []string{"http", "tls"}, MetadataOnly: false, }, Tag: "socks-in", }, }, Outbounds: []OutboundConfig{ { Protocol: "vless", Tag: "proxy", Settings: OutboundSettings{ Vnext: []VnextConfig{ { Address: server, Port: port, Users: []UserConfig{ { ID: uuid, Encryption: getParam(params, "encryption", "none"), Flow: params["flow"], }, }, }, }, }, StreamSettings: StreamSettings{ Network: getParam(params, "type", "tcp"), }, }, }, Stats: struct{}{}, Policy: PolicyConfig{ Levels: map[string]LevelPolicy{ "0": { StatsUserUplink: true, StatsUserDownlink: true, }, }, System: SystemPolicy{ StatsInboundUplink: true, StatsInboundDownlink: true, StatsOutboundUplink: true, StatsOutboundDownlink: true, }, }, } // Удаляем пустой flow if cfg.Outbounds[0].Settings.Vnext[0].Users[0].Flow == "" { cfg.Outbounds[0].Settings.Vnext[0].Users[0].Flow = "" } // Настройки безопасности security := params["security"] if security == "tls" { cfg.Outbounds[0].StreamSettings.Security = "tls" cfg.Outbounds[0].StreamSettings.TLSSettings = &TLSSettings{ ServerName: getParam(params, "sni", server), AllowInsecure: params["allowInsecure"] == "1", Fingerprint: params["fp"], } } else if security == "reality" { cfg.Outbounds[0].StreamSettings.Security = "reality" cfg.Outbounds[0].StreamSettings.RealitySettings = &RealitySettings{ ServerName: getParam(params, "sni", server), Fingerprint: getParam(params, "fp", "chrome"), Show: false, PublicKey: params["pbk"], ShortID: params["sid"], SpiderX: params["spx"], } } // Настройки транспорта network := getParam(params, "type", "tcp") if network == "ws" { headers := make(map[string]string) if host := params["host"]; host != "" { headers["Host"] = host } cfg.Outbounds[0].StreamSettings.WSSettings = &WSSettings{ Path: getParam(params, "path", "/"), Headers: headers, } } else if network == "grpc" { cfg.Outbounds[0].StreamSettings.GRPCSettings = &GRPCSettings{ ServiceName: getParam(params, "serviceName", params["path"]), MultiMode: params["mode"] == "multi", } } else if network == "h2" || network == "http" { cfg.Outbounds[0].StreamSettings.Network = "http" cfg.Outbounds[0].StreamSettings.HTTPSettings = &HTTPSettings{ Path: []string{getParam(params, "path", "/")}, Host: []string{getParam(params, "host", server)}, } } // Логируем информацию logFile := logger.GetLogPath(logsDir, "vless") logger.LogMessage(logFile, fmt.Sprintf("Создание конфига для сервера: %s:%d", server, port)) logger.LogMessage(logFile, fmt.Sprintf("UUID: %s", uuid)) logger.LogMessage(logFile, fmt.Sprintf("Транспорт: %s", network)) logger.LogMessage(logFile, fmt.Sprintf("Безопасность: %s", security)) if name != "" { logger.LogMessage(logFile, fmt.Sprintf("Имя: %s", name)) } return cfg, nil } func getParam(params map[string]string, key, defaultValue string) string { if val, ok := params[key]; ok && val != "" { return val } return defaultValue } // Connect подключается к VLESS серверу func Connect(configName string, logsDir string) error { // Загружаем настройки settings, err := config.LoadSettings() if err != nil { return fmt.Errorf("ошибка загрузки настроек: %w", err) } // Определяем директорию и имя исполняемого файла var coreDir, coreExe, coreName string if settings.CoreType == "v2ray" { coreDir = config.V2RayDir coreName = "V2Ray" if runtime.GOOS == "windows" { coreExe = "v2ray.exe" } else { coreExe = "v2ray" } } else { coreDir = config.XrayDir coreName = "Xray" if runtime.GOOS == "windows" { coreExe = "xray.exe" } else { coreExe = "xray" } } corePath := filepath.Join(coreDir, coreExe) // Загружаем конфигурации configs, err := config.LoadConfigs() if err != nil { return fmt.Errorf("ошибка загрузки конфигураций: %w", err) } // Ищем конфиг var vlessConfig *config.VLESSConfig for i := range configs.VLESS { if configs.VLESS[i].Name == configName { vlessConfig = &configs.VLESS[i] break } } if vlessConfig == nil { return fmt.Errorf("конфиг '%s' не найден", configName) } // Создаем конфигурацию coreConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir) if err != nil { return fmt.Errorf("ошибка парсинга VLESS URL: %w", err) } // Сохраняем конфигурацию configPath := filepath.Join(config.ConfigDir, "xray_config.json") data, err := json.MarshalIndent(coreConfig, "", " ") if err != nil { return fmt.Errorf("ошибка сериализации конфига: %w", err) } if err := os.WriteFile(configPath, data, 0644); err != nil { return fmt.Errorf("ошибка сохранения конфига: %w", err) } // Проверяем наличие исполняемого файла if _, err := os.Stat(corePath); os.IsNotExist(err) { return fmt.Errorf("%s не найден в %s\n\nПожалуйста, перезапустите приложение для автоматической загрузки %s", coreName, coreDir, coreName) } // Создаем лог-файл трафика trafficLog := logger.GetTrafficLogPath(logsDir) logFile, err := os.Create(trafficLog) if err != nil { return fmt.Errorf("ошибка создания лог-файла: %w", err) } // Записываем заголовок fmt.Fprintf(logFile, "=== VPN Подключение начато: %s ===\n", time.Now().Format("2006-01-02 15:04:05")) fmt.Fprintf(logFile, "Конфиг: %s\n", configName) fmt.Fprintf(logFile, "Клиент: %s\n", coreName) fmt.Fprintf(logFile, "Прокси: 127.0.0.1:10808\n") fmt.Fprintf(logFile, "%s\n\n", strings.Repeat("=", 60)) // Запускаем ядро cmd := exec.Command(corePath, "run", "-c", configPath) cmd.Stdout = logFile cmd.Stderr = logFile // На Windows создаем процесс в новой группе, чтобы Ctrl+C не убивал его if runtime.GOOS == "windows" { cmd.SysProcAttr = &syscall.SysProcAttr{ CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, } } if err := cmd.Start(); err != nil { logFile.Close() return fmt.Errorf("ошибка запуска %s: %w", coreName, err) } // Ждем немного для проверки запуска time.Sleep(2 * time.Second) // Проверяем, что процесс еще работает (для Windows используем другой метод) processRunning := true if runtime.GOOS == "windows" { // На Windows просто проверяем, что процесс существует process, err := os.FindProcess(cmd.Process.Pid) if err != nil { processRunning = false } else { // Пытаемся отправить сигнал 0 (проверка существования) err = process.Signal(syscall.Signal(0)) // На Windows это всегда возвращает ошибку, поэтому просто считаем что процесс работает processRunning = true } } else { // На Unix используем стандартную проверку err := cmd.Process.Signal(syscall.Signal(0)) processRunning = (err == nil) } if !processRunning { logFile.Close() // Читаем логи для диагностики errorLog := filepath.Join(logsDir, "vless_error.log") if errorData, readErr := os.ReadFile(errorLog); readErr == nil && len(errorData) > 0 { lines := strings.Split(string(errorData), "\n") errorLines := []string{} for i := len(lines) - 1; i >= 0 && len(errorLines) < 10; i-- { if strings.TrimSpace(lines[i]) != "" { errorLines = append([]string{lines[i]}, errorLines...) } } if len(errorLines) > 0 { return fmt.Errorf("процесс %s завершился с ошибкой.\n\nПоследние строки из лога ошибок:\n%s\n\nПолный лог: %s", coreName, strings.Join(errorLines, "\n"), errorLog) } } return fmt.Errorf("процесс %s завершился с ошибкой.\nПроверьте логи в: %s", coreName, errorLog) } // Сохраняем состояние state := &config.ConnectionState{ Connected: true, ConfigName: configName, ConfigType: "vless", StartTime: time.Now().Format(time.RFC3339), Interface: strings.ToLower(coreName), ProcessPID: cmd.Process.Pid, LogFile: trafficLog, } if err := config.SaveState(state); err != nil { cmd.Process.Kill() logFile.Close() return fmt.Errorf("ошибка сохранения состояния: %w", err) } // Логируем успешное подключение logPath := logger.GetLogPath(logsDir, "vless") logger.LogMessage(logPath, fmt.Sprintf("Успешно подключено к '%s' через %s (PID: %d, Лог: %s)", configName, coreName, cmd.Process.Pid, trafficLog)) fmt.Printf("✓ Подключено к '%s' через %s\n", configName, coreName) fmt.Printf("SOCKS5 прокси: 127.0.0.1:10808\n") // Автоматически настраиваем системный прокси fmt.Println("\n" + strings.Repeat("─", 60)) fmt.Println("Настройка системного прокси для работы VPN по всему ПК...") if err := proxy.EnableSystemProxy("127.0.0.1:10808"); err != nil { fmt.Printf("⚠ Не удалось настроить системный прокси: %v\n", err) fmt.Println("Вы можете настроить его вручную в настройках Windows") } else { fmt.Println("✓ Системный прокси настроен - VPN работает для всех приложений") } // Включаем защитные механизмы (БЕЗ Kill Switch!) fmt.Println("\nНастройка защитных механизмов...") secManager := security.NewSecurityManager(config.ConfigDir, "default-password") secConfig, _ := secManager.LoadSecurityConfig() // KILL SWITCH ПОЛНОСТЬЮ ОТКЛЮЧЕН! // Включаем только защиту DNS если она включена if secConfig != nil && secConfig.DNSProtectionEnabled { dnsProtection := security.NewDNSProtection() if err := dnsProtection.Enable(secConfig.VPNDNSServers); err != nil { fmt.Printf("⚠ Предупреждение: не удалось включить защиту DNS: %v\n", err) } else { fmt.Println("✓ Защита DNS включена") } } else { fmt.Println("ℹ Защита DNS отключена") } // Защищаем директорию конфигураций if err := secManager.ProtectConfigDirectory(); err != nil { fmt.Printf("⚠ Предупреждение: не удалось защитить директорию конфигураций: %v\n", err) } // Проверяем утечки fmt.Println("\nПроверка безопасности...") leaks, err := secManager.CheckForLeaks() if err == nil && len(leaks) == 0 { fmt.Println("✓ Утечек не обнаружено - соединение безопасно") } else if len(leaks) > 0 { fmt.Println("⚠ Обнаружены потенциальные утечки:") for _, leak := range leaks { fmt.Printf(" - %s\n", leak) } } fmt.Printf("\nЛоги трафика:\n") fmt.Printf(" - Основной: %s\n", trafficLog) fmt.Printf(" - Доступ (IP): %s\n", filepath.Join(logsDir, "vless_access.log")) fmt.Printf(" - Ошибки: %s\n", filepath.Join(logsDir, "vless_error.log")) return nil } // extractServerIP извлекает IP адрес сервера из VLESS URL func extractServerIP(vlessURL string) string { urlStr := strings.TrimPrefix(vlessURL, "vless://") if idx := strings.Index(urlStr, "#"); idx != -1 { urlStr = urlStr[:idx] } if idx := strings.Index(urlStr, "?"); idx != -1 { urlStr = urlStr[:idx] } parts := strings.Split(urlStr, "@") if len(parts) != 2 { return "" } serverPort := parts[1] var server string if strings.Contains(serverPort, "[") { endIdx := strings.Index(serverPort, "]") server = serverPort[1:endIdx] } else { lastColon := strings.LastIndex(serverPort, ":") server = serverPort[:lastColon] } return server } // PingServer проверяет доступность VLESS сервера func PingServer(vlessURL string, timeout time.Duration) (bool, float64, error) { // Парсим URL для получения адреса сервера urlStr := strings.TrimPrefix(vlessURL, "vless://") if idx := strings.Index(urlStr, "#"); idx != -1 { urlStr = urlStr[:idx] } if idx := strings.Index(urlStr, "?"); idx != -1 { urlStr = urlStr[:idx] } parts := strings.Split(urlStr, "@") if len(parts) != 2 { return false, 0, fmt.Errorf("неверный формат URL") } serverPort := parts[1] var server string var port string if strings.Contains(serverPort, "[") { endIdx := strings.Index(serverPort, "]") server = serverPort[1:endIdx] port = strings.TrimPrefix(serverPort[endIdx+1:], ":") } else { lastColon := strings.LastIndex(serverPort, ":") server = serverPort[:lastColon] port = serverPort[lastColon+1:] } if port == "" { port = "443" } // Измеряем время подключения start := time.Now() conn, err := net.DialTimeout("tcp", net.JoinHostPort(server, port), timeout) elapsed := time.Since(start) if err != nil { return false, 0, err } conn.Close() return true, float64(elapsed.Milliseconds()), nil }