Files
QVPN/src/QVPN.Core/Models.cs
T
Курнат Андрей aae6dd28dd Release QVPN 1.0.5 with Xray and resilient tunnel recovery
Integrate Xray 26.9.9 with the sing-box system tunnel and verify Windows routes during health checks.

Improve endpoint selection, network recovery, subscription refresh deadlines, diagnostics, packaging, and regression coverage.
2026-10-30 06:15:08 +03:00

71 lines
3.7 KiB
C#

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace QVPN.Core;
public static class Constants
{
public const string ServiceName = "QVPNService";
public const string PipeName = "QVPN.Control.v1";
public const int ProxyPort = 17891;
public const int XrayPort = 17892;
public static string DataPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "QVPN");
public static JsonSerializerOptions Json { get; } = new() { PropertyNameCaseInsensitive = true, WriteIndented = false };
}
public record VpnNode(string Id, string Name, string Host, int Port, string Uuid, string ServerName, string PublicKey, string ShortId, string Fingerprint, string Flow);
public record NodeView(string Id, string Name, int? LatencyMs);
public sealed class Settings
{
public string SubscriptionUrl { get; set; } = "";
public List<VpnNode> Nodes { get; set; } = [];
public bool DesiredConnected { get; set; }
public bool RussianDirect { get; set; } = true;
public bool AutoFailover { get; set; } = true;
public string SelectedId { get; set; } = "";
public string LastWorkingId { get; set; } = "";
public DateTimeOffset? UpdatedAt { get; set; }
public string? SubscriptionInfo { get; set; }
}
public record Request(string Command, string? Value = null, bool? Enabled = null);
public record Status(string State, string Message, bool DesiredConnected, string? ActiveId, string? ActiveName, DateTimeOffset? ConnectedSince,
int Restarts, int? EnginePid, bool RussianDirect, bool AutoFailover, string SelectedId, DateTimeOffset? UpdatedAt, List<NodeView> Nodes, List<string> Events,
int? XrayPid = null, string? VpnCore = null, bool? SystemRoutesVerified = null);
public record Response(bool Ok, string? Error, Status? Status);
public sealed class SecureStore(string directory)
{
private readonly string path = Path.Combine(directory, "settings.dpapi");
public Settings Load()
{
if (!File.Exists(path)) return new();
return JsonSerializer.Deserialize<Settings>(ProtectedData.Unprotect(File.ReadAllBytes(path), null, DataProtectionScope.LocalMachine), Constants.Json)
?? throw new InvalidDataException("Повреждены настройки QVPN.");
}
public void Save(Settings settings)
{
Directory.CreateDirectory(directory);
byte[] plain = JsonSerializer.SerializeToUtf8Bytes(settings, Constants.Json);
try
{
byte[] encrypted = ProtectedData.Protect(plain, null, DataProtectionScope.LocalMachine);
string temp = path + ".new";
using (var file = new FileStream(temp, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough)) { file.Write(encrypted); file.Flush(true); }
if (File.Exists(path)) File.Replace(temp, path, path + ".backup", true); else File.Move(temp, path);
}
finally { CryptographicOperations.ZeroMemory(plain); }
}
}
public static class SafeError
{
public static string Describe(Exception e) => e switch
{
HttpRequestException => "Сервер подписки недоступен. Сохранённые локации остаются доступны.",
OperationCanceledException => "Превышено время ожидания. Повторите действие.",
System.ComponentModel.Win32Exception => "Windows не разрешила запуск VPN. Проверьте установку службы.",
InvalidDataException or ArgumentException => e.Message,
_ => "Ошибка QVPN: " + e.GetType().Name + ". Подробности в диагностике."
};
}