1079 lines
36 KiB
Markdown
1079 lines
36 KiB
Markdown
|
|
# EonaCat.Connections
|
|
|
|
A high-performance, production-ready .netStandard 2.0, .NET Framework 4.8+ / .NET 8 compatible library for building scalable TCP/UDP servers and clients with optional TLS encryption and application-layer AES encryption.
|
|
|
|
## Features
|
|
|
|
### Core Capabilities
|
|
- **High Performance**: Event-driven, asynchronous architecture with minimal allocations
|
|
- **Massive Scalability**: Handles tens of thousands of concurrent connections using SocketAsyncEventArgs (SAEA)
|
|
- **Dual Protocol Support**: TCP and UDP with independent or mixed configurations
|
|
- **TLS/SSL Encryption**: Secure TCP connections using SslStream with certificate support
|
|
- **Application-Layer Encryption**: AES-CBC + PBKDF2_SHA256 encryption for both TCP and UDP payloads
|
|
- **Low Latency**: Optimized for millisecond-level response times
|
|
|
|
### Advanced Features
|
|
- **Auto-Reconnection**: Automatic client reconnection with configurable retry logic and exponential backoff
|
|
- **Heartbeat/Ping-Pong**: Keep-alive mechanism to detect dead connections
|
|
- **Idle Detection**: Automatic detection and handling of idle connections with configurable timeout
|
|
- **Message Framing**: Support for delimiter-based, length-prefixed, or raw message modes
|
|
- **Request-Response Patterns**: Send message and wait for response with timeout support
|
|
- **Broadcast Messaging**: Send to all connected clients simultaneously
|
|
- **Client-to-Client Messaging**: Direct peer communication through the server
|
|
- **Nicknames**: Optional client identification system
|
|
- **Health API**: Lightweight REST API for monitoring server health and statistics
|
|
- **Status Reporting**: Auto-generated HTML status pages showing connected clients and throughput
|
|
- **Network Monitoring**: Built-in network health monitoring and outage detection
|
|
- **SSL Diagnostics**: Detailed SSL handshake diagnostics and performance metrics
|
|
- **Connection Management**: Full lifecycle management with detailed disconnect reasons
|
|
|
|
## Design Goals
|
|
|
|
- High performance and low latency
|
|
- Scalable to tens of thousands of concurrent connections
|
|
- Scalable socket I/O via SocketAsyncEventArgs (SAEA) for raw TCP and UDP
|
|
- TLS (SSL) over TCP using SslStream (built-in)
|
|
- Optional encryption (AES-CBC + PBKDF2_SHA256) for TCP/UDP payloads
|
|
- Minimal allocations, event-driven callbacks
|
|
- For highest throughput, run x64, enable LargePage, set appropriate Socket options and OS registry tuning
|
|
|
|
|
|
|
|
## Configuration
|
|
|
|
The `Configuration` class provides extensive customization options:
|
|
|
|
### Connection Settings
|
|
- **Protocol**: `TCP` or `UDP` (default: TCP)
|
|
- **Host**: Server address for clients (default: 127.0.0.1)
|
|
- **Port**: Network port (default: 8080)
|
|
- **MaxConnections**: Maximum concurrent connections (default: 100000)
|
|
- **ConnectionTimeout**: Timeout for connection attempts (default: 30s)
|
|
|
|
### TLS/SSL Settings
|
|
- **Certificate**: X509Certificate2 for server (enables TLS when set)
|
|
- **AdditionalCertificates**: Additional certificates for chain validation
|
|
- **IsSelfSignedEnabled**: Allow self-signed certificates (testing only)
|
|
- **CheckCertificateRevocation**: Validate certificate revocation status
|
|
- **MutuallyAuthenticate**: Require mutual TLS authentication
|
|
- **UseSsl**: Read-only property (true if Certificate is set)
|
|
- **EnableSslDiagnostics**: Enable detailed SSL handshake diagnostics
|
|
- **AllowTlsRenegotiation**: Allow TLS renegotiation
|
|
|
|
### SSL Retry & Handshake Configuration
|
|
- **SSLMaxRetries**: Maximum SSL handshake retry attempts (0 = unlimited, default: 0)
|
|
- **SSLTimeoutInSeconds**: SSL handshake timeout (default: 35)
|
|
- **SSLRetryDelayInSeconds**: Delay between SSL retries (default: 5)
|
|
- **UseExponentialBackoffForSslRetries**: Enable exponential backoff (default: false)
|
|
- **SSLRetryDelayMaxSeconds**: Maximum retry delay when exponential backoff enabled (default: 60)
|
|
|
|
### Encryption Settings
|
|
- **UseAesEncryption**: Enable AES-CBC encryption (default: false)
|
|
- **AesPassword**: Password for AES key derivation
|
|
- **UseBigEndian**: Use big-endian byte order (default: false)
|
|
|
|
### Keep-Alive & Heartbeat
|
|
- **EnableKeepAlive**: Enable TCP keep-alive probes (default: true)
|
|
- **KeepAliveTimeSeconds**: Time before first keep-alive probe (default: 60)
|
|
- **KeepAliveIntervalSeconds**: Interval between probes (default: 10)
|
|
- **KeepAliveRetryCount**: Unacked probes before disconnect (default: 10)
|
|
- **EnableHeartbeat**: Enable ping-pong heartbeat (default: false)
|
|
- **HeartbeatIntervalSeconds**: Heartbeat interval (default: 5)
|
|
- **EnablePingPongLogs**: Log ping-pong messages (default: false)
|
|
- **DisconnectOnMissedPong**: Disconnect on missed pong response (default: false)
|
|
|
|
### Message Framing
|
|
- **MessageFraming**: `None`, `Delimiter`, or `LengthPrefixed` (default: None)
|
|
- **Delimiter**: Message separator bytes (default: %)
|
|
- **LengthPrefixedLength**: Prefix byte count for length-prefixed mode (default: 4)
|
|
- **MAX_MESSAGE_SIZE**: Maximum message size (default: 100 MB)
|
|
|
|
### Idle Detection & Timeout
|
|
- **IdleTimeoutSeconds**: Idle connection timeout, 0 = disabled (default: 30)
|
|
|
|
### Socket Configuration
|
|
- **BufferSize**: Socket buffer size (default: ExtraLarge ~65K)
|
|
- **EnableNagle**: Enable Nagle's algorithm (default: false)
|
|
- **EnableRST**: Send RST flag on close (default: false)
|
|
|
|
### Auto-Reconnection (Client)
|
|
- **EnableAutoReconnect**: Auto-reconnect on disconnect (default: true)
|
|
- **ReconnectDelayInSeconds**: Delay before reconnection attempt (default: 5)
|
|
- **MaxReconnectAttempts**: Max reconnect attempts, 0 = unlimited (default: 0)
|
|
|
|
### Logging & Diagnostics
|
|
- **EnableConnectionDebugLogs**: Log connection lifecycle events (default: false)
|
|
|
|
### HTML Status & Health API
|
|
- **EnableAutoHtmlReports**: Generate periodic HTML error reports (default: false)
|
|
- **HtmlReportOutputDirectory**: Report output directory (default: ./reports)
|
|
- **HtmlReportIntervalSeconds**: Report generation interval (default: 60)
|
|
- **EnableServerStatusPage**: Generate HTML status page with client list (default: false)
|
|
- **ServerStatusPageIntervalSeconds**: Status page update interval (default: 5)
|
|
- **EnableHealthApi**: Start lightweight REST health API (default: false)
|
|
- **HealthApiPort**: Health API port, 0 = random (default: 0)
|
|
- **HealthApiBindAddress**: Health API bind address (default: 127.0.0.1)
|
|
|
|
### Certificate Management
|
|
- **TrustedThumbprints**: List of trusted certificate thumbprints for validation
|
|
- **CheckAgainstInternalTrustedCertificates**: Validate against Windows certificate store (default: true)
|
|
|
|
|
|
|
|
## NetworkServer API
|
|
|
|
### Events
|
|
|
|
```csharp
|
|
// Connection events
|
|
public event EventHandler<ConnectionEventArgs> OnConnected;
|
|
public event EventHandler<ConnectionEventArgs> OnConnectedWithNickname;
|
|
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
|
|
|
// Data events
|
|
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
|
|
|
// Error events
|
|
public event EventHandler<ErrorEventArgs> OnSslError;
|
|
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
|
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
|
public event EventHandler<ErrorEventArgs> OnSocketError;
|
|
|
|
// Heartbeat events
|
|
public event EventHandler<PingEventArgs> OnClientPingResponse;
|
|
public event EventHandler<PingEventArgs> OnPongMissed;
|
|
|
|
// Idle/Timeout events
|
|
public event EventHandler<IdleEventArgs> OnIdleTimeout;
|
|
|
|
// Logging
|
|
public event EventHandler<string> OnLog;
|
|
```
|
|
|
|
### Properties
|
|
|
|
```csharp
|
|
// Server Status
|
|
public bool IsStarted { get; }
|
|
public int Port { get; }
|
|
public int ActiveConnections { get; }
|
|
public int MaxConnections { get; }
|
|
public ProtocolType Protocol { get; }
|
|
|
|
// Security Status
|
|
public bool IsSecure { get; } // TLS or AES enabled
|
|
public bool IsEncrypted { get; } // AES enabled
|
|
|
|
// Statistics
|
|
public long DroppedPackets { get; }
|
|
public long DroppedConnections { get; }
|
|
public long TotalConnections { get; }
|
|
public long BytesSent { get; }
|
|
public long BytesReceived { get; }
|
|
public long MessagesSent { get; }
|
|
public long MessagesReceived { get; }
|
|
public double MessagesPerSecond { get; }
|
|
public TimeSpan Uptime { get; }
|
|
public DateTime StartTime { get; }
|
|
public DateTime LastDataSent { get; }
|
|
public DateTime LastDataReceived { get; }
|
|
|
|
// Status Pages
|
|
public ServerStatusPage ServerStatus { get; }
|
|
public SocketStatusPage StatusPage { get; }
|
|
public HealthApiServer HealthApi { get; }
|
|
|
|
// Debug
|
|
public bool DEBUG_DATA_SEND { get; set; }
|
|
public bool DEBUG_DATA_RECEIVED { get; set; }
|
|
```
|
|
|
|
### Core Methods
|
|
|
|
```csharp
|
|
// Lifecycle
|
|
public Task StartAsync();
|
|
public void Stop();
|
|
public async Task StopAsync();
|
|
public Task StartTcpServerAsync();
|
|
public async Task StartUdpServerAsync(CancellationToken token);
|
|
public async Task StopTcpServerAsync();
|
|
|
|
// Client Management
|
|
public Dictionary<string, Connection> GetClients();
|
|
public List<Connection> GetClient(string clientId);
|
|
public async Task DisconnectClientAsync(string clientId, DisconnectReason reason, Exception? exception);
|
|
|
|
// Messaging - Send to Specific Client
|
|
public async Task<bool> SendToClientAsync(string clientId, byte[] data);
|
|
public async Task<bool> SendToClientAsync(string clientId, string message);
|
|
|
|
// Messaging - Client-to-Client
|
|
public async Task<bool> SendFromClientToClientAsync(string fromClientId, string toClientId, byte[] data);
|
|
public async Task<bool> SendFromClientToClientAsync(string fromClientId, string toClientId, string message);
|
|
|
|
// Broadcast Messaging
|
|
public async Task<bool> BroadcastAsync(byte[] data);
|
|
public async Task<bool> BroadcastAsync(string message);
|
|
|
|
// Request-Response Patterns
|
|
public async Task<DataReceivedEventArgs> SendToClientAndWaitForResponseAsync(
|
|
string clientId, byte[] data, TimeSpan? timeout = null);
|
|
public async Task<DataReceivedEventArgs> SendToClientAndWaitForResponseAsync(
|
|
string clientId, string message, TimeSpan? timeout = null);
|
|
|
|
public async Task<DataReceivedEventArgs> SendFromClientToClientAndWaitForResponseAsync(
|
|
string fromClientId, string toClientId, byte[] data, TimeSpan? timeout = null);
|
|
public async Task<DataReceivedEventArgs> SendFromClientToClientAndWaitForResponseAsync(
|
|
string fromClientId, string toClientId, string message, TimeSpan? timeout = null);
|
|
|
|
public async Task<List<DataReceivedEventArgs>> BroadcastAndWaitForResponsesAsync(
|
|
byte[] data, TimeSpan? timeout = null);
|
|
public async Task<List<DataReceivedEventArgs>> BroadcastAndWaitForResponsesAsync(
|
|
string message, TimeSpan? timeout = null);
|
|
|
|
// Configuration
|
|
public void SetSeparator(string separator);
|
|
public void SetSeparator(byte[] separator);
|
|
|
|
// Statistics
|
|
public Stats GetStats();
|
|
|
|
// Cleanup
|
|
public void Dispose();
|
|
```
|
|
|
|
|
|
|
|
## NetworkClient API
|
|
|
|
### Events
|
|
|
|
```csharp
|
|
// Connection events
|
|
public event EventHandler<ConnectionEventArgs> OnConnected;
|
|
public event EventHandler<ConnectionEventArgs> OnNicknameSend;
|
|
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
|
|
|
// Data events
|
|
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
|
|
|
// Error events
|
|
public event EventHandler<ErrorEventArgs> OnSslError;
|
|
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
|
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
|
public event EventHandler<ErrorEventArgs> OnSocketError;
|
|
|
|
// Heartbeat events
|
|
public event EventHandler<PingEventArgs> OnPingResponse;
|
|
public event EventHandler<PingEventArgs> OnPongResponse;
|
|
|
|
// Idle/Timeout events
|
|
public event EventHandler<IdleClientEventArgs> OnIdleTimeout;
|
|
|
|
// Logging
|
|
public event EventHandler<string> OnLog;
|
|
```
|
|
|
|
### Properties
|
|
|
|
```csharp
|
|
// Connection Status
|
|
public bool IsConnected { get; set; }
|
|
public int Port { get; }
|
|
|
|
// Connection Time Metrics
|
|
public DateTime ConnectionTime { get; }
|
|
public TimeSpan Uptime { get; }
|
|
public DateTime LastActive { get; }
|
|
public int IdleTimeInSeconds();
|
|
public int IdleTimeInMinutes();
|
|
public int IdleTimeInHours();
|
|
public int IdleTimeInDays();
|
|
public int ConnectedTimeInSeconds();
|
|
public int ConnectedTimeInMinutes();
|
|
public int ConnectedTimeInHours();
|
|
public int ConnectedTimeInDays();
|
|
|
|
// Security Status
|
|
public bool IsSecure { get; } // TLS or AES enabled
|
|
public bool IsEncrypted { get; } // AES enabled
|
|
public bool IsTcp { get; }
|
|
|
|
// Statistics
|
|
public long BytesSent { get; }
|
|
public long BytesReceived { get; }
|
|
public long MessagesSent { get; }
|
|
public long MessagesReceived { get; }
|
|
|
|
// Heartbeat
|
|
public bool DisconnectOnMissedPong { get; set; }
|
|
public bool EnableKeepAlive { get; set; }
|
|
|
|
// Auto-Reconnect
|
|
public bool IsAutoConnectStarted { get; }
|
|
public bool IsIdleTimeoutTriggered { get; }
|
|
|
|
// Debug
|
|
public bool DEBUG_DATA_SEND { get; set; }
|
|
public bool DEBUG_DATA_RECEIVED { get; set; }
|
|
```
|
|
|
|
### Core Methods
|
|
|
|
```csharp
|
|
// Connection Lifecycle
|
|
public async Task<bool> ConnectAsync();
|
|
public async Task DisconnectAsync();
|
|
public async Task DisconnectDueTimeoutAsync();
|
|
|
|
// Messaging
|
|
public async Task<bool> SendAsync(byte[] data);
|
|
public async Task<bool> SendAsync(string message);
|
|
|
|
// Nickname
|
|
public async Task<bool> SendNicknameAsync(string nickname);
|
|
|
|
// Request-Response
|
|
public async Task<DataReceivedEventArgs> SendAndWaitForResponseAsync(
|
|
byte[] data, TimeSpan? timeout = null);
|
|
public async Task<DataReceivedEventArgs> SendAndWaitForResponseAsync(
|
|
string message, TimeSpan? timeout = null);
|
|
public async Task<DataReceivedEventArgs> SendNicknameAndWaitForResponseAsync(
|
|
string nickname, TimeSpan? timeout = null);
|
|
|
|
// Cleanup
|
|
public void Dispose();
|
|
```
|
|
|
|
|
|
|
|
## Message Framing Modes
|
|
|
|
Three message framing modes are supported:
|
|
|
|
### 1. None (Default)
|
|
Raw bytes sent as-is, no frame boundaries marked:
|
|
```csharp
|
|
config.MessageFraming = FramingMode.None;
|
|
```
|
|
|
|
### 2. Delimiter
|
|
Messages separated by a delimiter byte sequence (default: %):
|
|
```csharp
|
|
config.MessageFraming = FramingMode.Delimiter;
|
|
config.Delimiter = Encoding.UTF8.GetBytes("%");
|
|
```
|
|
|
|
### 3. Length-Prefixed
|
|
Messages prefixed with their length as bytes:
|
|
```csharp
|
|
config.MessageFraming = FramingMode.LengthPrefixed;
|
|
config.LengthPrefixedLength = 4; // 4-byte prefix
|
|
```
|
|
|
|
|
|
|
|
## SSL/TLS Certificate Setup
|
|
|
|
### Generate self-signed certificate for TLS (TCP):
|
|
Run the following in PowerShell as Administrator:
|
|
|
|
```powershell
|
|
$cert = New-SelfSignedCertificate `
|
|
-DnsName "localhost" `
|
|
-CertStoreLocation "Cert:\LocalMachine\My" `
|
|
-KeyExportPolicy Exportable `
|
|
-NotAfter (Get-Date).AddYears(5) `
|
|
-FriendlyName "EonaCat Connections Test Certificate"
|
|
|
|
$password = ConvertTo-SecureString -String "p@ss" -Force -AsPlainText
|
|
|
|
Export-PfxCertificate `
|
|
-Cert "Cert:\LocalMachine\My\$($cert.Thumbprint)" `
|
|
-FilePath "C:\temp\server.pfx" `
|
|
-Password $password
|
|
```
|
|
|
|
This creates a self-signed certificate with password `p@ss` in `C:\temp\server.pfx`.
|
|
|
|
|
|
|
|
## Advanced Features
|
|
|
|
### Auto-Reconnection
|
|
The client automatically reconnects on disconnect:
|
|
```csharp
|
|
config.EnableAutoReconnect = true; // Enable auto-reconnect
|
|
config.ReconnectDelayInSeconds = 5; // Delay before retry
|
|
config.MaxReconnectAttempts = 0; // 0 = unlimited attempts
|
|
```
|
|
|
|
### Heartbeat (Ping-Pong)
|
|
Keep connections alive and detect dead connections:
|
|
```csharp
|
|
config.EnableHeartbeat = true;
|
|
config.HeartbeatIntervalSeconds = 5;
|
|
config.DisconnectOnMissedPong = true; // Disconnect if pong not received
|
|
```
|
|
|
|
### Idle Detection
|
|
Automatically disconnect idle connections:
|
|
```csharp
|
|
config.IdleTimeoutSeconds = 30; // 0 = disabled
|
|
```
|
|
|
|
### Request-Response Patterns
|
|
Send a message and wait for a response with timeout:
|
|
```csharp
|
|
// Server sends message and waits for client response
|
|
var response = await server.SendToClientAndWaitForResponseAsync(
|
|
clientId, "question", TimeSpan.FromSeconds(5));
|
|
|
|
if (response != null)
|
|
{
|
|
Console.WriteLine($"Got response: {response.StringData}");
|
|
}
|
|
|
|
// Client sends message and waits for server response
|
|
var response = await client.SendAndWaitForResponseAsync(
|
|
"answer", TimeSpan.FromSeconds(5));
|
|
```
|
|
|
|
### Health API
|
|
Enable a lightweight REST API for server monitoring:
|
|
```csharp
|
|
config.EnableHealthApi = true;
|
|
config.HealthApiPort = 0; // Random port
|
|
config.HealthApiBindAddress = IPAddress.Any; // For Docker/containers
|
|
|
|
// After server starts, access the API:
|
|
// http://localhost:{HealthApi.Port}/health
|
|
// http://localhost:{HealthApi.Port}/stats
|
|
```
|
|
|
|
### Status Pages
|
|
Auto-generate HTML status reports:
|
|
```csharp
|
|
config.EnableServerStatusPage = true;
|
|
config.ServerStatusPageIntervalSeconds = 5;
|
|
|
|
config.EnableAutoHtmlReports = true;
|
|
config.HtmlReportOutputDirectory = "./reports";
|
|
config.HtmlReportIntervalSeconds = 60;
|
|
```
|
|
|
|
### SSL Diagnostics
|
|
Enable detailed SSL handshake diagnostics:
|
|
```csharp
|
|
config.EnableSslDiagnostics = true;
|
|
// Access via: server.StatusPage.SslMetrics
|
|
```
|
|
|
|
### Network Monitoring
|
|
Monitor network health and detect outages:
|
|
```csharp
|
|
var monitor = new NetworkMonitor();
|
|
monitor.OnNetworkOutageDetected += (s, e) =>
|
|
{
|
|
Console.WriteLine($"Network outage detected: {e.Duration}");
|
|
};
|
|
```
|
|
|
|
|
|
|
|
## Server Example
|
|
|
|
```csharp
|
|
using EonaCat.Connections;
|
|
using EonaCat.Connections.Models;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
namespace EonaCat.Connections.Server.Example
|
|
{
|
|
public class Program
|
|
{
|
|
private static NetworkServer _server;
|
|
|
|
public static void Main(string[] args)
|
|
{
|
|
CreateServerAsync().GetAwaiter().GetResult();
|
|
|
|
while (true)
|
|
{
|
|
Console.Write("Enter message to broadcast (or 'exit' to quit): ");
|
|
var message = Console.ReadLine();
|
|
|
|
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_server.StopAsync().GetAwaiter().GetResult();
|
|
_server.Dispose();
|
|
Console.WriteLine("Server stopped.");
|
|
break;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(message))
|
|
{
|
|
_server.BroadcastAsync(message).GetAwaiter().GetResult();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static async Task CreateServerAsync()
|
|
{
|
|
var config = new Configuration
|
|
{
|
|
Protocol = ProtocolType.TCP,
|
|
Port = 1111,
|
|
Certificate = new X509Certificate2("server.pfx", "p@ss"), // Enables TLS
|
|
UseAesEncryption = true,
|
|
AesPassword = "EonaCat.Connections.Password",
|
|
MaxConnections = 100000,
|
|
EnableHeartbeat = true,
|
|
HeartbeatIntervalSeconds = 5,
|
|
IdleTimeoutSeconds = 30,
|
|
EnableHealthApi = true,
|
|
HealthApiPort = 8888,
|
|
};
|
|
|
|
_server = new NetworkServer(config);
|
|
|
|
// Connection events
|
|
_server.OnConnected += (sender, e) =>
|
|
Console.WriteLine($"[CONNECTED] Client {e.ClientId} from {e.RemoteEndPoint}");
|
|
|
|
_server.OnConnectedWithNickname += (sender, e) =>
|
|
Console.WriteLine($"[NICKNAME] Client {e.ClientId} identified as: {e.Nickname}");
|
|
|
|
// Data reception
|
|
_server.OnDataReceived += async (sender, e) =>
|
|
{
|
|
var clientName = e.HasNickname ? e.Nickname : e.ClientId;
|
|
Console.WriteLine($"[DATA] From {clientName}: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
|
|
|
|
// Echo response
|
|
if (e.IsBinary)
|
|
{
|
|
await _server.SendToClientAsync(e.ClientId, e.Data);
|
|
}
|
|
else
|
|
{
|
|
await _server.SendToClientAsync(e.ClientId, $"Echo: {e.StringData}");
|
|
}
|
|
};
|
|
|
|
// Disconnection
|
|
_server.OnDisconnected += (sender, e) =>
|
|
{
|
|
var clientName = e.HasNickname ? e.Nickname : e.ClientId;
|
|
Console.WriteLine($"[DISCONNECTED] {clientName} ({e.DisconnectReason})");
|
|
};
|
|
|
|
// Error handling
|
|
_server.OnGeneralError += (sender, e) =>
|
|
Console.WriteLine($"[ERROR] {e.Message}");
|
|
|
|
_server.OnSslError += (sender, e) =>
|
|
Console.WriteLine($"[SSL ERROR] {e.Message}");
|
|
|
|
// Idle connections
|
|
_server.OnIdleTimeout += (sender, e) =>
|
|
Console.WriteLine($"[IDLE] Client disconnected due to timeout: {e.ClientId}");
|
|
|
|
// Heartbeat
|
|
_server.OnPongMissed += (sender, e) =>
|
|
Console.WriteLine($"[HEARTBEAT] Pong missed from {e.ClientId}");
|
|
|
|
Console.WriteLine("Server starting...");
|
|
await _server.StartAsync();
|
|
Console.WriteLine($"Server listening on port {_server.Port}");
|
|
Console.WriteLine($"Health API available on: http://localhost:8888/health");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
|
|
## Client Example
|
|
|
|
```csharp
|
|
using EonaCat.Connections;
|
|
using EonaCat.Connections.Models;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
namespace EonaCat.Connections.Client.Example
|
|
{
|
|
public class Program
|
|
{
|
|
private static NetworkClient _client;
|
|
|
|
public static async Task Main(string[] args)
|
|
{
|
|
await CreateClientAsync();
|
|
|
|
while (true)
|
|
{
|
|
if (!_client.IsConnected)
|
|
{
|
|
Console.WriteLine("Waiting for connection...");
|
|
await Task.Delay(1000);
|
|
continue;
|
|
}
|
|
|
|
Console.Write("Enter message to send (or 'exit' to quit): ");
|
|
var message = Console.ReadLine();
|
|
|
|
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
await _client.DisconnectAsync();
|
|
_client.Dispose();
|
|
break;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(message))
|
|
{
|
|
await _client.SendAsync(message);
|
|
|
|
// Optional: wait for response
|
|
var response = await _client.SendAndWaitForResponseAsync(
|
|
message, TimeSpan.FromSeconds(5));
|
|
|
|
if (response != null)
|
|
{
|
|
Console.WriteLine($"Server response: {response.StringData}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static async Task CreateClientAsync()
|
|
{
|
|
var config = new Configuration
|
|
{
|
|
Protocol = ProtocolType.TCP,
|
|
Host = "127.0.0.1",
|
|
Port = 1111,
|
|
Certificate = new X509Certificate2("client.pfx", "p@ss"), // For TLS (optional)
|
|
UseAesEncryption = true,
|
|
AesPassword = "EonaCat.Connections.Password",
|
|
EnableAutoReconnect = true,
|
|
ReconnectDelayInSeconds = 5,
|
|
MaxReconnectAttempts = 0, // Unlimited retries
|
|
EnableHeartbeat = true,
|
|
HeartbeatIntervalSeconds = 5,
|
|
IdleTimeoutSeconds = 30,
|
|
};
|
|
|
|
_client = new NetworkClient(config);
|
|
|
|
// Error handling
|
|
_client.OnGeneralError += (sender, e) =>
|
|
Console.WriteLine($"[ERROR] {e.Message}");
|
|
|
|
_client.OnSslError += (sender, e) =>
|
|
Console.WriteLine($"[SSL ERROR] {e.Message}");
|
|
|
|
// Connection events
|
|
_client.OnConnected += async (sender, e) =>
|
|
{
|
|
Console.WriteLine($"[CONNECTED] Connected to server at {e.RemoteEndPoint}");
|
|
|
|
// Set client nickname
|
|
await _client.SendNicknameAsync("TestUser");
|
|
|
|
// Send initial message
|
|
await _client.SendAsync("Hello from client!");
|
|
};
|
|
|
|
// Data reception
|
|
_client.OnDataReceived += (sender, e) =>
|
|
Console.WriteLine($"[SERVER] {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
|
|
|
|
// Disconnection
|
|
_client.OnDisconnected += (sender, e) =>
|
|
Console.WriteLine($"[DISCONNECTED] Disconnected from server ({e.DisconnectReason})");
|
|
|
|
// Idle/Timeout
|
|
_client.OnIdleTimeout += (sender, e) =>
|
|
Console.WriteLine($"[IDLE] Connection idle for {e.IdleTime} seconds");
|
|
|
|
// Heartbeat
|
|
_client.OnPingResponse += (sender, e) =>
|
|
Console.WriteLine($"[PING] Response time: {e.ResponseTime}ms");
|
|
|
|
Console.WriteLine("Client connecting...");
|
|
await _client.ConnectAsync();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
|
|
## Advanced Usage Examples
|
|
|
|
### Request-Response Pattern
|
|
|
|
```csharp
|
|
// Server: Wait for client to send data, then respond
|
|
_server.OnDataReceived += async (sender, e) =>
|
|
{
|
|
if (e.StringData == "QUERY")
|
|
{
|
|
await _server.SendToClientAsync(e.ClientId, "RESPONSE_DATA");
|
|
}
|
|
};
|
|
|
|
// Client: Send request and wait for response
|
|
var response = await _client.SendAndWaitForResponseAsync(
|
|
"QUERY", TimeSpan.FromSeconds(10));
|
|
Console.WriteLine($"Got: {response.StringData}");
|
|
|
|
// Server: Broadcast and wait for all responses
|
|
var responses = await _server.BroadcastAndWaitForResponsesAsync(
|
|
"QUESTION", TimeSpan.FromSeconds(15));
|
|
Console.WriteLine($"Received {responses.Count} responses");
|
|
```
|
|
|
|
### Client-to-Client Communication
|
|
|
|
```csharp
|
|
// Client A sends to Client B through server
|
|
await _server.SendFromClientToClientAsync(
|
|
"clientA_id", "clientB_id", "Direct message");
|
|
|
|
// Or with response
|
|
var response = await _server.SendFromClientToClientAndWaitForResponseAsync(
|
|
"clientA_id", "clientB_id", "Question for B");
|
|
```
|
|
|
|
### Message Framing
|
|
|
|
```csharp
|
|
// Delimiter-based framing
|
|
config.MessageFraming = FramingMode.Delimiter;
|
|
config.Delimiter = Encoding.UTF8.GetBytes("\n");
|
|
|
|
// Length-prefixed framing
|
|
config.MessageFraming = FramingMode.LengthPrefixed;
|
|
config.LengthPrefixedLength = 4; // 4-byte length prefix
|
|
|
|
// Custom separator
|
|
server.SetSeparator("||");
|
|
```
|
|
|
|
### Accessing Statistics
|
|
|
|
```csharp
|
|
// Server statistics
|
|
Console.WriteLine($"Active connections: {_server.ActiveConnections}");
|
|
Console.WriteLine($"Total connected: {_server.TotalConnections}");
|
|
Console.WriteLine($"Messages/sec: {_server.MessagesPerSecond}");
|
|
Console.WriteLine($"Data sent: {_server.BytesSent} bytes");
|
|
Console.WriteLine($"Data received: {_server.BytesReceived} bytes");
|
|
Console.WriteLine($"Uptime: {_server.Uptime}");
|
|
|
|
var stats = _server.GetStats();
|
|
Console.WriteLine($"Dropped packets: {stats.DroppedPackets}");
|
|
Console.WriteLine($"Dropped connections: {stats.DroppedConnections}");
|
|
|
|
// Client statistics
|
|
Console.WriteLine($"Connection time: {_client.ConnectedTimeInHours()} hours");
|
|
Console.WriteLine($"Idle time: {_client.IdleTimeInSeconds()} seconds");
|
|
Console.WriteLine($"Bytes sent: {_client.BytesSent}");
|
|
Console.WriteLine($"Messages sent: {_client.MessagesSent}");
|
|
```
|
|
|
|
### SSL/TLS with Certificate Validation
|
|
|
|
```csharp
|
|
// Server with certificate
|
|
var config = new Configuration
|
|
{
|
|
Certificate = new X509Certificate2("server.pfx", "password"),
|
|
MutuallyAuthenticate = true,
|
|
CheckCertificateRevocation = true,
|
|
};
|
|
|
|
// Client with certificate validation
|
|
var clientConfig = new Configuration
|
|
{
|
|
Host = "server.example.com",
|
|
Port = 443,
|
|
Certificate = new X509Certificate2("client.pfx", "password"),
|
|
MutuallyAuthenticate = true,
|
|
TrustedThumbprints = new List<string> { "CERT_THUMBPRINT" },
|
|
CheckCertificateRevocation = true,
|
|
};
|
|
```
|
|
|
|
### Encryption and Security
|
|
|
|
```csharp
|
|
// AES Encryption
|
|
config.UseAesEncryption = true;
|
|
config.AesPassword = "SecurePassword123";
|
|
|
|
// Both TLS and AES together
|
|
config.Certificate = new X509Certificate2("server.pfx", "pass");
|
|
config.UseAesEncryption = true;
|
|
config.AesPassword = "AdditionalPassword";
|
|
|
|
// Disable Nagle for low-latency
|
|
config.EnableNagle = false;
|
|
```
|
|
|
|
## Event Arguments Reference
|
|
|
|
### ConnectionEventArgs
|
|
- `string ClientId` - Unique client identifier
|
|
- `EndPoint RemoteEndPoint` - Client's remote address/port
|
|
- `string Nickname` - Client's nickname (if set)
|
|
- `bool HasNickname` - Whether client has a nickname
|
|
|
|
### DataReceivedEventArgs
|
|
- `string ClientId` - Sender client ID
|
|
- `byte[] Data` - Raw binary data
|
|
- `string StringData` - Data decoded as UTF-8 string
|
|
- `bool IsBinary` - True if binary data, false if text
|
|
- `bool HasNickname` - Whether sender has a nickname
|
|
- `string Nickname` - Sender's nickname (if set)
|
|
|
|
### ErrorEventArgs
|
|
- `string Message` - Error description
|
|
- `string ClientId` - Associated client (if applicable)
|
|
- `Exception Exception` - Inner exception details
|
|
|
|
### PingEventArgs
|
|
- `string ClientId` - Client that responded to ping
|
|
- `long ResponseTime` - Response time in milliseconds
|
|
|
|
### IdleEventArgs / IdleClientEventArgs
|
|
- `string ClientId` - Idle client ID
|
|
- `double IdleTime` - Idle duration in seconds
|
|
- `DisconnectReason Reason` - Reason for disconnection
|
|
|
|
## Disconnect Reasons
|
|
|
|
The `DisconnectReason` enum provides detailed disconnect information:
|
|
|
|
```csharp
|
|
public enum DisconnectReason
|
|
{
|
|
Unknown, // Unknown reason
|
|
RemoteClosed, // Remote peer closed connection
|
|
LocalClosed, // Local side closed connection
|
|
Timeout, // Connection timeout
|
|
Error, // General error
|
|
SSLError, // SSL/TLS handshake error
|
|
ServerShutdown, // Server shutting down
|
|
Reconnect, // Reconnecting (client)
|
|
ClientRequested, // Client requested disconnect
|
|
Forced, // Forcefully disconnected
|
|
NoPongReceived, // Heartbeat pong not received
|
|
ProtocolError, // Protocol violation
|
|
}
|
|
```
|
|
|
|
## Key Security Enhancements
|
|
|
|
### 1. New Security Validation Layer (SecurityValidator.cs)
|
|
|
|
**Location:** `EonaCat.Connections/Helpers/SecurityValidator.cs`
|
|
|
|
**Features:**
|
|
- **Length Prefix Validation** - Prevents buffer overflow and memory exhaustion by validating message lengths against configured maximum
|
|
- **Message Stream Size Validation** - Detects decompression bombs and message accumulation attacks
|
|
- **UDP Packet Validation** - Ensures UDP packets don't exceed buffer boundaries
|
|
- **Rate Limiting** - Implements token-bucket style rate limiting per source address to prevent DoS attacks
|
|
- **HTTP Header Validation** - Detects header injection attacks by checking for newline and null byte characters
|
|
- **Delimiter Validation** - Prevents delimiter injection attacks
|
|
- **Compression Ratio Validation** - Detects decompression bomb attacks
|
|
- **Error Message Sanitization** - Prevents information leakage in error responses
|
|
- **Rate Limit Cache Cleanup** - Prevents memory leaks from rate limit tracking
|
|
|
|
### 2. Configuration Security Settings
|
|
|
|
```csharp
|
|
public long MaxMessageStreamSize { get; set; } = 500 * 1024 * 1024; // 500 MB
|
|
public int MaxMessagesPerSecond { get; set; } = 0; // 0 = unlimited
|
|
public bool EnableLengthPrefixValidation { get; set; } = true;
|
|
public int MaxUdpPacketsPerSecond { get; set; } = 0; // 0 = unlimited
|
|
public int MaxHealthApiRequestSize { get; set; } = 10 * 1024 * 1024; // 10 MB
|
|
public bool EnableErrorMessageSanitization { get; set; } = true;
|
|
public bool EnableRateLimitCacheCleanup { get; set; } = true;
|
|
public int RateLimitCacheCleanupIntervalSeconds { get; set; } = 300;
|
|
public bool EnableHttpHeaderValidation { get; set; } = true;
|
|
```
|
|
|
|
**Benefits:**
|
|
- Configurable security thresholds for different deployment scenarios
|
|
- Ability to fine-tune security vs. performance trade-offs
|
|
- Granular control over which protections are enabled
|
|
|
|
### 3. NetworkServer Hardening
|
|
|
|
1. **Message Extraction Validation**
|
|
- Updated `TryExtractLengthPrefixedMessage()` to use SecurityValidator for length prefix validation
|
|
- Added checks to prevent message stream from exceeding configured maximum size
|
|
- Enhanced error messages to indicate validation failures
|
|
|
|
2. **Message Stream Protection**
|
|
- `HandleClientCommunicationAsync()` now validates message stream size before writing received data
|
|
- Prevents accumulation of large amounts of buffered data that could exhaust memory
|
|
- Disconnects clients that exceed limits with proper error logging
|
|
|
|
3. **UDP Rate Limiting**
|
|
- `HandleUdpDataAsync()` implements packet size validation
|
|
- UDP packet rate limiting prevents flood attacks
|
|
- Per-source tracking of packet rates
|
|
|
|
4. **Background Cleanup Task**
|
|
- Added `CleanupRateLimitCacheAsync()` to periodically clean up rate limit entries
|
|
- Prevents memory leaks from unbounded cache growth
|
|
- Configurable cleanup interval
|
|
|
|
5. **Startup Integration**
|
|
- Rate limit cache cleanup task integrated into server startup
|
|
- Runs as background task with configurable interval
|
|
|
|
### 4. NetworkClient Hardening
|
|
|
|
1. **Length-Prefixed Message Validation**
|
|
- Updated `ReceiveLengthPrefixedMessagesAsync()` to validate length prefixes using SecurityValidator
|
|
- Added dedicated exception handling for malformed length messages
|
|
- Improved error messages for validation failures
|
|
|
|
2. **Delimiter-Based Message Protection**
|
|
- Enhanced `ReceiveDelimiterAsync()` with max message stream size enforcement
|
|
- Added checks for accumulated message buffer size
|
|
- Prevents memory exhaustion from large buffered data
|
|
|
|
### 5. HealthApiServer REST API Hardening
|
|
|
|
1. **Request Size Limits**
|
|
- New `MaxRequestSize` property to enforce maximum HTTP request/response size
|
|
- Defaults to 10 MB, configurable before server starts
|
|
- Request line limited to 8192 bytes
|
|
- Total headers limited to 65536 bytes
|
|
|
|
2. **HTTP Header Security**
|
|
- Enhanced `ReadRequestLineAsync()` to validate for invalid control characters
|
|
- Rejects null bytes (0x00) in request lines
|
|
- `DrainHeadersAsync()` validates header size and detects null bytes
|
|
- Prevents header-based injection attacks
|
|
|
|
3. **Response Security Hardening**
|
|
- `WriteResponseAsync()` validates response body size against limit
|
|
- Content-Type validation prevents injection of control characters
|
|
- Additional security headers added:
|
|
- `X-Frame-Options: DENY` - Clickjacking protection
|
|
- `X-XSS-Protection: 1; mode=block` - XSS protection
|
|
- `Strict-Transport-Security` - Forces HTTPS
|
|
- `Pragma: no-cache` - Additional cache control
|
|
- Improved error handling for oversized responses
|
|
|
|
## Attack Vectors Mitigated
|
|
|
|
### 1. Buffer Overflow Attacks
|
|
- **Mitigation:** Length prefix validation, buffer size checks, message size limits
|
|
- **Implementation:** SecurityValidator validates all length values before buffer allocation
|
|
|
|
### 2. Memory Exhaustion / Decompression Bombs
|
|
- **Mitigation:** MaxMessageStreamSize configuration, per-message size limits
|
|
- **Implementation:** Accumulated stream size checked before each write operation
|
|
|
|
### 3. Slow Loris / Slowhttptest Attacks
|
|
- **Mitigation:** Request timeouts, header size limits
|
|
- **Implementation:** Network timeouts and maximum header sizes enforced
|
|
|
|
### 4. UDP Flood / Amplification Attacks
|
|
- **Mitigation:** UDP rate limiting, packet size validation
|
|
- **Implementation:** Per-source rate limiting with configurable thresholds
|
|
|
|
### 5. HTTP Header Injection
|
|
- **Mitigation:** HTTP header validation, null byte detection
|
|
- **Implementation:** SecurityValidator checks for newlines and null bytes in headers
|
|
|
|
### 6. Malformed Packet Attacks
|
|
- **Mitigation:** Strict validation of message framing and length prefixes
|
|
- **Implementation:** Enhanced error handling with specific exceptions for malformed data
|
|
|
|
### 7. Information Leakage
|
|
- **Mitigation:** Error message sanitization
|
|
- **Implementation:** Sensitive error details hidden from clients, detailed logs for server admins
|
|
|
|
### 8. Rate Limiting / DoS
|
|
- **Mitigation:** Per-client/source rate limiting capability
|
|
- **Implementation:** SecurityValidator.CheckRateLimit with configurable thresholds
|
|
|
|
## Configuration Best Practices
|
|
|
|
### Production Deployment
|
|
```csharp
|
|
var config = new Configuration
|
|
{
|
|
// Message constraints
|
|
MAX_MESSAGE_SIZE = 100 * 1024 * 1024, // 100 MB
|
|
MaxMessageStreamSize = 500 * 1024 * 1024, // 500 MB
|
|
|
|
// Rate limiting
|
|
MaxMessagesPerSecond = 1000, // Per client
|
|
MaxUdpPacketsPerSecond = 5000, // Per source
|
|
MaxHealthApiRequestSize = 10 * 1024 * 1024, // 10 MB
|
|
|
|
// Security features
|
|
EnableLengthPrefixValidation = true,
|
|
EnableErrorMessageSanitization = true,
|
|
EnableRateLimitCacheCleanup = true,
|
|
EnableHttpHeaderValidation = true
|
|
};
|
|
```
|
|
|
|
### High-Security Deployment
|
|
```csharp
|
|
var config = new Configuration
|
|
{
|
|
MAX_MESSAGE_SIZE = 10 * 1024 * 1024, // 10 MB
|
|
MaxMessageStreamSize = 50 * 1024 * 1024, // 50 MB
|
|
MaxMessagesPerSecond = 100, // Strict rate limit
|
|
MaxUdpPacketsPerSecond = 500, // Strict UDP limit
|
|
MaxHealthApiRequestSize = 1 * 1024 * 1024, // 1 MB
|
|
// All security features enabled by default
|
|
};
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### Optimization Tips
|
|
1. **Disable Nagle's Algorithm**: Set `EnableNagle = false` for lower latency
|
|
2. **Buffer Size**: Use appropriate `BufferSize` (default is ExtraLarge ~65KB)
|
|
3. **Keep-Alive**: Enable `EnableKeepAlive` to detect dead connections early
|
|
4. **Message Framing**: Use `LengthPrefixed` for better parsing performance
|
|
5. **Connection Pool**: Design clients to maintain persistent connections
|
|
6. **Batching**: Group small messages when possible
|
|
7. **Thread Affinity**: Run on dedicated cores for high throughput
|
|
|
|
### Monitoring
|
|
- Use `HealthApi` for external monitoring
|
|
- Enable `EnableServerStatusPage` for HTML reports
|
|
- Monitor `MessagesPerSecond` for throughput
|
|
- Track `IdleTimeout` events for connection health
|
|
- Use `NetworkMonitor` for advanced diagnostics
|
|
|
|
## Thread Safety
|
|
|
|
- `NetworkServer` and `NetworkClient` are thread-safe for all public methods
|
|
- Events are called on socket completion threads
|
|
- Use `ConfigureAwait(false)` in async handlers
|
|
- Access shared state with appropriate synchronization
|
|
|
|
## Supported Platforms
|
|
|
|
- ** .NET Standard 2.0** - Full support
|
|
- **.NET Framework 4.8+** - Full support
|
|
- **.NET 8.0** - Full support
|
|
- **Platforms**: Windows, Linux, macOS (with .NET Core)
|