diff --git a/Projects/Server/Events/SocketConnectionEvent.cs b/Projects/Server/Events/SocketConnectionEvent.cs
new file mode 100644
index 000000000..9ac96dcd3
--- /dev/null
+++ b/Projects/Server/Events/SocketConnectionEvent.cs
@@ -0,0 +1,41 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2023 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: SocketConnectionEvent.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 . *
+ *************************************************************************/
+
+using System;
+using System.Net.Sockets;
+using System.Runtime.CompilerServices;
+
+namespace Server;
+
+public class SocketConnectEventArgs
+{
+ public SocketConnectEventArgs(Socket c)
+ {
+ Connection = c;
+ AllowConnection = true;
+ }
+
+ public Socket Connection { get; }
+
+ public bool AllowConnection { get; set; }
+}
+
+public static partial class EventSink
+{
+ public static event Action SocketConnect;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void InvokeSocketConnect(SocketConnectEventArgs e) => SocketConnect?.Invoke(e);
+}
diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs
index 79fa61788..13577440d 100644
--- a/Projects/Server/Main.cs
+++ b/Projects/Server/Main.cs
@@ -336,6 +336,8 @@ public static class Core
World.WaitForWriteCompletion();
World.ExitSerializationThreads();
+ PingServer.Shutdown();
+ TcpServer.Shutdown();
if (!_crashed)
{
diff --git a/Projects/Server/Network/Firewall/Firewall.cs b/Projects/Server/Network/Firewall/Firewall.cs
index f35046d40..e80050416 100644
--- a/Projects/Server/Network/Firewall/Firewall.cs
+++ b/Projects/Server/Network/Firewall/Firewall.cs
@@ -14,83 +14,21 @@
*************************************************************************/
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 _isBlockedCache = new();
- private static readonly ConcurrentQueue<(IFirewallEntry FirewallyEntry, bool Remove)> _firewallQueue = new();
private static readonly SortedSet _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);
- }
- }
- }
+ public static SortedSet FirewallSet => _firewallSet;
internal static bool IsBlocked(IPAddress address)
{
@@ -128,22 +66,32 @@ public static class Firewall
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void AddEntry(IFirewallEntry firewallEntry)
+ public static bool Add(IFirewallEntry firewallEntry)
{
- _firewallSet.Add(firewallEntry);
- _isBlockedCache.Clear();
+ if (_firewallSet.Add(firewallEntry))
+ {
+ _isBlockedCache.Clear();
+ return true;
+ }
+
+ return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void RemoveEntry(IFirewallEntry entry)
+ public static bool Remove(IFirewallEntry entry)
{
if (entry == null)
{
- return;
+ return false;
}
- _firewallSet.Remove(entry);
- _isBlockedCache.Clear();
+ if (_firewallSet.Remove(entry))
+ {
+ _isBlockedCache.Clear();
+ return true;
+ }
+
+ return false;
}
private class InternalValidationEntry : BaseFirewallEntry
diff --git a/Projects/Server/Network/IPLimiter.cs b/Projects/Server/Network/IPLimiter.cs
index 046ccd639..dc4717645 100644
--- a/Projects/Server/Network/IPLimiter.cs
+++ b/Projects/Server/Network/IPLimiter.cs
@@ -51,22 +51,32 @@ public static class IPLimiter
var now = Core.Now;
- CheckThrottledAddresses(now);
+ IPAccessLog accessLog;
+
+ while (_throttledAddresses.Count > 0)
+ {
+ accessLog = _throttledAddresses.Min;
+ if (now <= accessLog.Expiration)
+ {
+ break;
+ }
+
+ _throttledAddresses.Remove(accessLog);
+ }
_accessCheck.IPAddress = ourAddress;
- if (_connectionAttempts.TryGetValue(_accessCheck, out var accessLog))
+ if (_connectionAttempts.TryGetValue(_accessCheck, out accessLog))
{
_connectionAttempts.Remove(accessLog);
accessLog.Count++;
+ accessLog.Expiration = now + ConnectionAttemptsDuration;
if (now <= accessLog.Expiration && accessLog.Count >= MaxConnections)
{
- BlockConnection(now, accessLog);
+ _throttledAddresses.Add(accessLog);
return false;
}
-
- accessLog.Expiration = now + ConnectionAttemptsDuration;
}
else
{
@@ -79,26 +89,6 @@ public static class IPLimiter
return true;
}
- private static void BlockConnection(DateTime now, IPAccessLog accessLog)
- {
- accessLog.Expiration = now + ConnectionAttemptsDuration;
- _throttledAddresses.Add(accessLog);
- }
-
- private static void CheckThrottledAddresses(DateTime now)
- {
- while (_throttledAddresses.Count > 0)
- {
- var accessLog = _throttledAddresses.Min;
- if (now <= accessLog.Expiration)
- {
- break;
- }
-
- _throttledAddresses.Remove(accessLog);
- }
- }
-
private class IPAccessLog : IComparable
{
public IPAddress IPAddress;
diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs
index 80b095d72..ad676c5fd 100755
--- a/Projects/Server/Network/NetState/NetState.cs
+++ b/Projects/Server/Network/NetState/NetState.cs
@@ -32,8 +32,6 @@ using System.Runtime.InteropServices;
namespace Server.Network;
-public delegate void NetStateCreatedCallback(NetState ns);
-
public delegate void DecodePacket(Span buffer, ref int length);
public delegate int EncodePacket(ReadOnlySpan inputBuffer, Span outputBuffer);
@@ -56,8 +54,6 @@ public partial class NetState : IComparable, IValueLinkListNode _throttled = new(256);
private static readonly Queue _throttledPending = new(256);
- public static NetStateCreatedCallback CreatedCallback { get; set; }
-
private static readonly SortedSet _connecting = new(NetStateConnectingComparer.Instance);
private static readonly HashSet _instances = new(2048);
public static IReadOnlySet Instances => _instances;
@@ -68,8 +64,8 @@ public partial class NetState : IComparable, IValueLinkListNode, IValueLinkListNode, IValueLinkListNode= 0 and < 0x100)
{
+ _packetThrottles ??= new long[0x100];
_packetThrottles[packetID] = Core.TickCount;
}
}
- public long GetPacketTime(int packetID) => packetID is >= 0 and < 0x100 ? _packetThrottles[packetID] : 0;
+ public long GetPacketTime(int packetID) =>
+ packetID is >= 0 and < 0x100 && _packetThrottles != null ? _packetThrottles[packetID] : 0;
private void UpdatePacketCount(int packetID)
{
if (packetID is >= 0 and < 0x100)
{
+ _packetCounts ??= new long[0x100];
_packetCounts[packetID]++;
}
}
public int CheckPacketCounts()
{
+ if (_packetCounts == null)
+ {
+ return 0;
+ }
+
for (int i = 0; i < _packetCounts.Length; i++)
{
long count = _packetCounts[i];
@@ -917,7 +925,7 @@ public partial class NetState : IComparable, IValueLinkListNode, IValueLinkListNode 0)
{
var ns = _throttled.Dequeue();
@@ -972,7 +969,7 @@ public partial class NetState : IComparable, IValueLinkListNode 0)
{
diff --git a/Projects/Server/Network/PingServer.cs b/Projects/Server/Network/PingServer.cs
index 31db1ad08..41a59468b 100644
--- a/Projects/Server/Network/PingServer.cs
+++ b/Projects/Server/Network/PingServer.cs
@@ -16,7 +16,6 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
-using System.Threading;
using Server.Logging;
namespace Server.Network;
@@ -47,8 +46,8 @@ public static class PingServer
return;
}
- HashSet listeningAddresses = new HashSet();
- List listeners = new List();
+ HashSet listeningAddresses = [];
+ List listeners = [];
foreach (var serverIpep in ServerConfiguration.Listeners)
{
@@ -70,7 +69,7 @@ public static class PingServer
}
listeners.Add(listener);
- new Thread(BeginAcceptingUdpRequest).Start(listener);
+ BeginAcceptingUdpRequest(listener);
}
foreach (var ipep in listeningAddresses)
@@ -140,4 +139,12 @@ public static class PingServer
}
}
}
+
+ public static void Shutdown()
+ {
+ foreach (var listener in Listeners)
+ {
+ listener.Close();
+ }
+ }
}
diff --git a/Projects/Server/Network/TcpServer.cs b/Projects/Server/Network/TcpServer.cs
index 8ec399bd0..be526f703 100644
--- a/Projects/Server/Network/TcpServer.cs
+++ b/Projects/Server/Network/TcpServer.cs
@@ -13,8 +13,6 @@
* along with this program. If not, see . *
*************************************************************************/
-using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -22,7 +20,6 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
-using System.Threading;
using Server.Logging;
using Server.Misc;
@@ -32,30 +29,52 @@ public static class TcpServer
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer));
- private const long ListenerErrorMessageDelay = 10000; // 10 seconds
-
- private static long _nextMaximumSocketsReachedMessage;
- private static readonly SemaphoreSlim _queueSemaphore = new(0);
- private static readonly ConcurrentQueue _connectingQueue = [];
- private static Thread _processConnectionsThread;
-
- // Sanity. 256 * 1024 * 4096 = ~1.3GB of ram
- public static int MaxConnections { get; set; }
+ // AccountLoginReject BadComm
+ private static readonly byte[] _socketRejected = [0x82, 0xFF];
public static IPEndPoint[] ListeningAddresses { get; private set; }
public static Socket[] Listeners { get; private set; }
- public static ConcurrentQueue ConnectedQueue { get; } = [];
-
- public static void Configure()
- {
- MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", 4096);
- }
-
public static void Start()
{
- _processConnectionsThread = new Thread(ProcessConnections);
- _processConnectionsThread.Start();
+ HashSet listeningAddresses = new HashSet();
+ List listeners = new List();
+ foreach (var ipep in ServerConfiguration.Listeners)
+ {
+ var listener = CreateListener(ipep);
+ if (listener == null)
+ {
+ continue;
+ }
+
+ if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
+ {
+ listeningAddresses.UnionWith(GetListeningAddresses(ipep));
+ }
+ else
+ {
+ listeningAddresses.Add(ipep);
+ }
+
+ listeners.Add(listener);
+ BeginAcceptingSockets(listener);
+ }
+
+ foreach (var ipep in listeningAddresses)
+ {
+ logger.Information("Listening: {Address}:{Port}", ipep.Address, ipep.Port);
+ }
+
+ ListeningAddresses = listeningAddresses.ToArray();
+ Listeners = listeners.ToArray();
+ }
+
+ public static void Shutdown()
+ {
+ foreach (var listener in Listeners)
+ {
+ listener.Close();
+ }
}
public static IEnumerable GetListeningAddresses(IPEndPoint ipep) =>
@@ -104,104 +123,48 @@ public static class TcpServer
return null;
}
- private static async void BeginAcceptingSockets(object state)
+ private static async void BeginAcceptingSockets(Socket listener)
{
- if (state is not Socket listener)
- {
- return;
- }
-
- var cancellationToken = Core.ClosingTokenSource.Token;
-
- while (!cancellationToken.IsCancellationRequested)
+ while (!Core.Closing)
{
+ Socket socket = null;
try
{
- var socket = await listener.AcceptAsync(cancellationToken);
- _connectingQueue.Enqueue(socket);
- _queueSemaphore.Release();
- }
- catch (OperationCanceledException)
- {
- return;
+ socket = await listener.AcceptAsync();
+ var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address;
+
+ if (!IPLimiter.Verify(remoteIP))
+ {
+ TraceDisconnect("Past IP limit threshold", remoteIP);
+ logger.Debug("{Address} Past IP limit threshold", remoteIP);
+ }
+ else if (Firewall.IsBlocked(remoteIP))
+ {
+ TraceDisconnect("Firewalled", remoteIP);
+ logger.Debug("{Address} Firewalled", remoteIP);
+ }
+ else
+ {
+ var args = new SocketConnectEventArgs(socket);
+ EventSink.InvokeSocketConnect(args);
+
+ if (args.AllowConnection)
+ {
+ _ = new NetState(socket);
+ continue;
+ }
+
+ TraceDisconnect("Rejected by socket event handler", remoteIP);
+
+ // Reject the connection
+ socket.Send(_socketRejected, SocketFlags.None);
+ }
+
+ CloseSocket(socket);
}
catch
{
- // ignored
- }
- }
-
- listener.Close();
- }
-
- private static void ProcessConnections()
- {
- var cancellationToken = Core.ClosingTokenSource.Token;
- HashSet listeningAddresses = [];
- List listeners = [];
-
- foreach (var ipep in ServerConfiguration.Listeners)
- {
- var listener = CreateListener(ipep);
- if (listener == null)
- {
- continue;
- }
-
- bool added;
-
- if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
- {
- var beforeCount = listeningAddresses.Count;
- listeningAddresses.UnionWith(GetListeningAddresses(ipep));
- added = listeningAddresses.Count > beforeCount;
- }
- else
- {
- added = listeningAddresses.Add(ipep);
- }
-
- if (added)
- {
- listeners.Add(listener);
-
- new Thread(BeginAcceptingSockets).Start(listener);
- }
- }
-
- foreach (var ipep in listeningAddresses)
- {
- logger.Information("Listening: {Address}:{Port}", ipep.Address, ipep.Port);
- }
-
- ListeningAddresses = listeningAddresses.ToArray();
- Listeners = listeners.ToArray();
-
- while (true)
- {
- try
- {
- while (!cancellationToken.IsCancellationRequested)
- {
- _queueSemaphore.Wait(cancellationToken);
-
- Firewall.ProcessQueue();
-
- if (_connectingQueue.TryDequeue(out var socket))
- {
- ProcessConnection(socket);
- }
- }
-
- return;
- }
- catch (OperationCanceledException)
- {
- return;
- }
- catch (Exception e)
- {
- logger.Error(e, "Error occurred in ProcessConnections");
+ CloseSocket(socket);
}
}
}
@@ -213,71 +176,9 @@ public static class TcpServer
{
socket.Shutdown(SocketShutdown.Both);
}
- catch
+ finally
{
- // ignored
- }
-
- socket.Close();
- }
-
- private static void ProcessConnection(Socket socket)
- {
- try
- {
- var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address;
-
- if (NetState.Instances.Count >= MaxConnections)
- {
- var ticks = Core.TickCount;
-
- if (ticks - _nextMaximumSocketsReachedMessage > 0)
- {
- if (socket.RemoteEndPoint is IPEndPoint ipep)
- {
- var ip = ipep.Address.ToString();
- logger.Warning("{Address} Failed (Maximum connections reached)", ip);
- }
-
- _nextMaximumSocketsReachedMessage = ticks + ListenerErrorMessageDelay;
- }
-
- CloseSocket(socket);
- return;
- }
-
- var firewalled = Firewall.IsBlocked(remoteIP);
- if (!firewalled)
- {
- var socketConnectedArgs = new SocketConnectedEventArgs(socket);
- EventSink.InvokeSocketConnected(socketConnectedArgs);
- firewalled = !socketConnectedArgs.ConnectionAllowed;
- }
-
- if (firewalled)
- {
- TraceDisconnect("Firewalled", remoteIP);
- logger.Debug("{Address} Firewalled", remoteIP);
-
- CloseSocket(socket);
- return;
- }
-
- if (!IPLimiter.Verify(remoteIP))
- {
- TraceDisconnect("Past IP limit threshold", remoteIP);
- logger.Debug("{Address} Past IP limit threshold", remoteIP);
-
- CloseSocket(socket);
- return;
- }
-
- var ns = new NetState(socket);
- ConnectedQueue.Enqueue(ns);
- }
- catch
- {
- // ignored
+ socket.Close();
}
}
@@ -299,22 +200,4 @@ public static class TcpServer
// ignored
}
}
-
- public static class EventSink
- {
- // IMPORTANT: This is executed asynchronously! Do not run any game thread code on these delegates!
- public static event Action SocketConnected;
-
- internal static void InvokeSocketConnected(SocketConnectedEventArgs context) =>
- SocketConnected?.Invoke(context);
- }
-
- public class SocketConnectedEventArgs
- {
- public Socket Socket { get; }
-
- public bool ConnectionAllowed { get; set; } = true;
-
- internal SocketConnectedEventArgs(Socket socket) => Socket = socket;
- }
}
diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs
index 9cbcef016..43d6c1bef 100644
--- a/Projects/UOContent/Gumps/AdminGump.cs
+++ b/Projects/UOContent/Gumps/AdminGump.cs
@@ -169,7 +169,7 @@ namespace Server.Gumps
AddLabel(150, 150, LabelHue, banned.ToString());
AddLabel(20, 170, LabelHue, "Firewalled:");
- AddLabel(150, 170, LabelHue, AdminFirewall.Set.Count.ToString());
+ AddLabel(150, 170, LabelHue, Firewall.FirewallSet.Count.ToString());
AddLabel(20, 190, LabelHue, "Clients:");
AddLabel(150, 190, LabelHue, NetState.Instances.Count.ToString());
@@ -1161,7 +1161,7 @@ namespace Server.Gumps
{
AddFirewallHeader();
- m_List ??= AdminFirewall.Set.ToList