feat: Moves TcpServer to another thread. Rewrites Firewall (#1660)
## Breaking Changes * The Firewall and IP Limiter have been rewritten. Please read the notes carefully! * `TcpServer.Instances` moved back to `NetState.Instances` - sorry - it was stupid to move it to begin with. > [!Note] > Sockets that fail the IP Limiter or Firewall will be immediately and forcibly disconnected. > This means they will be stuck at "Verifying account..." if it was a real client. ### Summary - Removes firewall wildcard support. - Removes `AccessRestrictions`. - Moves Firewall/IPLimiter to the core. - Moves `TcpServer` to its own thread. - Removes the `SocketConnect` and `SocketDisconnect` event sinks. - Moves `Instances` back to `NetState.Instances`. - Fixes a long standing bug with bad handling of duplicate listener addresses. #### Firewall The firewall has been completely rewritten. There is now an "Admin Firewall" which saves to the config file. Secondarily, there is an internal firewall used exclusively by the TcpServer while processing sockets. The Admin firewall mirrors it's additions/deletions to the internal firewall by adding requests to a queue. > [!IMPORTANT] > **Wildcard firewall entries, such as `X`, `*`, `?` are not allowed.** > **Ranges in between IP classes or sextets are not allowed.** > **Please make sure to use one of the following:** > * IP Address - `192.168.1.1` > * CIDR - `192.168.1.0/24` > * Range - `192.168.1.1-192.168.1.100` #### IP Limiter The IP Limiter has been completely rewritten. The available configurations are: ```json "ipLimiter.enable": "True", "ipLimiter.maxConnectionsPerIP": 10, "ipLimiter.clearConnectionAttemptsDuration": "00:00:00:10", "ipLimiter.clearThrottledDuration": "00:00:02:00", ``` The IP Limiter is set up to prevent spamming connections from the same IP. Every time an IP connects, it is added to a connection list. After 10 attempts, the IP is added to the throttle list. To keep the system fast, the connection list is entirely wiped every 10 seconds, and the throttle list is entirely wiped every 2 minutes.
This commit is contained in:
parent
5f3de6537b
commit
4cd668ef61
38 changed files with 1117 additions and 1030 deletions
|
|
@ -1,48 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Net;
|
||||
using Server.Logging;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public static class AccessRestrictions
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(AccessRestrictions));
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.SocketConnect += EventSink_SocketConnect;
|
||||
}
|
||||
|
||||
private static void EventSink_SocketConnect(SocketConnectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ip = (e.Connection.RemoteEndPoint as IPEndPoint)?.Address;
|
||||
|
||||
if (Firewall.IsBlocked(ip))
|
||||
{
|
||||
logger.Information("Client: {IP}: Firewall blocked connection attempt.", ip);
|
||||
e.AllowConnection = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IPLimiter.SocketBlock && !IPLimiter.Verify(ip))
|
||||
{
|
||||
logger.Warning("Client: {IP}: Past IP limit threshold", ip);
|
||||
|
||||
using (var op = new StreamWriter("ipLimits.log", true))
|
||||
{
|
||||
op.WriteLine("{0}\tPast IP limit threshold\t{1}", ip, Core.Now);
|
||||
}
|
||||
|
||||
e.AllowConnection = false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
e.AllowConnection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ public static class AccountHandler
|
|||
{
|
||||
var ipAddress = ns.Address;
|
||||
|
||||
if (Utility.IPMatchClassC(accessList[0], ipAddress))
|
||||
if (accessList[0].MatchClassC(ipAddress))
|
||||
{
|
||||
acct.SetPassword(pass);
|
||||
from.SendMessage("The password to your account has changed.");
|
||||
|
|
@ -302,19 +302,6 @@ public static class AccountHandler
|
|||
|
||||
public static void EventSink_AccountLogin(AccountLoginEventArgs e)
|
||||
{
|
||||
if (!IPLimiter.SocketBlock && !IPLimiter.Verify(e.State.Address))
|
||||
{
|
||||
e.Accepted = false;
|
||||
e.RejectReason = ALRReason.InUse;
|
||||
|
||||
logger.Information("Login: {NetState}: Past IP limit threshold", e.State);
|
||||
|
||||
using var op = new StreamWriter("ipLimits.log", true);
|
||||
op.WriteLine($"{e.State}\tPast IP limit threshold\t{Core.Now}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var un = e.Username;
|
||||
var pw = e.Password;
|
||||
|
||||
|
|
@ -371,18 +358,6 @@ public static class AccountHandler
|
|||
|
||||
public static void EventSink_GameLogin(GameLoginEventArgs e)
|
||||
{
|
||||
if (!IPLimiter.SocketBlock && !IPLimiter.Verify(e.State.Address))
|
||||
{
|
||||
e.Accepted = false;
|
||||
|
||||
logger.Warning("Login: {NetState} Past IP limit threshold", e.State);
|
||||
|
||||
using var op = new StreamWriter("ipLimits.log", true);
|
||||
op.WriteLine($"{e.State}\tPast IP limit threshold\t{Core.Now}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var un = e.Username;
|
||||
var pw = e.Password;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,233 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CommunityToolkit.HighPerformance;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public static class Firewall
|
||||
{
|
||||
private const string firewallConfigPath = "firewall.cfg";
|
||||
|
||||
static Firewall()
|
||||
{
|
||||
Set = new HashSet<IFirewallEntry>();
|
||||
|
||||
if (File.Exists(firewallConfigPath))
|
||||
{
|
||||
using var ip = new StreamReader(firewallConfigPath);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
||||
if (line.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Set.Add(ToFirewallEntry(line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<IFirewallEntry> Set { get; }
|
||||
|
||||
public static IFirewallEntry ToFirewallEntry(object entry)
|
||||
{
|
||||
return entry switch
|
||||
{
|
||||
IFirewallEntry firewallEntry => firewallEntry,
|
||||
IPAddress address => new IPFirewallEntry(address),
|
||||
string s => ToFirewallEntry(s),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static IFirewallEntry ToFirewallEntry(string entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IPAddress.TryParse(entry, out var addr))
|
||||
{
|
||||
return new IPFirewallEntry(addr);
|
||||
}
|
||||
|
||||
// Try CIDR parse
|
||||
var tokenizer = entry.Tokenize('/');
|
||||
var ip = tokenizer.MoveNext() ? tokenizer.Current : null;
|
||||
var length = tokenizer.MoveNext() ? tokenizer.Current : null;
|
||||
|
||||
if (
|
||||
length != null &&
|
||||
IPAddress.TryParse(ip, out var cidrPrefix) &&
|
||||
int.TryParse(length, out var cidrLength)
|
||||
)
|
||||
{
|
||||
return new CIDRFirewallEntry(cidrPrefix, cidrLength);
|
||||
}
|
||||
|
||||
return new WildcardIPFirewallEntry(entry);
|
||||
}
|
||||
|
||||
public static void Remove(object obj)
|
||||
{
|
||||
var entry = ToFirewallEntry(obj);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
Set.Remove(entry);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(object obj)
|
||||
{
|
||||
Add(ToFirewallEntry(obj));
|
||||
}
|
||||
|
||||
public static void Add(IFirewallEntry entry)
|
||||
{
|
||||
Set.Add(entry);
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void Add(string pattern)
|
||||
{
|
||||
Add(ToFirewallEntry(pattern));
|
||||
}
|
||||
|
||||
public static void Add(IPAddress ip)
|
||||
{
|
||||
Add(ToFirewallEntry(ip));
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
using var op = new StreamWriter(firewallConfigPath);
|
||||
foreach (var entry in Set)
|
||||
{
|
||||
op.WriteLine(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsBlocked(IPAddress ip)
|
||||
{
|
||||
foreach (var entry in Set)
|
||||
{
|
||||
if (entry.IsBlocked(ip))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface IFirewallEntry
|
||||
{
|
||||
bool IsBlocked(IPAddress address);
|
||||
}
|
||||
|
||||
public class IPFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly IPAddress m_Address;
|
||||
|
||||
public IPFirewallEntry(IPAddress address) => m_Address = address;
|
||||
|
||||
public bool IsBlocked(IPAddress address) => m_Address.Equals(address);
|
||||
|
||||
public override string ToString() => m_Address.ToString();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj switch
|
||||
{
|
||||
IPAddress => obj.Equals(m_Address),
|
||||
string s when IPAddress.TryParse(s, out var otherAddress) => otherAddress.Equals(m_Address),
|
||||
IPFirewallEntry entry => m_Address.Equals(entry.m_Address),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => m_Address.GetHashCode();
|
||||
}
|
||||
|
||||
public class CIDRFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly int m_CIDRLength;
|
||||
private readonly IPAddress m_CIDRPrefix;
|
||||
|
||||
public CIDRFirewallEntry(IPAddress cidrPrefix, int cidrLength)
|
||||
{
|
||||
m_CIDRPrefix = cidrPrefix;
|
||||
m_CIDRLength = cidrLength;
|
||||
}
|
||||
|
||||
public bool IsBlocked(IPAddress address) => Utility.IPMatchCIDR(m_CIDRPrefix, address, m_CIDRLength);
|
||||
|
||||
public override string ToString() => $"{m_CIDRPrefix}/{m_CIDRLength}";
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is string entry)
|
||||
{
|
||||
var str = entry.Split('/');
|
||||
|
||||
if (str.Length == 2)
|
||||
{
|
||||
if (IPAddress.TryParse(str[0], out var cidrPrefix))
|
||||
{
|
||||
if (int.TryParse(str[1], out var cidrLength))
|
||||
{
|
||||
return m_CIDRPrefix.Equals(cidrPrefix) && m_CIDRLength.Equals(cidrLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (obj is CIDRFirewallEntry cidrEntry)
|
||||
{
|
||||
return m_CIDRPrefix.Equals(cidrEntry.m_CIDRPrefix) && m_CIDRLength.Equals(cidrEntry.m_CIDRLength);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(m_CIDRPrefix, m_CIDRLength);
|
||||
}
|
||||
|
||||
public class WildcardIPFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly string m_Entry;
|
||||
|
||||
private bool m_Valid = true;
|
||||
|
||||
public WildcardIPFirewallEntry(string entry) => m_Entry = entry;
|
||||
|
||||
public bool IsBlocked(IPAddress address) => m_Valid && Utility.IPMatch(m_Entry, address, out m_Valid);
|
||||
|
||||
public override string ToString() => m_Entry;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is string)
|
||||
{
|
||||
return obj.Equals(m_Entry);
|
||||
}
|
||||
|
||||
return obj is WildcardIPFirewallEntry entry && m_Entry == entry.m_Entry;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => m_Entry.GetHashCode(StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
using System.Net;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class IPLimiter
|
||||
{
|
||||
public static readonly IPAddress[] Exemptions =
|
||||
{
|
||||
IPAddress.Parse( "127.0.0.1" )
|
||||
};
|
||||
|
||||
public static bool Enabled { get; private set; }
|
||||
public static bool SocketBlock { get; private set; }
|
||||
public static int MaxAddresses { get; private set; }
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("ipLimiter.enable", true);
|
||||
SocketBlock = ServerConfiguration.GetOrUpdateSetting("ipLimiter.blockAtConnection", true);
|
||||
MaxAddresses = ServerConfiguration.GetOrUpdateSetting("ipLimiter.maxConnectionsPerIP", 10);
|
||||
}
|
||||
|
||||
public static bool IsExempt(IPAddress ip)
|
||||
{
|
||||
for (int i = 0; i < Exemptions.Length; i++)
|
||||
{
|
||||
if (ip.Equals(Exemptions[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Verify(IPAddress ourAddress)
|
||||
{
|
||||
if (!Enabled || IsExempt(ourAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
{
|
||||
if (ourAddress.Equals(ns.Address))
|
||||
{
|
||||
++count;
|
||||
|
||||
if (count >= MaxAddresses)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1137,7 +1137,7 @@ namespace Server.Commands.Generic
|
|||
|
||||
try
|
||||
{
|
||||
Firewall.Add(state.Address);
|
||||
AdminFirewall.Add(state.Address);
|
||||
AddResponse("They have been firewalled.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ namespace Server.Commands.Generic
|
|||
var list = new List<object>();
|
||||
var addresses = new List<IPAddress>();
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
var mob = ns.Mobile;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ namespace Server.Commands.Generic
|
|||
|
||||
var list = new List<object>();
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
var mob = ns.Mobile;
|
||||
|
||||
|
|
|
|||
|
|
@ -753,7 +753,7 @@ namespace Server.Commands
|
|||
|
||||
public static void BroadcastMessage(AccessLevel ac, int hue, string message)
|
||||
{
|
||||
foreach (var state in TcpServer.Instances)
|
||||
foreach (var state in NetState.Instances)
|
||||
{
|
||||
var m = state.Mobile;
|
||||
|
||||
|
|
@ -849,7 +849,7 @@ namespace Server.Commands
|
|||
[Description("View some stats about the server.")]
|
||||
public static void Stats_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.SendMessage($"Open Connections: {TcpServer.Instances.Count}");
|
||||
e.Mobile.SendMessage($"Open Connections: {NetState.Instances.Count}");
|
||||
e.Mobile.SendMessage($"Mobiles: {World.Mobiles.Count}");
|
||||
e.Mobile.SendMessage($"Items: {World.Items.Count}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ namespace Server.Engines.Help
|
|||
|
||||
var isStaffOnline = false;
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
var m = ns.Mobile;
|
||||
|
||||
|
|
|
|||
|
|
@ -171,10 +171,10 @@ namespace Server.Gumps
|
|||
AddLabel(150, 150, LabelHue, banned.ToString());
|
||||
|
||||
AddLabel(20, 170, LabelHue, "Firewalled:");
|
||||
AddLabel(150, 170, LabelHue, Firewall.Set.Count.ToString());
|
||||
AddLabel(150, 170, LabelHue, AdminFirewall.Set.Count.ToString());
|
||||
|
||||
AddLabel(20, 190, LabelHue, "Clients:");
|
||||
AddLabel(150, 190, LabelHue, TcpServer.Instances.Count.ToString());
|
||||
AddLabel(150, 190, LabelHue, NetState.Instances.Count.ToString());
|
||||
|
||||
AddLabel(20, 210, LabelHue, "Mobiles:");
|
||||
AddLabel(150, 210, LabelHue, World.Mobiles.Count.ToString());
|
||||
|
|
@ -437,7 +437,7 @@ namespace Server.Gumps
|
|||
{
|
||||
if (m_List == null)
|
||||
{
|
||||
var states = TcpServer.Instances.ToList();
|
||||
var states = NetState.Instances.ToList();
|
||||
states.Sort(NetStateComparer.Instance);
|
||||
|
||||
m_List = states.ToList<object>();
|
||||
|
|
@ -1225,7 +1225,7 @@ namespace Server.Gumps
|
|||
{
|
||||
AddFirewallHeader();
|
||||
|
||||
m_List ??= Firewall.Set.ToList<object>();
|
||||
m_List ??= AdminFirewall.Set.ToList<object>();
|
||||
|
||||
AddLabelCropped(12, 120, 358, 20, LabelHue, "IP Address");
|
||||
|
||||
|
|
@ -1275,7 +1275,7 @@ namespace Server.Gumps
|
|||
{
|
||||
AddFirewallHeader();
|
||||
|
||||
if (state is not Firewall.IFirewallEntry firewallEntry)
|
||||
if (state is not IFirewallEntry firewallEntry)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
@ -1804,7 +1804,7 @@ namespace Server.Gumps
|
|||
{
|
||||
for (var i = 0; i < a.LoginIPs.Length; ++i)
|
||||
{
|
||||
Firewall.Add(a.LoginIPs[i]);
|
||||
AdminFirewall.Add(a.LoginIPs[i]);
|
||||
}
|
||||
|
||||
notice = "All addresses in the list have been firewalled.";
|
||||
|
|
@ -1828,7 +1828,7 @@ namespace Server.Gumps
|
|||
|
||||
if (okay)
|
||||
{
|
||||
Firewall.Add(toFirewall);
|
||||
AdminFirewall.Add(toFirewall);
|
||||
|
||||
notice = $"{toFirewall} : Added to firewall.";
|
||||
}
|
||||
|
|
@ -2427,7 +2427,7 @@ namespace Server.Gumps
|
|||
{
|
||||
var count = 0;
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
var a = ns.Account;
|
||||
|
||||
|
|
@ -2533,7 +2533,7 @@ namespace Server.Gumps
|
|||
}
|
||||
else
|
||||
{
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
bool isMatch;
|
||||
|
||||
|
|
@ -3626,7 +3626,7 @@ namespace Server.Gumps
|
|||
}
|
||||
else
|
||||
{
|
||||
foreach (var check in Firewall.Set)
|
||||
foreach (var check in AdminFirewall.Set)
|
||||
{
|
||||
var checkStr = check.ToString();
|
||||
|
||||
|
|
@ -3692,42 +3692,47 @@ namespace Server.Gumps
|
|||
m_PageType,
|
||||
m_ListPage,
|
||||
m_List,
|
||||
"You must enter an address or pattern to add.",
|
||||
m_State
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (!Utility.IsValidIP(text))
|
||||
{
|
||||
from.SendGump(
|
||||
new AdminGump(
|
||||
from,
|
||||
m_PageType,
|
||||
m_ListPage,
|
||||
m_List,
|
||||
"That is not a valid address or pattern.",
|
||||
"You must enter an address or CIDR to add.",
|
||||
m_State
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
object toAdd = Firewall.ToFirewallEntry(text);
|
||||
IFirewallEntry firewallEntry;
|
||||
try
|
||||
{
|
||||
firewallEntry = AdminFirewall.ToFirewallEntry(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
from.SendGump(
|
||||
new AdminGump(
|
||||
from,
|
||||
m_PageType,
|
||||
m_ListPage,
|
||||
m_List,
|
||||
"That is not a valid address or CIDR.",
|
||||
m_State
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
CommandLogging.WriteLine(
|
||||
from,
|
||||
$"{from.AccessLevel} {CommandLogging.Format(from)} firewalling {toAdd}"
|
||||
$"{from.AccessLevel} {CommandLogging.Format(from)} firewalling {firewallEntry}"
|
||||
);
|
||||
|
||||
Firewall.Add(toAdd);
|
||||
AdminFirewall.Add(firewallEntry);
|
||||
from.SendGump(
|
||||
new AdminGump(
|
||||
from,
|
||||
AdminGumpPage.FirewallInfo,
|
||||
0,
|
||||
null,
|
||||
$"{toAdd} : Added to firewall.",
|
||||
toAdd
|
||||
$"{firewallEntry} : Added to firewall.",
|
||||
firewallEntry
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -3751,14 +3756,14 @@ namespace Server.Gumps
|
|||
}
|
||||
case 3:
|
||||
{
|
||||
if (m_State is Firewall.IFirewallEntry)
|
||||
if (m_State is IFirewallEntry)
|
||||
{
|
||||
CommandLogging.WriteLine(
|
||||
from,
|
||||
$"{from.AccessLevel} {CommandLogging.Format(from)} removing {m_State} from firewall list"
|
||||
);
|
||||
|
||||
Firewall.Remove(m_State);
|
||||
AdminFirewall.Remove(m_State);
|
||||
from.SendGump(
|
||||
new AdminGump(
|
||||
from,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ namespace Server.Gumps
|
|||
|
||||
var list = new List<Mobile>();
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
var m = ns.Mobile;
|
||||
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ public partial class HouseRaffleStone : Item
|
|||
|
||||
foreach (var entry in Entries)
|
||||
{
|
||||
if (Utility.IPMatchClassC(entry.Address, address))
|
||||
if (entry.Address.MatchClassC(address))
|
||||
{
|
||||
if (++tickets >= EntryLimitPerIP)
|
||||
{
|
||||
|
|
|
|||
133
Projects/UOContent/Misc/AdminFirewall.cs
Normal file
133
Projects/UOContent/Misc/AdminFirewall.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Server.Logging;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server;
|
||||
|
||||
public static class AdminFirewall
|
||||
{
|
||||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(AdminFirewall));
|
||||
|
||||
private static readonly HashSet<IFirewallEntry> _firewallSet = [];
|
||||
private const string firewallConfigPath = "firewall.cfg";
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
if (File.Exists(firewallConfigPath))
|
||||
{
|
||||
var searchValues = SearchValues.Create("*Xx?");
|
||||
|
||||
using var ip = new StreamReader(firewallConfigPath);
|
||||
|
||||
while (ip.ReadLine() is { } line)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
||||
if (line.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.AsSpan().ContainsAny(searchValues))
|
||||
{
|
||||
logger.Warning("Legacy firewall entry \"{Entry}\" ignored", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
Add(ToFirewallEntry(line), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This is not optimized, so do not use this in hot paths
|
||||
public static IReadOnlySet<IFirewallEntry> Set => _firewallSet;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IFirewallEntry ToFirewallEntry(object entry)
|
||||
{
|
||||
return entry switch
|
||||
{
|
||||
IFirewallEntry firewallEntry => firewallEntry,
|
||||
IPAddress address => new SingleIpFirewallEntry(address),
|
||||
string s => ToFirewallEntry(s),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static IFirewallEntry ToFirewallEntry(string entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var rangeSeparator = entry.IndexOf('-');
|
||||
if (rangeSeparator > -1)
|
||||
{
|
||||
return new CidrFirewallEntry(
|
||||
IPAddress.Parse(entry.AsSpan(0, rangeSeparator)),
|
||||
IPAddress.Parse(entry.AsSpan(rangeSeparator + 1))
|
||||
);
|
||||
}
|
||||
|
||||
// CIDR notation
|
||||
if (entry.IndexOf('/') > -1)
|
||||
{
|
||||
return new CidrFirewallEntry(entry);
|
||||
}
|
||||
|
||||
return new SingleIpFirewallEntry(entry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Remove(object obj, bool save = true)
|
||||
{
|
||||
var entry = ToFirewallEntry(obj);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
_firewallSet.Remove(entry);
|
||||
Firewall.RequestRemoveEntry(entry); // Request that the TcpServer also remove the entry
|
||||
|
||||
if (save)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Add(object obj) => Add(ToFirewallEntry(obj));
|
||||
|
||||
public static bool Add(IFirewallEntry entry, bool save = true)
|
||||
{
|
||||
var added = _firewallSet.Add(entry);
|
||||
Firewall.RequestAddEntry(entry); // Request that the TcpServer also add the entry
|
||||
|
||||
if (save)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
using var op = new StreamWriter(firewallConfigPath);
|
||||
foreach (var entry in Set)
|
||||
{
|
||||
op.WriteLine(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -178,11 +178,11 @@ namespace Server.Misc
|
|||
|
||||
try
|
||||
{
|
||||
var states = TcpServer.Instances;
|
||||
var states = NetState.Instances;
|
||||
|
||||
op.WriteLine($"- Count: {states.Count}");
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
op.Write($"+ {ns}:");
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Server.Misc
|
|||
|
||||
public static void FoodDecay()
|
||||
{
|
||||
foreach (var state in TcpServer.Instances)
|
||||
foreach (var state in NetState.Instances)
|
||||
{
|
||||
HungerDecay(state.Mobile);
|
||||
ThirstDecay(state.Mobile);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Server
|
|||
{
|
||||
m_LevelOverride = value;
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
var m = ns.Mobile;
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ namespace Server
|
|||
|
||||
protected override void OnTick()
|
||||
{
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
ns.Mobile?.CheckLightLevels(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace Server.Misc
|
|||
|
||||
private static void EventSink_Login(Mobile m)
|
||||
{
|
||||
var userCount = TcpServer.Instances.Count;
|
||||
var userCount = NetState.Instances.Count;
|
||||
var itemCount = World.Items.Count;
|
||||
var mobileCount = World.Mobiles.Count;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Net.Http;
|
|||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using Server.Logging;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
|
|
@ -153,18 +154,39 @@ namespace Server.Misc
|
|||
return false;
|
||||
}
|
||||
|
||||
// 10.0.0.0/8
|
||||
// 172.16.0.0/12
|
||||
// 192.168.0.0/16
|
||||
// 169.254.0.0/16
|
||||
// 100.64.0.0/10 RFC 6598
|
||||
private static bool IsPrivateNetwork(IPAddress ip) =>
|
||||
ip.AddressFamily != AddressFamily.InterNetworkV6 &&
|
||||
(Utility.IPMatch("192.168.*", ip) ||
|
||||
Utility.IPMatch("10.*", ip) ||
|
||||
Utility.IPMatch("172.16-31.*", ip) ||
|
||||
Utility.IPMatch("169.254.*", ip) ||
|
||||
Utility.IPMatch("100.64-127.*", ip));
|
||||
ip.AddressFamily switch
|
||||
{
|
||||
AddressFamily.InterNetwork => IsPrivateNetworkV4(ip),
|
||||
AddressFamily.InterNetworkV6 => IsPrivateNetworkV6(ip),
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static readonly IFirewallEntry[] _privateNetworkV4 =
|
||||
[
|
||||
new CidrFirewallEntry("192.168.0.0/16"),
|
||||
new CidrFirewallEntry("10.0.0.0/8"),
|
||||
new CidrFirewallEntry("172.16.0.0/12"),
|
||||
new CidrFirewallEntry("169.254.0.0/16"),
|
||||
new CidrFirewallEntry("100.64.0.0/10")
|
||||
];
|
||||
|
||||
private static readonly IFirewallEntry[] _privateNetworkV6 =
|
||||
[
|
||||
new CidrFirewallEntry("fc00::/7"),
|
||||
new CidrFirewallEntry("fe80::/10")
|
||||
];
|
||||
|
||||
private static bool IsPrivateNetworkV4(IPAddress ip) =>
|
||||
_privateNetworkV4[0].IsBlocked(ip) ||
|
||||
_privateNetworkV4[1].IsBlocked(ip) ||
|
||||
_privateNetworkV4[2].IsBlocked(ip) ||
|
||||
_privateNetworkV4[3].IsBlocked(ip) ||
|
||||
_privateNetworkV4[4].IsBlocked(ip);
|
||||
|
||||
private static bool IsPrivateNetworkV6(IPAddress ip) =>
|
||||
_privateNetworkV6[0].IsBlocked(ip) ||
|
||||
_privateNetworkV6[1].IsBlocked(ip);
|
||||
|
||||
private const string _ipifyUrl = "https://api.ipify.org";
|
||||
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ namespace Server.Misc
|
|||
|
||||
for (var i = 0; i < Voters.Length; ++i)
|
||||
{
|
||||
if (Utility.IPMatchClassC(Voters[i], ipAddress))
|
||||
if (Voters[i].MatchClassC(ipAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ namespace Server.Misc
|
|||
type = 2;
|
||||
}
|
||||
|
||||
foreach (var ns in TcpServer.Instances)
|
||||
foreach (var ns in NetState.Instances)
|
||||
{
|
||||
var mob = ns.Mobile;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace Server.Network
|
|||
{
|
||||
state.SendCompactShardStats(
|
||||
(uint)(Core.Uptime / 1000),
|
||||
TcpServer.Instances.Count - 1, // Shame if you modify this!
|
||||
NetState.Instances.Count - 1, // Shame if you modify this!
|
||||
World.Items.Count,
|
||||
World.Mobiles.Count,
|
||||
GC.GetTotalMemory(false)
|
||||
|
|
@ -50,7 +50,7 @@ namespace Server.Network
|
|||
state.SendExtendedShardStats(
|
||||
ServerList.ServerName,
|
||||
(int)(Core.Uptime / ticksInHour),
|
||||
TcpServer.Instances.Count - 1, // Shame if you modify this!
|
||||
NetState.Instances.Count - 1, // Shame if you modify this!
|
||||
World.Items.Count,
|
||||
World.Mobiles.Count,
|
||||
(int)(GC.GetTotalMemory(false) / 1024)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue