/* * 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; using System.Collections.Generic; using System.Linq; using Autofac; using ProtonVPN.Common.Core.Dns; using ProtonVPN.Configurations.Contracts; using ProtonVPN.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.FirewallLogs; using ProtonVPN.NetworkFilter; using ProtonVPN.Service.Driver; using ProtonVPN.Vpn.NRPT; using Action = ProtonVPN.NetworkFilter.Action; namespace ProtonVPN.Service.Firewall; internal class Firewall : IFirewall, IStartable { private const string PERMIT_APP_FILTER_NAME = "ProtonVPN permit app"; private const int LOCAL_TRAFFIC_WEIGHT = 2; private readonly ILogger _logger; private readonly ICalloutDriver _calloutDriver; private readonly IStaticConfiguration _staticConfig; private readonly IpLayer _ipLayer; private readonly IpFilter _ipFilter; private readonly INrptWrapper _nrptWrapper; private readonly object _lock = new(); private FirewallParams _lastParams = FirewallParams.Empty; private bool _dnsCalloutFiltersAdded; private bool _isNrptRuleCreated; private readonly List _serverAddressFilterCollection = []; private readonly List _firewallItems = []; private const int DNS_UDP_PORT = 53; private const int DHCP_UDP_PORT = 67; public Firewall( ILogger logger, ICalloutDriver calloutDriver, IStaticConfiguration staticConfig, IpLayer ipLayer, IpFilter ipFilter, INrptWrapper nrptWrapper) { _logger = logger; _calloutDriver = calloutDriver; _staticConfig = staticConfig; _ipLayer = ipLayer; _ipFilter = ipFilter; _nrptWrapper = nrptWrapper; } public bool LeakProtectionEnabled { get; private set; } public bool? IsLocalAreaNetworkAccessEnabled => _lastParams?.IsLocalAreaNetworkAccessEnabled; public void Start() { if (_ipFilter.PermanentSublayer.GetFilterCount() > 0) { _lastParams = new() { ServerIp = string.Empty, Persistent = true, PermanentStateAfterReboot = true, }; LeakProtectionEnabled = true; _logger.Info("Detected permanent filters. Trying to recreate process permit filters."); //In case the app was launched after update, //we need to recreate permit from process filters since paths have changed due to version folder. _ipFilter.PermanentSublayer.DestroyFiltersByName(PERMIT_APP_FILTER_NAME); PermitFromProcesses(4, _lastParams); } } public void EnableLeakProtection(FirewallParams firewallParams) { lock (_lock) { if (LeakProtectionEnabled) { ApplyChange(firewallParams); return; } _calloutDriver.Start(); PermitServerAddress(firewallParams); ApplyFilters(firewallParams); SetLastParams(firewallParams); } } public void DisableLeakProtection() { lock (_lock) { try { _logger.Info("Restoring internet"); _nrptWrapper.DeleteRule(); _ipFilter.DynamicSublayer.DestroyAllFilters(); _ipFilter.PermanentSublayer.DestroyAllFilters(); _serverAddressFilterCollection.Clear(); _firewallItems.Clear(); LeakProtectionEnabled = false; _dnsCalloutFiltersAdded = false; _isNrptRuleCreated = false; _calloutDriver.Stop(); _lastParams = FirewallParams.Empty; _logger.Info("Internet restored"); } catch (NetworkFilterException ex) { _logger.Error("An error occurred when deleting the network filters.", ex); } } } private void ApplyFilters(FirewallParams firewallParams) { try { _logger.Info("Blocking internet"); EnableDnsLeakProtection(firewallParams); PermitFromNetworkInterface(4, firewallParams); if (!firewallParams.DnsLeakOnly) { EnableBaseLeakProtection(firewallParams); } LeakProtectionEnabled = true; _logger.Info("Internet blocked"); } catch (NetworkFilterException ex) { _logger.Error("An error occurred when applying the network filters.", ex); } } private void ApplyChange(FirewallParams firewallParams) { if (_lastParams.PermanentStateAfterReboot) { HandlePermanentStateAfterReboot(firewallParams); SetLastParams(firewallParams); return; } if (firewallParams.SessionType != _lastParams.SessionType) { List previousFilters = GetFirewallGuidsByTypes(FirewallItemType.VariableFilter, FirewallItemType.LocalNetworkFilter); List previousInterfaceFilters = GetFirewallGuidsByTypes(FirewallItemType.PermitInterfaceFilter); ApplyFilters(firewallParams); RemoveItems(previousFilters, _lastParams.SessionType); RemoveItems(previousInterfaceFilters, _lastParams.SessionType); } if (_lastParams.SessionType == SessionType.Permanent && firewallParams.SessionType == SessionType.Dynamic) { // When downgrading from persistent (advanced kill switch) to dynamic filters, wipe any // leftover permanent rules that might have been created in a previous session and are // not tracked in-memory (e.g., after an app restart). _ipFilter.PermanentSublayer.DestroyAllFilters(); } if (firewallParams.AddInterfaceFilters && firewallParams.InterfaceIndex != _lastParams.InterfaceIndex) { List previousGuids = GetFirewallGuidsByTypes(FirewallItemType.PermitInterfaceFilter); PermitFromNetworkInterface(4, firewallParams); RemoveItems(previousGuids, _lastParams.SessionType); } bool wasDnsBlockModeRecreated = false; if (firewallParams.DnsBlockMode != _lastParams.DnsBlockMode || firewallParams.ForceRecreateDnsBlock) { List previousGuids = GetFirewallGuidsByTypes(FirewallItemType.DnsCalloutFilter, FirewallItemType.DnsFilter); _dnsCalloutFiltersAdded = false; _nrptWrapper.DeleteRule(); _isNrptRuleCreated = false; CreateDnsBlock(firewallParams); RemoveItems(previousGuids, _lastParams.SessionType); wasDnsBlockModeRecreated = true; } if (firewallParams.DnsLeakOnly != _lastParams.DnsLeakOnly) { if (firewallParams.DnsLeakOnly) { List blockOutsideOpenVpnGuids = []; List baseLeakProtectionGuids = []; List permanentFilters = []; // When the service starts with advanced kill switch enabled, // we don't have in-memory guids for existing permanent filters, // so we need to collect them directly from WFP if (_lastParams.SessionType == SessionType.Permanent) { permanentFilters = _ipFilter.PermanentSublayer.GetFilters(); } else { blockOutsideOpenVpnGuids = GetFirewallGuidsByTypes(FirewallItemType.BlockOutsideOpenVpnFilter); baseLeakProtectionGuids = GetFirewallGuidsByTypes( FirewallItemType.VariableFilter, FirewallItemType.LocalNetworkFilter); } EnableDnsLeakProtection(firewallParams); // Always drop the OpenVPN server block before tearing down the process permits // to avoid a window where Proton processes are still blocked but no longer whitelisted. RemoveItems(blockOutsideOpenVpnGuids, _lastParams.SessionType); RemoveItems(baseLeakProtectionGuids, _lastParams.SessionType); if (permanentFilters.Count > 0) { RemoveItems(permanentFilters, _lastParams.SessionType); PermitFromNetworkInterface(4, firewallParams); } } else { EnableBaseLeakProtection(firewallParams); } } if (firewallParams.IsLocalAreaNetworkAccessEnabled != _lastParams.IsLocalAreaNetworkAccessEnabled) { if (firewallParams.IsLocalAreaNetworkAccessEnabled) { PermitPrivateNetwork(LOCAL_TRAFFIC_WEIGHT, firewallParams); } else { RemoveItems(GetFirewallGuidsByTypes(FirewallItemType.LocalNetworkFilter), _lastParams.SessionType); } } PermitServerAddress(firewallParams); BlockOutsideOpenVpnTraffic(firewallParams); // DNS block mode changed but couldn't be applied because the interface was not known, save it as unchanged if (!wasDnsBlockModeRecreated && firewallParams.DnsBlockMode != _lastParams.DnsBlockMode) { firewallParams.DnsBlockMode = _lastParams.DnsBlockMode; } SetLastParams(firewallParams); } private void SetLastParams(FirewallParams firewallParams) { //This is needed due to WireGuard, because we don't know the interface index in advance. uint interfaceIndex = 0; if (_lastParams.InterfaceIndex > 0 && firewallParams.InterfaceIndex == 0) { interfaceIndex = _lastParams.InterfaceIndex; } _lastParams = firewallParams; if (interfaceIndex > 0) { _lastParams.InterfaceIndex = interfaceIndex; } } private void HandlePermanentStateAfterReboot(FirewallParams firewallParams) { _calloutDriver.Start(); CreateDnsBlock(firewallParams); PermitFromNetworkInterface(4, firewallParams); PermitServerAddress(firewallParams); } private void RemoveItems(List guids, SessionType sessionType) { DeleteIpFilters(guids, sessionType); List firewallItems = _firewallItems.Where(item => guids.Contains(item.Guid)).ToList(); foreach (FirewallItem item in firewallItems) { _firewallItems.Remove(item); } } private List GetFirewallGuidsByTypes(params FirewallItemType[] firewallItemTypes) { return _firewallItems .Where(item => firewallItemTypes.Contains(item.ItemType)) .Select(item => item.Guid) .ToList(); } private void EnableDnsLeakProtection(FirewallParams firewallParams) { BlockDns(3, firewallParams); CreateDnsBlock(firewallParams); } private void EnableBaseLeakProtection(FirewallParams firewallParams) { // Add blocks first so that during cleanup (which follows insertion order) the block filters // disappear before any exceptions, ensuring Proton processes always retain their bypass rules. BlockAllIpv4Network(1, firewallParams); BlockAllIpv6Network(1, firewallParams); BlockOutsideOpenVpnTraffic(firewallParams); PermitDhcp(4, firewallParams); PermitFromProcesses(4, firewallParams); PermitNetworkDiscoveryProtocol(4, firewallParams); PermitIpv4Loopback(LOCAL_TRAFFIC_WEIGHT, firewallParams); PermitIpv6Loopback(LOCAL_TRAFFIC_WEIGHT, firewallParams); PermitPrivateNetwork(LOCAL_TRAFFIC_WEIGHT, firewallParams); } private void BlockOutsideOpenVpnTraffic(FirewallParams firewallParams) { if (string.IsNullOrEmpty(firewallParams.ServerIp) || firewallParams.DnsLeakOnly) { return; } List filters = GetFirewallGuidsByTypes(FirewallItemType.BlockOutsideOpenVpnFilter); if (filters.Count > 0) { RemoveItems(filters, firewallParams.SessionType); } _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).BlockOutsideOpenVpn( new DisplayData("ProtonVPN block outside OpenVPN traffic", "Blocks outgoing traffic to VPN server if when the process is not openvpn.exe"), layer, weight: 1, _staticConfig.OpenVpn.ExePath, firewallParams.ServerIp, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.BlockOutsideOpenVpnFilter, guid)); }); } private void BlockDns(uint weight, FirewallParams firewallParams) { _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateRemoteUdpPortFilter(new DisplayData( "ProtonVPN DNS filter", "Block UDP 53 port"), Action.HardBlock, layer, weight, DNS_UDP_PORT, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateRemoteTcpPortFilter(new DisplayData( "ProtonVPN block DNS", "Block TCP 53 port"), Action.HardBlock, layer, weight, DNS_UDP_PORT, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); _ipLayer.ApplyToIpv6(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateRemoteTcpPortFilter(new DisplayData( "ProtonVPN block DNS", "Block TCP 53 port"), Action.HardBlock, layer, weight, DNS_UDP_PORT, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); _ipLayer.ApplyToIpv6(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateRemoteUdpPortFilter(new DisplayData( "ProtonVPN block DNS", "Block UDP 53 port"), Action.HardBlock, layer, weight, DNS_UDP_PORT, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); } private void CreateDnsBlock(FirewallParams firewallParams) { DnsBlockMode dnsBlockMode = firewallParams?.DnsBlockMode ?? DnsBlockMode.Nrpt; switch (dnsBlockMode) { case DnsBlockMode.Nrpt: if (_isNrptRuleCreated) { _nrptWrapper.DeleteRule(); } _isNrptRuleCreated = _nrptWrapper.CreateRule(); break; case DnsBlockMode.Callout: _logger.Info("DNS block mode is Callout. Creating DNS callout filter."); CreateDnsCalloutFilter(firewallParams); break; case DnsBlockMode.Disabled: _logger.Info("DNS block mode is Disabled. No NRPT rule or DNS callout filter will be created."); break; } } private void CreateDnsCalloutFilter(FirewallParams firewallParams) { if (_dnsCalloutFiltersAdded || !firewallParams.AddInterfaceFilters) { return; } const uint weight = 4; Guid guid = _ipFilter.DynamicSublayer.BlockOutsideDns( new DisplayData("ProtonVPN block DNS", "Block outside dns"), Layer.OutboundIPPacketV4, weight, IpFilter.DnsCalloutGuid, firewallParams.InterfaceIndex); _firewallItems.Add(new FirewallItem(FirewallItemType.DnsCalloutFilter, guid)); _ipLayer.ApplyToIpv4(layer => { guid = _ipFilter.DynamicSublayer.CreateRemoteUdpPortFilter( new DisplayData("ProtonVPN DNS filter", "Permit UDP 53 port so we can block it at network layer"), Action.HardPermit, layer, weight, DNS_UDP_PORT); _firewallItems.Add(new FirewallItem(FirewallItemType.DnsFilter, guid)); }); _dnsCalloutFiltersAdded = true; } private void PermitDhcp(uint weight, FirewallParams firewallParams) { _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateRemoteUdpPortFilter( new DisplayData("ProtonVPN permit DHCP IPv4", "Permit 67 UDP port"), Action.SoftPermit, layer, weight, DHCP_UDP_PORT, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).PermitOutboundIpv6Dhcp( new DisplayData("ProtonVPN permit outbound DHCP IPv6", ""), Action.SoftPermit, Layer.AppAuthConnectV6, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); guid = _ipFilter.GetSublayer(firewallParams.SessionType).PermitInboundIpv6Dhcp( new DisplayData("ProtonVPN permit inbound DHCP IPv6", ""), Action.SoftPermit, Layer.AppAuthRecvAcceptV6, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); } private void PermitFromNetworkInterface(uint weight, FirewallParams firewallParams) { if (!firewallParams.AddInterfaceFilters) { return; } try { //Create the following filters dynamically on permanent or dynamic sublayer, //but prevent keeping them after reboot, as interface index might be changed. _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateNetInterfaceFilter( new DisplayData("ProtonVPN permit VPN tunnel", "Permit tunnel interface traffic"), Action.SoftPermit, layer, firewallParams.InterfaceIndex, weight, persistent: false); _firewallItems.Add(new FirewallItem(FirewallItemType.PermitInterfaceFilter, guid)); }); _ipLayer.ApplyToIpv6(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateNetInterfaceFilter( new DisplayData("ProtonVPN permit VPN tunnel", "Permit tunnel interface traffic"), Action.SoftPermit, layer, firewallParams.InterfaceIndex, weight, persistent: false); _firewallItems.Add(new FirewallItem(FirewallItemType.PermitInterfaceFilter, guid)); }); } catch (AdapterNotFoundException) { _logger.Error($"Interface with index {firewallParams.InterfaceIndex} was not found."); } } private void PermitServerAddress(FirewallParams firewallParams) { if (string.IsNullOrEmpty(firewallParams.ServerIp)) { return; } ReorderServerPermitFilters(firewallParams.ServerIp); List filterGuids = new(); _ipLayer.ApplyToIpv4(layer => { filterGuids.Add(_ipFilter.GetSublayer(firewallParams.SessionType).CreateRemoteIPv4Filter( new DisplayData("ProtonVPN permit OpenVPN server", "Permit server ip"), Action.HardPermit, layer, 1, firewallParams.ServerIp, persistent: false)); }); _serverAddressFilterCollection.Add(new ServerAddressFilterCollection { ServerIp = firewallParams.ServerIp, SessionType = firewallParams.SessionType, Filters = filterGuids, }); DeleteServerPermitFilters(firewallParams); } private void ReorderServerPermitFilters(string serverIp) { if (_serverAddressFilterCollection.Count == 0) { return; } int index = 0; ServerAddressFilterCollection item = null; foreach (ServerAddressFilterCollection collection in _serverAddressFilterCollection) { if (collection.ServerIp == serverIp) { item = collection; break; } index++; } if (item != null) { _serverAddressFilterCollection.RemoveAt(index); _serverAddressFilterCollection.Add(item); } } private void DeleteServerPermitFilters(FirewallParams firewallParams) { if (_serverAddressFilterCollection.Count >= 3) { ServerAddressFilterCollection serverAddressFilterCollection = _serverAddressFilterCollection.FirstOrDefault(); if (serverAddressFilterCollection == null || serverAddressFilterCollection.Filters?.Count == 0) { return; } //Use permanent session here to be able to remove filters created //on both dynamic and permanent sublayers. DeleteIpFilters(serverAddressFilterCollection.Filters, SessionType.Permanent); _serverAddressFilterCollection.Remove(serverAddressFilterCollection); } //If session type changes, we need to remove previous permit filters from dynamic/persistent sublayer. if (_lastParams.SessionType != firewallParams.SessionType) { foreach (ServerAddressFilterCollection serverAddressFilters in _serverAddressFilterCollection.ToList()) { if (serverAddressFilters.SessionType == _lastParams.SessionType) { DeleteIpFilters(serverAddressFilters.Filters, _lastParams.SessionType); _serverAddressFilterCollection.Remove(serverAddressFilters); } } } } private void DeleteIpFilters(List guids, SessionType sessionType) { foreach (Guid guid in guids) { _ipFilter.GetSublayer(sessionType).DestroyFilter(guid); } } private void BlockAllIpv4Network(uint weight, FirewallParams firewallParams) { _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateLayerFilter( new DisplayData("ProtonVPN block IPv4", "Block all IPv4 traffic"), Action.SoftBlock, layer, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); } private void BlockAllIpv6Network(uint weight, FirewallParams firewallParams) { _ipLayer.ApplyToIpv6(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateLayerFilter( new DisplayData("ProtonVPN block IPv6", "Block all IPv6 traffic"), Action.SoftBlock, layer, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); } private void PermitIpv4Loopback(uint weight, FirewallParams firewallParams) { _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateLoopbackFilter( new DisplayData("ProtonVPN permit IPv4 loopback", "Permit IPv4 loopback traffic"), Action.HardPermit, layer, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); } private void PermitIpv6Loopback(uint weight, FirewallParams firewallParams) { _ipLayer.ApplyToIpv6(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateLoopbackFilter( new DisplayData("ProtonVPN permit IPv6 loopback", "Permit IPv6 loopback traffic"), Action.HardPermit, layer, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); } private void PermitNetworkDiscoveryProtocol(uint weight, FirewallParams firewallParams) { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).PermitRouterSolicitationMessage( new DisplayData("ProtonVPN permit ICMP type 133, code 0.", ""), Action.HardPermit, Layer.AppAuthConnectV6, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); guid = _ipFilter.GetSublayer(firewallParams.SessionType).PermitRouterAdvertisementMessage( new DisplayData("ProtonVPN permit ICMP type 134, code 0.", ""), Action.HardPermit, Layer.AppAuthRecvAcceptV6, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); _ipLayer.Apply(layer => { guid = _ipFilter.GetSublayer(firewallParams.SessionType).PermitNeighborSolicitationMessage( new DisplayData("ProtonVPN permit ICMP type 135, code 0.", ""), Action.HardPermit, layer, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); guid = _ipFilter.GetSublayer(firewallParams.SessionType).PermitNeighborAdvertisementMessage( new DisplayData("ProtonVPN permit ICMP type 136, code 0.", ""), Action.HardPermit, layer, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }, [Layer.AppAuthConnectV6, Layer.AppAuthRecvAcceptV6]); guid = _ipFilter.GetSublayer(firewallParams.SessionType).PermitIcmpRedirectMessage( new DisplayData("ProtonVPN permit ICMP type 137, code 0.", ""), Action.HardPermit, Layer.AppAuthRecvAcceptV6, weight, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); } private void PermitPrivateNetwork(uint weight, FirewallParams firewallParams) { if (!firewallParams.IsLocalAreaNetworkAccessEnabled) { return; } List networkAddresses = [ NetworkAddress.FromIpv4("10.0.0.0", "255.0.0.0"), NetworkAddress.FromIpv4("169.254.0.0", "255.255.0.0"), NetworkAddress.FromIpv4("172.16.0.0", "255.240.0.0"), NetworkAddress.FromIpv4("192.168.0.0", "255.255.0.0"), NetworkAddress.FromIpv4("224.0.0.0", "240.0.0.0"), NetworkAddress.FromIpv4("255.255.255.255", "255.255.255.255"), NetworkAddress.FromIpv6("fc00::", 7), NetworkAddress.FromIpv6("fe80::", 10), ]; foreach (NetworkAddress networkAddress in networkAddresses) { if (networkAddress.IsIpv6) { _ipLayer.ApplyToIpv6(layer => { PermitPrivateNetworkAddress(firewallParams, networkAddress, layer, weight); }); } else { _ipLayer.ApplyToIpv4(layer => { PermitPrivateNetworkAddress(firewallParams, networkAddress, layer, weight); }); } } } private void PermitPrivateNetworkAddress(FirewallParams firewallParams, NetworkAddress networkAddress, Layer layer, uint weight) { try { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateRemoteNetworkIPFilter( new DisplayData("ProtonVPN permit private network", ""), Action.HardPermit, layer, weight, networkAddress, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.LocalNetworkFilter, guid)); } catch (InvalidArgumentException) { _logger.Error($"Failed to create private network filter for address {networkAddress} due to invalid argument."); } } private void PermitFromProcesses(uint weight, FirewallParams firewallParams) { List processes = new() { _staticConfig.ClientExePath, _staticConfig.ServiceExePath, _staticConfig.WireGuard.ServicePath, }; foreach (string processPath in processes) { try { _ipLayer.ApplyToIpv4(layer => { Guid guid = _ipFilter.GetSublayer(firewallParams.SessionType).CreateAppFilter( new DisplayData(PERMIT_APP_FILTER_NAME, "Permit ProtonVPN app to bypass VPN tunnel"), Action.HardPermit, layer, weight, processPath, false, firewallParams.Persistent); _firewallItems.Add(new FirewallItem(FirewallItemType.VariableFilter, guid)); }); } catch (InvalidArgumentException) { _logger.Error($"Failed to create app filter for path {processPath} due to invalid argument."); } } } }