163 lines
5.2 KiB
C#
163 lines
5.2 KiB
C#
/*************************************************************************
|
|
* ModernUO *
|
|
* Copyright 2019-2024 - ModernUO Development Team *
|
|
* Email: hi@modernuo.com *
|
|
* File: Firewall.cs *
|
|
* *
|
|
* This program 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. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
*************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using Server.Logging;
|
|
|
|
namespace Server.Network;
|
|
|
|
public static class Firewall
|
|
{
|
|
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Firewall));
|
|
|
|
private static InternalValidationEntry _validationEntry;
|
|
private static readonly Dictionary<IPAddress, bool> _isBlockedCache = new();
|
|
|
|
private static readonly ConcurrentQueue<(IFirewallEntry FirewallyEntry, bool Remove)> _firewallQueue = new();
|
|
private static readonly SortedSet<IFirewallEntry> _firewallSet = new();
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IFirewallEntry RequestAddSingleIPEntry(string entry)
|
|
{
|
|
try
|
|
{
|
|
var firewallEntry = new SingleIpFirewallEntry(entry);
|
|
_firewallQueue.Enqueue((firewallEntry, false));
|
|
return firewallEntry;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.Warning(e, "Failed to add firewall entry: {Pattern}", entry);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IFirewallEntry RequestAddCIDREntry(string entry)
|
|
{
|
|
try
|
|
{
|
|
var firewallEntry = new CidrFirewallEntry(entry);
|
|
_firewallQueue.Enqueue((firewallEntry, false));
|
|
return firewallEntry;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.Warning(e, "Failed to add firewall entry: {Pattern}", entry);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void RequestAddEntry(IFirewallEntry entry)
|
|
{
|
|
_firewallQueue.Enqueue((entry, false));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void RequestRemoveEntry(IFirewallEntry entry)
|
|
{
|
|
_firewallQueue.Enqueue((entry, true));
|
|
}
|
|
|
|
internal static void ProcessQueue()
|
|
{
|
|
while (_firewallQueue.TryDequeue(out var entry))
|
|
{
|
|
if (entry.Remove)
|
|
{
|
|
RemoveEntry(entry.FirewallyEntry);
|
|
}
|
|
else
|
|
{
|
|
AddEntry(entry.FirewallyEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static bool IsBlocked(IPAddress address)
|
|
{
|
|
ref var isBlocked = ref CollectionsMarshal.GetValueRefOrAddDefault(_isBlockedCache, address, out var exists);
|
|
if (exists)
|
|
{
|
|
return isBlocked;
|
|
}
|
|
|
|
if (_validationEntry == null)
|
|
{
|
|
_validationEntry = new InternalValidationEntry(address);
|
|
}
|
|
else
|
|
{
|
|
_validationEntry.Address = address;
|
|
}
|
|
|
|
// Get all entries that are lower than our validation entry
|
|
var view = _firewallSet.GetViewBetween(_firewallSet.Min, _validationEntry);
|
|
|
|
// Loop backward since there shouldn't be any entries where the Min address is higher than ours
|
|
foreach (var firewallEntry in view.Reverse())
|
|
{
|
|
if (firewallEntry.IsBlocked(_validationEntry.MinIpAddress))
|
|
{
|
|
isBlocked = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
isBlocked = view.Max?.IsBlocked(_validationEntry.MinIpAddress) == true;
|
|
|
|
return isBlocked;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static void AddEntry(IFirewallEntry firewallEntry)
|
|
{
|
|
_firewallSet.Add(firewallEntry);
|
|
_isBlockedCache.Clear();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static void RemoveEntry(IFirewallEntry entry)
|
|
{
|
|
if (entry == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_firewallSet.Remove(entry);
|
|
_isBlockedCache.Clear();
|
|
}
|
|
|
|
private class InternalValidationEntry : BaseFirewallEntry
|
|
{
|
|
private UInt128 _address;
|
|
|
|
public IPAddress Address
|
|
{
|
|
set => _address = value.ToUInt128();
|
|
}
|
|
|
|
public override UInt128 MinIpAddress => _address;
|
|
public override UInt128 MaxIpAddress => _address;
|
|
|
|
public InternalValidationEntry(IPAddress ipAddress) => Address = ipAddress;
|
|
}
|
|
}
|