using System; using MongoDB.Driver; using PolyTraderSharp.Extensions; using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using PolyTraderSharp.Models; namespace PolyTraderSharp.Services { public class MullvadVpnService : BackgroundService { private readonly TerminalLogger _logger; private ServerSettings _settings; private readonly string _settingsPath = "server_settings.xml"; private bool _isConnected = false; private int _consecutiveFailures = 0; private readonly int _maxRetries = 3; public bool IsConnected => _isConnected; public MullvadVpnService(TerminalLogger logger) { _logger = logger; _settings = ServerSettings.Load(_settingsPath); } public void ReloadSettings() { _settings = ServerSettings.Load(_settingsPath); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { if (_settings.VpnEnabled) { await HealthCheckAsync(); } await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); } } public async Task HealthCheckAsync() { if (!_settings.VpnEnabled) return true; string status = await RunCliAsync("status"); if (status.Contains("Connected")) { _isConnected = true; _consecutiveFailures = 0; return true; } // Try reconnecting _logger.Warning("VPN is not connected. Attempting to reconnect..."); if (await ConnectAsync()) return true; _consecutiveFailures++; if (_consecutiveFailures >= _maxRetries) { _logger.Error($"VPN unrecoverable after {_maxRetries} retries."); } return false; } public async Task ConnectAsync() { if (!_settings.VpnEnabled) return true; string status = await RunCliAsync("status"); if (status.Contains("Connected")) { if (string.IsNullOrEmpty(_settings.VpnLocation) || status.Contains(_settings.VpnLocation, StringComparison.OrdinalIgnoreCase)) { _logger.Info($"VPN already connected tightly to {_settings.VpnLocation}"); _isConnected = true; _consecutiveFailures = 0; return true; } } if (!string.IsNullOrEmpty(_settings.MullvadAccount)) { await RunCliAsync($"account login {_settings.MullvadAccount}"); await Task.Delay(1000); } await RunCliAsync("lan set allow"); if (!string.IsNullOrEmpty(_settings.VpnLocation)) { await RunCliAsync($"relay set location {_settings.VpnLocation}"); await Task.Delay(1000); } await RunCliAsync("connect"); for (int i = 0; i < 10; i++) { await Task.Delay(2000); string check = await RunCliAsync("status"); if (check.Contains("Connected")) { _isConnected = true; _consecutiveFailures = 0; _logger.Info($"VPN connected successfully: {check.Trim()}"); return true; } } _logger.Error("VPN failed to connect after waiting"); _consecutiveFailures++; return false; } public async Task DisconnectAsync() { string result = await RunCliAsync("disconnect"); _isConnected = false; _logger.Info("VPN disconnected."); return true; } private async Task RunCliAsync(string args) { try { if (!File.Exists(_settings.MullvadCliPath)) { _logger.Error($"Mullvad CLI not found at: {_settings.MullvadCliPath}"); return string.Empty; } var psi = new ProcessStartInfo { FileName = _settings.MullvadCliPath, Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(psi); if (process == null) return string.Empty; await process.WaitForExitAsync(); string output = await process.StandardOutput.ReadToEndAsync(); string err = await process.StandardError.ReadToEndAsync(); return string.IsNullOrWhiteSpace(output) ? err : output; } catch (Exception ex) { _logger.Error($"Mullvad CLI exc: {ex.Message}"); return string.Empty; } } } }