/* * 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.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.NetworkLogs; using ProtonVPN.OperatingSystems.Network.Contracts; using Vanara.PInvoke; using static Vanara.PInvoke.IpHlpApi; using static Vanara.PInvoke.Ws2_32; namespace ProtonVPN.OperatingSystems.Network.Policies; public sealed class NetworkInterfacePolicyLease : INetworkInterfacePolicyLease { private readonly ILogger _logger; private readonly uint _interfaceIndex; private readonly List _appliedStates = []; private bool _disposed; public NetworkInterfacePolicyLease(ILogger logger, uint interfaceIndex) { _logger = logger; _interfaceIndex = interfaceIndex; } public void Apply() { TryApplyForFamily(ADDRESS_FAMILY.AF_INET); TryApplyForFamily(ADDRESS_FAMILY.AF_INET6); } private void TryApplyForFamily(ADDRESS_FAMILY family) { MIB_IPINTERFACE_ROW row = new() { Family = family, InterfaceIndex = _interfaceIndex, }; Win32Error result = GetIpInterfaceEntry(ref row); if (result.Failed) { _logger.Warn($"Failed to query interface {_interfaceIndex} ({family}) while applying policy.", result.GetException()); return; } bool originalWeakHostSend = row.WeakHostSend; bool originalWeakHostReceive = row.WeakHostReceive; bool wasWeakHostSendModified = false; bool wasWeakHostReceiveModified = false; if (row.WeakHostSend) { row.WeakHostSend = false; wasWeakHostSendModified = true; } if (row.WeakHostReceive) { row.WeakHostReceive = false; wasWeakHostReceiveModified = true; } if (!wasWeakHostSendModified && !wasWeakHostReceiveModified) { return; } FixInterfaceRow(ref row); result = SetIpInterfaceEntry(row); if (result.Succeeded) { _appliedStates.Add(new AppliedInterfaceState() { Family = family, OriginalWeakHostSend = originalWeakHostSend, WasWeakHostSendModified = wasWeakHostSendModified, OriginalWeakHostReceive = originalWeakHostReceive, WasWeakHostReceiveModified = wasWeakHostReceiveModified, }); _logger.Info($"Applied interface policy for index {_interfaceIndex} ({family}).\n" + $"Was WeakHostSend disabled: {wasWeakHostSendModified}\n" + $"Was WeakHostReceive disabled: {wasWeakHostReceiveModified}"); } else { _logger.Warn($"Failed to apply interface policy for index {_interfaceIndex} ({family}).", result.GetException()); } } public void Dispose() { if (_disposed) { return; } foreach (AppliedInterfaceState state in _appliedStates) { Restore(state); } _disposed = true; } private void Restore(AppliedInterfaceState state) { if (!state.WasWeakHostSendModified && !state.WasWeakHostReceiveModified) { return; } MIB_IPINTERFACE_ROW row = new() { Family = state.Family, InterfaceIndex = _interfaceIndex, }; Win32Error query = GetIpInterfaceEntry(ref row); if (query.Failed) { _logger.Warn($"Failed to query interface {_interfaceIndex} ({state.Family}) while restoring policy.", query.GetException()); return; } row.WeakHostSend = state.OriginalWeakHostSend; row.WeakHostReceive = state.OriginalWeakHostReceive; FixInterfaceRow(ref row); Win32Error update = SetIpInterfaceEntry(row); if (update.Failed) { _logger.Warn($"Failed to restore interface policy for index {_interfaceIndex} ({state.Family}).", update.GetException()); } } private static void FixInterfaceRow(ref MIB_IPINTERFACE_ROW row) { byte maxPrefix = row.Family == ADDRESS_FAMILY.AF_INET ? (byte)32 : (byte)128; if (row.SitePrefixLength > maxPrefix) { row.SitePrefixLength = maxPrefix; } } }