Permissions, RateControl and Auditing

This commit is contained in:
2026-07-16 14:01:08 +03:00
parent 3a1b95dbc5
commit ba64a0c95a
62 changed files with 6095 additions and 2366 deletions
+151 -55
View File
@@ -37,13 +37,20 @@ namespace Esiur.Net;
public abstract class NetworkServer<TConnection> : IDestructible where TConnection : NetworkConnection, new()
{
private Sockets.ISocket listener;
private volatile Sockets.ISocket listener;
private readonly object lifecycleLock = new object();
public AutoList<TConnection, NetworkServer<TConnection>> Connections { get; internal set; }
private Thread thread;
private Timer timer;
/// <summary>
/// Maximum time allowed for an accepted socket to finish protocol initialization
/// (for example, a TLS handshake). A zero or negative value disables the deadline.
/// </summary>
public TimeSpan ConnectionInitializationTimeout { get; set; } = TimeSpan.FromSeconds(10);
public event DestroyedEvent OnDestroy;
@@ -78,68 +85,142 @@ public abstract class NetworkServer<TConnection> : IDestructible where TConnecti
public void Start(Sockets.ISocket socket)//, uint timeout, uint clock)
{
if (listener != null)
return;
if (socket == null)
throw new ArgumentNullException(nameof(socket));
Connections = new AutoList<TConnection, NetworkServer<TConnection>>(this);
if (Timeout > 0 & Clock > 0)
lock (lifecycleLock)
{
timer = new Timer(MinuteThread, null, TimeSpan.FromMinutes(0), TimeSpan.FromSeconds(Clock));
}
if (listener != null)
return;
Connections = new AutoList<TConnection, NetworkServer<TConnection>>(this);
listener = socket;
thread = new Thread(new ThreadStart(() =>
{
while (true)
if (Timeout > 0 && Clock > 0)
{
timer = new Timer(MinuteThread, null, TimeSpan.FromMinutes(0), TimeSpan.FromSeconds(Clock));
}
listener = socket;
// Bind this thread to this particular Start invocation. If the server is
// stopped and restarted before the old Accept call unwinds, the old thread
// must not begin accepting from the replacement listener.
thread = new Thread(() => AcceptLoop(socket))
{
IsBackground = true
};
thread.Start();
}
}
private void AcceptLoop(ISocket activeListener)
{
while (ReferenceEquals(listener, activeListener))
{
try
{
var acceptedSocket = activeListener.Accept();
if (acceptedSocket == null)
return;
TConnection connection = null;
var stopped = false;
// Admission and Stop's connection snapshot share this gate. Therefore
// either the connection is added before Stop snapshots it, or Stop wins
// and this accepted socket is closed without being exposed to the server.
lock (lifecycleLock)
{
if (!ReferenceEquals(listener, activeListener))
{
stopped = true;
}
else
{
connection = new TConnection();
connection.Assign(acceptedSocket);
Add(connection);
stopped = !ReferenceEquals(listener, activeListener);
}
}
if (stopped)
{
try { acceptedSocket.Close(); } catch { }
return;
}
// A derived server can reject admission (for example, due to a per-peer
// connection quota) by not adding the connection and closing its socket.
if (!Connections.Contains(connection))
continue;
try
{
var s = listener.Accept();
if (s == null)
{
//Global.Log("NetworkServer", LogType.Error, "sock == null");
return;
}
var c = new TConnection();
c.Assign(s);
Add(c);
// A derived server can reject admission (for example, due to a per-peer
// connection quota) by not adding the connection and closing its socket.
if (!Connections.Contains(c))
continue;
try
{
ClientConnected(c);
}
catch
{
// something wrong with the child.
}
s.Begin();
ClientConnected(connection);
}
catch (Exception ex)
catch
{
Global.Log(ex);
// something wrong with the child.
}
if (!ReferenceEquals(listener, activeListener))
{
try { connection.Close(); } catch { }
return;
}
// Some socket implementations perform a protocol handshake in Begin
// (notably SSLSocket). Never run that handshake on the single accept
// thread: a peer that stops mid-handshake would otherwise prevent all
// subsequent clients from being accepted.
_ = BeginAcceptedSocketAsync(acceptedSocket);
}
catch (Exception ex)
{
if (!ReferenceEquals(listener, activeListener))
return;
Global.Log(ex);
}
}
}
private async Task BeginAcceptedSocketAsync(ISocket socket)
{
try
{
var beginTask = AwaitSocketBeginAsync(socket);
var timeout = ConnectionInitializationTimeout;
if (timeout > TimeSpan.Zero)
{
var completed = await Task.WhenAny(beginTask, Task.Delay(timeout)).ConfigureAwait(false);
if (!ReferenceEquals(completed, beginTask))
{
try { socket.Close(); } catch { }
_ = beginTask.ContinueWith(
completedTask => _ = completedTask.Exception,
TaskContinuationOptions.OnlyOnFaulted);
return;
}
}
}));
thread.Start();
if (!await beginTask.ConfigureAwait(false))
try { socket.Close(); } catch { }
}
catch (Exception ex)
{
Global.Log("NetworkServer", LogType.Warning,
$"Accepted socket initialization failed: {ex.Message}");
try { socket.Close(); } catch { }
}
}
private static async Task<bool> AwaitSocketBeginAsync(ISocket socket)
=> await socket.BeginAsync();
//[Attribute]
public uint Timeout
@@ -160,25 +241,39 @@ public abstract class NetworkServer<TConnection> : IDestructible where TConnecti
public void Stop()
{
var port = 0;
ISocket currentListener = null;
TConnection[] connections = null;
Timer currentTimer = null;
try
{
var currentListener = listener;
lock (lifecycleLock)
{
currentListener = listener;
listener = null;
connections = Connections?.ToArray();
currentTimer = timer;
timer = null;
}
if (currentListener != null)
{
// Reading the endpoint can throw if the socket is already disposed (e.g. a second
// Stop or the finalizer after Destroy), so it is best-effort and only used for logging.
try { port = currentListener.LocalEndPoint.Port; } catch { }
try { currentListener.Close(); } catch { }
listener = null; // make Stop idempotent
}
foreach (TConnection con in Connections.ToArray())
try { con.Close(); } catch { }
if (connections != null)
{
foreach (TConnection con in connections)
try { con.Close(); } catch { }
}
}
finally
{
Global.Log("NetworkServer", LogType.Warning, $"Server@{port} is down.");
try { currentTimer?.Dispose(); } catch { }
Global.Log("NetworkServer", LogType.Warning, $"Server on port {port} is down.");
}
}
@@ -200,7 +295,8 @@ public abstract class NetworkServer<TConnection> : IDestructible where TConnecti
{
get
{
return listener.State == SocketState.Listening;
var currentListener = listener;
return currentListener != null && currentListener.State == SocketState.Listening;
}
}