/* * Copyright (c) 2026 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using ProtonVPN.Common.Core.Helpers; using ProtonVPN.Common.Core.LocalAgent; using ProtonVPN.Common.Core.Networking; using ProtonVPN.Common.Legacy.Threading; using ProtonVPN.Common.Legacy.Vpn; using ProtonVPN.EntityMapping.Contracts; using ProtonVPN.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.ConnectLogs; using ProtonVPN.Logging.Contracts.Events.DisconnectLogs; using ProtonVPN.ProcessCommunication.Contracts.Controllers; using ProtonVPN.ProcessCommunication.Contracts.Entities.LocalAgent; using ProtonVPN.ProcessCommunication.Contracts.Entities.Settings; using ProtonVPN.ProcessCommunication.Contracts.Entities.Vpn; using ProtonVPN.Service.ControllerRetries; using ProtonVPN.Service.ProcessCommunication; using ProtonVPN.Service.Settings; using ProtonVPN.Service.StateMachine; using ProtonVPN.Service.Vpn; using ProtonVPN.Vpn.Connection; using ProtonVPN.Vpn.LocalAgent; using ProtonVPN.Vpn.PortMapping; namespace ProtonVPN.Service; public class VpnController : IVpnController { private readonly ILogger _logger; private readonly IServiceSettings _serviceSettings; private readonly ITaskQueue _taskQueue; private readonly IPortMappingProtocolClient _portMappingProtocolClient; private readonly IClientControllerSender _appControllerCaller; private readonly IEntityMapper _entityMapper; private readonly ILocalAgentTlsCredentialsCache _localAgentTlsCredentialsCache; private readonly IControllerRetryManager _controllerRetryManager; private readonly IVpnConnectionStateMachine _stateMachine; private readonly ITunnelOrchestrator _tunnelOrchestrator; private readonly ILocalAgent _localAgent; private readonly ILocalAgentEventReceiver _localAgentEventReceiver; public VpnController( ILogger logger, IServiceSettings serviceSettings, ITaskQueue taskQueue, IPortMappingProtocolClient portMappingProtocolClient, IClientControllerSender appControllerCaller, IEntityMapper entityMapper, ILocalAgentTlsCredentialsCache localAgentTlsCredentialsCache, IControllerRetryManager controllerRetryManager, IVpnConnectionStateMachine stateMachine, ITunnelOrchestrator tunnelOrchestrator, ILocalAgent localAgent, ILocalAgentEventReceiver localAgentEventReceiver) { _logger = logger; _serviceSettings = serviceSettings; _taskQueue = taskQueue; _portMappingProtocolClient = portMappingProtocolClient; _appControllerCaller = appControllerCaller; _entityMapper = entityMapper; _localAgentTlsCredentialsCache = localAgentTlsCredentialsCache; _controllerRetryManager = controllerRetryManager; _stateMachine = stateMachine; _tunnelOrchestrator = tunnelOrchestrator; _localAgent = localAgent; _localAgentEventReceiver = localAgentEventReceiver; } public async Task Connect(ConnectionRequestIpcEntity connectionRequest, CancellationToken cancelToken) { Ensure.NotNull(connectionRequest, nameof(connectionRequest)); _controllerRetryManager.EnforceRetryId(connectionRequest); _logger.Info("Connect requested"); if (_stateMachine.LastError == VpnError.BaseFilteringEngineServiceNotRunning || cancelToken.IsCancellationRequested) { return; } _serviceSettings.Apply(connectionRequest.Settings); VpnConfig config = _entityMapper.Map(connectionRequest.Config); config.OpenVpnAdapter = _serviceSettings.OpenVpnAdapter; IReadOnlyList endpoints = _entityMapper.Map(connectionRequest.Servers); VpnCredentials credentials = _entityMapper.Map(connectionRequest.Credentials); if (string.IsNullOrEmpty(credentials.ClientCertPem) && string.IsNullOrEmpty(credentials.Username) && string.IsNullOrEmpty(credentials.Password)) { _logger.Error("Connection credentials are missing, aborting connection."); return; } await _localAgentTlsCredentialsCache.SetAsync(new LocalAgentTlsCredentials( new ConnectionCertificate(credentials.ClientCertPem, credentials.ClientCertificateExpirationDateUtc), credentials.ClientKeyPair), cancelToken); _stateMachine.Connect(endpoints, config, credentials); } public async Task Disconnect(DisconnectionRequestIpcEntity disconnectionRequest, CancellationToken cancelToken) { Ensure.NotNull(disconnectionRequest, nameof(disconnectionRequest)); _controllerRetryManager.EnforceRetryId(disconnectionRequest); _logger.Info($"Disconnect requested (Error: {disconnectionRequest.ErrorType})"); _serviceSettings.Apply(disconnectionRequest.Settings); VpnError error = _entityMapper.Map(disconnectionRequest.ErrorType); _stateMachine.Disconnect(error); } public async Task UpdateLocalAgentTlsCredentialsAsync(LocalAgentTlsCredentialsIpcEntity credentialsIpcEntity, CancellationToken cancelToken) { LocalAgentTlsCredentials credentials = _entityMapper.Map(credentialsIpcEntity); await _localAgentTlsCredentialsCache.SetAsync(credentials, cancelToken); } public Task GetNetworkTraffic(CancellationToken cancelToken) { return Task.FromResult(_entityMapper.Map(_tunnelOrchestrator.NetworkTraffic)); } public async Task ApplySettings(MainSettingsIpcEntity settings, CancellationToken cancelToken) { Ensure.NotNull(settings, nameof(settings)); _serviceSettings.Apply(settings); } public async Task RepeatState(CancellationToken cancelToken) { _taskQueue.Enqueue(async () => { await _appControllerCaller.SendCurrentVpnStateAsync(); }); } public async Task RepeatPortForwardingState(CancellationToken cancelToken) { _portMappingProtocolClient.RepeatState(); } public async Task RequestNetShieldStats(CancellationToken cancelToken) { _localAgent.RequestNetShieldStats(); } public async Task RequestConnectionDetails(CancellationToken cancelToken) { await _localAgentEventReceiver.RequestConnectionDetailsAsync(cancelToken); } }