using System.Diagnostics; using System.IO.Compression; using System.Net; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text.Json; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Proxd_man.Subscribe; namespace Proxd_man; public class XrayCoreManager { private const string XrayLatestReleaseApiUrl = "https://api.github.com/repos/XTLS/Xray-core/releases/latest"; private const string XrayLatestReleaseAssetDownloadUrlPrefix = "https://github.com/XTLS/Xray-core/releases/latest/download/"; private readonly ILogger _logger; private readonly string _coreProgramDir; private readonly string _workingDirectory; public XrayCoreManager( ILogger logger, SubscribeManager subscribeManager, string coreProgramDir = "./xray-core", string workingDirectory = "./xray-core-run") { _logger = logger; _coreProgramDir = coreProgramDir; _workingDirectory = workingDirectory; // TODO: 看看注册订阅更新事件后, 需要进行哪些操作. } public string GetXrayCoreProgramPath() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return _coreProgramDir + "/xray.exe"; } return _coreProgramDir + "/xray"; } public string GetXrayCoreProgramDownloadUrl() { string os; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var osVersion = Environment.OSVersion.Version; switch (osVersion.Major) { case < 6: case 6 when osVersion.Minor < 1: throw new Exception("Windows 7 or later is required."); case 6 when osVersion.Minor == 1: os = "win7"; break; default: os = "windows"; break; } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { os = "linux"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { os = "macos"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) { os = "freebsd"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("openbsd"))) { os = "openbsd"; } else { throw new Exception("Unsupported operating system."); } string arch; if (RuntimeInformation.ProcessArchitecture == Architecture.X64) { arch = "64"; } else if (RuntimeInformation.ProcessArchitecture == Architecture.X86) { arch = "32"; } else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) { arch = "arm32-v7a"; } else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { arch = "arm64-v8a"; } else if (RuntimeInformation.ProcessArchitecture == Architecture.Armv6) { arch = "arm32-v6"; } else { throw new Exception("Unsupported architecture."); } return $"{XrayLatestReleaseAssetDownloadUrlPrefix}Xray-{os}-{arch}.zip"; } private Process CreateXrayCoreProcess(string args, bool redirectOutput = true) { var programPath = GetXrayCoreProgramPath(); if (!File.Exists(programPath)) { throw new Exception("Xray-Core program not found: " + programPath); } if (!Directory.Exists(_workingDirectory)) { Directory.CreateDirectory(_workingDirectory); } return new Process { StartInfo = new ProcessStartInfo { FileName = programPath, Arguments = args, RedirectStandardOutput = redirectOutput, UseShellExecute = false, CreateNoWindow = true, WorkingDirectory = _workingDirectory } }; } /// /// 获取目前已安装的 Xray Core 版本. /// /// 如果已安装了 Xray Core 则返回对应的版本号, 否则返回 null. private async Task GetCurrentXrayCoreVersion() { // 检查平台, 如果是 Linux, 则检查 coreProgramDir + "/xray", 如果是 Windows, 则检查 coreProgramDir + "/xray.exe" var xrayCoreProgramPath = GetXrayCoreProgramPath(); if (!File.Exists(xrayCoreProgramPath)) { return null; } var process = CreateXrayCoreProcess("--version"); process.Start(); await process.WaitForExitAsync(); var output = (await process.StandardOutput.ReadToEndAsync()).Trim(); var versionMatch = Regex.Match(output, @"Xray (\d+\.\d+\.\d+)"); return versionMatch.Success ? versionMatch.Groups[1].Value : null; } public async Task GetLatestXrayCoreVersion() { using var client = new HttpClient(); client.DefaultRequestHeaders.Add("User-Agent", "XrayCoreManager"); var response = await client.GetAsync(XrayLatestReleaseApiUrl); if (response.StatusCode != HttpStatusCode.OK) { throw new Exception("Failed to get latest Xray Core version."); } using var content = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); var root = content.RootElement; if (!root.TryGetProperty("tag_name", out var tagNameElement)) { throw new Exception("Failed to get latest Xray Core version."); } var tagName = tagNameElement.GetString() ?? throw new Exception("Failed to get latest Xray Core version."); return tagName.StartsWith('v') ? tagName[1..] : tagName; } public async Task FetchXrayCoreProgram() { if (!Directory.Exists(_coreProgramDir)) { Directory.CreateDirectory(_coreProgramDir); } var programArchiveUrl = GetXrayCoreProgramDownloadUrl(); var programArchiveDigestUrl = programArchiveUrl + ".dgst"; _logger.LogDebug("Xray-Core 下载地址: {programArchiveUrl}", programArchiveUrl); _logger.LogDebug("Xray-Core 下载地址 (Digest): {programArchiveDigestUrl}", programArchiveDigestUrl); var tempArchiveFile = Path.GetTempFileName(); using var httpClient = new HttpClient(); _logger.LogDebug("正在下载 Xray-Core..."); await using var archiveStream = await httpClient.GetStreamAsync(programArchiveUrl); await using (var programArchiveFileStream = new FileStream(tempArchiveFile, FileMode.Create, FileAccess.Write)) { await archiveStream.CopyToAsync(programArchiveFileStream); } _logger.LogDebug("正在获取 SHA-256 Digest..."); var digests = await httpClient.GetStringAsync(programArchiveDigestUrl); _logger.LogDebug("可用的 Digests: \n{digests}", digests); // digests 每行都有一个不同算法的 digest HEX 值, 因此从 digests 读出开头为 SHA-256= 的行, 并提取出其右侧的 Sha256 值 var sha256Digest = digests.Split('\n').FirstOrDefault(line => line.StartsWith("SHA2-256="))?["SHA2-256=".Length..] ?.Trim(); if (sha256Digest == null) { throw new Exception("Failed to get SHA-256 digest."); } _logger.LogDebug("SHA-256 Digest: {sha256Digest}", sha256Digest); // Verify digest using var sha256 = SHA256.Create(); await using (var localArchiveFileStream = new FileStream(tempArchiveFile, FileMode.Open, FileAccess.Read)) { var localDigest = await sha256.ComputeHashAsync(localArchiveFileStream); var localDigestHex = BitConverter.ToString(localDigest).Replace("-", "").ToLower(); _logger.LogDebug("本地 SHA-256 Digest: {localDigest}", localDigestHex); if (localDigestHex != sha256Digest) { throw new Exception("SHA-256 digest mismatch."); } _logger.LogDebug("SHA-256 Digest 验证通过."); } ZipFile.ExtractToDirectory(tempArchiveFile, _coreProgramDir, true); // Set permissions if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) { await Process.Start("chmod", "+x " + GetXrayCoreProgramPath()).WaitForExitAsync(); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { await Process.Start("chmod", "+x " + GetXrayCoreProgramPath()).WaitForExitAsync(); } File.Delete(tempArchiveFile); } public async Task InitialXrayCoreProgram() { _logger.LogInformation("正在检查 Xray-Core 安装情况以及版本..."); var currentVersion = await GetCurrentXrayCoreVersion(); _logger.LogDebug("当前 Xray-Core 版本: {currentVersion}", currentVersion); var requiredUpdate = false; if (currentVersion == null) { requiredUpdate = true; _logger.LogInformation("未找到 Xray-Core, 开始下载最新版本..."); } else { var latestXrayCoreVersion = await GetLatestXrayCoreVersion(); if (currentVersion != latestXrayCoreVersion) { requiredUpdate = true; _logger.LogInformation("发现新版本 Xray-Core (Version: {latestVersion}), 开始下载最新版本...", latestXrayCoreVersion); } } if (requiredUpdate) { await FetchXrayCoreProgram(); _logger.LogInformation("Xray-Core 更新完成."); } else { _logger.LogInformation("Xray-Core 已是最新版本, 无需更新."); } } public async Task VerifyXrayCoreConfigFile(string configFilePath) { var process = CreateXrayCoreProcess($"run -test -config {configFilePath}"); process.Start(); await process.WaitForExitAsync(); return process.ExitCode == 0; } }