/*
* Copyright (c) 2023 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 Autofac;
using ProtonVPN.Logging.Contracts;
using ProtonVPN.Logging.Contracts.Events.FirewallLogs;
using ProtonVPN.NetworkFilter;
namespace ProtonVPN.Service.Firewall;
public class IpFilter : IStartable
{
public static Guid DnsCalloutGuid = Guid.Parse("{10636af3-50d6-4f53-acb7-d5af33217fcb}");
private readonly Guid _providerGuid = Guid.Parse("{20865f68-0b04-44da-bb83-2238622540fa}");
private readonly Guid _sublayerGuid = Guid.Parse("{aa867e71-5765-4be3-9399-581585c226ce}");
private readonly ILogger _logger;
private const int PERMANENT_SUBLAYER_WEIGHT = 1000;
private const int DYNAMIC_SUBLAYER_WEIGHT = 1001;
public IpFilter(ILogger logger)
{
_logger = logger;
}
public void Start()
{
CreatePermanentFilters();
CreateDynamicSession();
CreatePermanentSession();
}
public NetworkFilter.IpFilter PermanentInstance { get; private set; }
public NetworkFilter.IpFilter DynamicInstance { get; private set; }
public Sublayer PermanentSublayer { get; private set; }
public Sublayer DynamicSublayer { get; private set; }
public Sublayer GetSublayer(SessionType type)
{
return type == SessionType.Dynamic ? DynamicSublayer : PermanentSublayer;
}
public void CloseSession(NetworkFilter.IpFilter instance, Sublayer sublayer)
{
if (instance.Session.Type == SessionType.Permanent)
{
sublayer.DestroyAllFilters();
}
instance.Session.Close();
}
private void CreateDynamicSession()
{
DynamicInstance = NetworkFilter.IpFilter.Create(
Session.Dynamic(),
new DisplayData { Name = "ProtonVPN Dynamic Provider" });
DynamicSublayer = DynamicInstance.CreateSublayer(new DisplayData { Name = "ProtonVPN Dynamic Sublayer" },
DYNAMIC_SUBLAYER_WEIGHT);
}
private void CreatePermanentSession()
{
PermanentInstance = new NetworkFilter.IpFilter(Session.Permanent(), _providerGuid);
PermanentSublayer = new Sublayer(PermanentInstance, _sublayerGuid);
}
private void CreatePermanentFilters()
{
var session = Session.Permanent();
if (NetworkFilter.IpFilter.IsRegistered(session, _providerGuid))
{
try
{
ExecuteTransaction(session, () =>
{
NetworkFilter.IpFilter instance = new(session, _providerGuid);
CreateCallout(instance);
});
}
catch (NetworkFilterException e)
{
_logger.Error("Error when creating the callout for an already existing Provider.", e);
throw;
}
finally
{
session.Close();
}
return;
}
try
{
ExecuteTransaction(session, () =>
{
NetworkFilter.IpFilter instance = NetworkFilter.IpFilter.Create(session,
new DisplayData { Name = "ProtonVPN Permanent Provider" },
true,
_providerGuid);
instance.CreateSublayer(new DisplayData { Name = "ProtonVPN Permanent Sublayer" },
PERMANENT_SUBLAYER_WEIGHT,
true,
_sublayerGuid);
CreateCallout(instance);
});
}
catch (NetworkFilterException e)
{
_logger.Error("Error when creating permanent IP filtering.", e);
throw;
}
finally
{
session.Close();
}
}
private void CreateCallout(NetworkFilter.IpFilter instance)
{
instance.CreateCallout(
new DisplayData
{
Name = "ProtonVPN block dns callout",
Description = "Sends server failure packet response for non TAP/TUN DNS queries.",
},
DnsCalloutGuid,
Layer.OutboundIPPacketV4,
true);
}
private void ExecuteTransaction(Session session, System.Action action)
{
session.StartTransaction();
try
{
action();
session.CommitTransaction();
}
catch (NetworkFilterException e)
{
_logger.Error("Transaction failed, aborting.", e);
session.AbortTransaction();
throw;
}
}
}