package vless import ( "encoding/json" "fmt" "net" "net/url" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "time" "vpn-client/internal/config" "vpn-client/internal/logger" ) // 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, xrayDir string) error { // Загружаем конфигурации 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) } // Создаем конфигурацию Xray xrayConfig, 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(xrayConfig, "", " ") if err != nil { return fmt.Errorf("ошибка сериализации конфига: %w", err) } if err := os.WriteFile(configPath, data, 0644); err != nil { return fmt.Errorf("ошибка сохранения конфига: %w", err) } // Путь к xray xrayExe := "xray" if runtime.GOOS == "windows" { xrayExe = "xray.exe" } xrayPath := filepath.Join(xrayDir, xrayExe) if xrayPath, err = ensureXrayBinary(xrayDir); err != nil { return err } // Создаем лог-файл трафика 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, "Клиент: Xray\n") fmt.Fprintf(logFile, "Прокси: 127.0.0.1:10808\n") fmt.Fprintf(logFile, "%s\n\n", strings.Repeat("=", 60)) // Запускаем xray cmd := exec.Command(xrayPath, "run", "-c", configPath) cmd.Stdout = logFile cmd.Stderr = logFile if err := cmd.Start(); err != nil { logFile.Close() return fmt.Errorf("ошибка запуска xray: %w", err) } // Ждем немного для проверки запуска time.Sleep(3 * time.Second) // Проверяем, что процесс еще работает if err := cmd.Process.Signal(os.Signal(nil)); err != nil { logFile.Close() return fmt.Errorf("процесс xray завершился с ошибкой") } // Сохраняем состояние state := &config.ConnectionState{ Connected: true, ConfigName: configName, ConfigType: "vless", StartTime: time.Now().Format(time.RFC3339), Interface: "xray", 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' (PID: %d, Лог: %s)", configName, cmd.Process.Pid, trafficLog)) fmt.Printf("✓ Подключено к '%s'\n", configName) fmt.Printf("SOCKS5 прокси: 127.0.0.1:10808\n") 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 } // 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 }