Files
PolyTraderSharp/services/MullvadVpnService.cs
T
bergmandClaude Opus 4.8 475d396f80 Baseline: Ausgangszustand vor Modularisierung
Erster Commit des bestehenden monolithischen WinForms-Copytraders,
inklusive der Alt-Backups (*.bak), damit diese dauerhaft in der
Historie rekonstruierbar bleiben. Threema-Lib unter libs/ wurde
vendored (nested .git entfernt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:16:16 +02:00

165 lines
5.3 KiB
C#

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<bool> 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<bool> 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<bool> DisconnectAsync()
{
string result = await RunCliAsync("disconnect");
_isConnected = false;
_logger.Info("VPN disconnected.");
return true;
}
private async Task<string> 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;
}
}
}
}