/* * Copyright (c) 2025 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 ProtonVPN.Api.Contracts; using ProtonVPN.Api.Contracts.VpnConfig; using ProtonVPN.Client.Common.Observers; using ProtonVPN.Client.EventMessaging.Contracts; using ProtonVPN.Client.Logic.Auth.Contracts.Messages; using ProtonVPN.Client.Logic.Servers.Contracts.Messages; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Client.Settings.Contracts.Observers; using ProtonVPN.Common.Core.Networking; using ProtonVPN.Configurations.Contracts; using ProtonVPN.IssueReporting.Contracts; using ProtonVPN.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.SettingsLogs; namespace ProtonVPN.Client.Settings.Observers; public class ClientConfigObserver : PollingObserverBase, IClientConfigObserver, IEventMessageReceiver, IEventMessageReceiver, IEventMessageReceiver { private readonly List _unsupportedWireGuardPorts = [53]; private readonly ISettings _settings; private readonly IApiClient _apiClient; private readonly IConfiguration _config; protected override TimeSpan PollingInterval => _config.ClientConfigUpdateInterval; public ClientConfigObserver( ILogger logger, IIssueReporter issueReporter, ISettings settings, IApiClient apiClient, IConfiguration config) : base(logger, issueReporter) { _settings = settings; _apiClient = apiClient; _config = config; } public void Receive(LoggedInMessage message) { StartTimer(); } public void Receive(LoggedOutMessage message) { StopTimer(); } public void Receive(DeviceLocationChangedMessage message) { if (message.HasCountryChangedAndHasValue && message.IsUserLoggedIn) { TriggerAction.Run(); } } public Task UpdateAsync(CancellationToken cancellationToken) { return MakeClientConfigRequestAsync(cancellationToken); } protected override async Task OnTriggerAsync() { await MakeClientConfigRequestAsync(CancellationToken.None); } private async Task MakeClientConfigRequestAsync(CancellationToken cancellationToken) { try { Logger.Info("Retrieving Client Config"); ApiResponseResult response = await _apiClient.GetVpnConfigAsync(_settings.DeviceLocation, cancellationToken); if (response.Success) { HandleVpnConfigResponse(response.Value); } } catch (Exception e) { Logger.Error("Failed to retrieve Client Config", e); } } private void HandleVpnConfigResponse(VpnConfigResponse value) { _settings.ProTunUdpPorts = value.DefaultPorts.WireGuard.Udp.Where(IsWireGuardPortSupported).ToArray(); _settings.ProTunTcpPorts = value.DefaultPorts.WireGuard.Tcp.Where(IsWireGuardPortSupported).ToArray(); _settings.ProTunTlsPorts = value.DefaultPorts.WireGuard.Tls.Where(IsWireGuardPortSupported).ToArray(); _settings.WireGuardUdpPorts = value.DefaultPorts.WireGuard.Udp.Where(IsWireGuardPortSupported).ToArray(); _settings.WireGuardTcpPorts = value.DefaultPorts.WireGuard.Tcp.Where(IsWireGuardPortSupported).ToArray(); _settings.WireGuardTlsPorts = value.DefaultPorts.WireGuard.Tls.Where(IsWireGuardPortSupported).ToArray(); _settings.OpenVpnTcpPorts = value.DefaultPorts.OpenVpn.Tcp; _settings.OpenVpnUdpPorts = value.DefaultPorts.OpenVpn.Udp; if (value.FeatureFlags.ServerRefresh.HasValue) { _settings.IsFeatureConnectedServerCheckEnabled = value.FeatureFlags.ServerRefresh.Value; } if (value.ServerRefreshInterval.HasValue) { _settings.ConnectedServerCheckInterval = TimeSpan.FromMinutes(value.ServerRefreshInterval.Value); } _settings.ChangeServerSettings = new() { AttemptsLimit = value.ChangeServerAttemptLimit, ShortDelay = TimeSpan.FromSeconds(value.ChangeServerShortDelayInSeconds), LongDelay = TimeSpan.FromSeconds(value.ChangeServerLongDelayInSeconds) }; if (value.SmartProtocol is not null) { List disabledVpnProtocols = []; if (!value.SmartProtocol.WireGuardUdp) { disabledVpnProtocols.Add(VpnProtocol.WireGuardUdp); disabledVpnProtocols.Add(VpnProtocol.ProTunUdp); } if (!value.SmartProtocol.WireGuardTcp) { disabledVpnProtocols.Add(VpnProtocol.WireGuardTcp); disabledVpnProtocols.Add(VpnProtocol.ProTunTcp); } if (!value.SmartProtocol.WireGuardTls) { disabledVpnProtocols.Add(VpnProtocol.WireGuardTls); disabledVpnProtocols.Add(VpnProtocol.ProTunTls); } if (!value.SmartProtocol.OpenVpnUdp) { disabledVpnProtocols.Add(VpnProtocol.OpenVpnUdp); } if (!value.SmartProtocol.OpenVpnTcp) { disabledVpnProtocols.Add(VpnProtocol.OpenVpnTcp); } _settings.DisabledSmartProtocols = disabledVpnProtocols.ToArray(); } } private bool IsWireGuardPortSupported(int port) { return !_unsupportedWireGuardPorts.Contains(port); } }