Added AesPassword

This commit is contained in:
Jeroen Saey 2025-08-21 07:45:14 +02:00
parent bdf2d1b935
commit 9564e2002d
6 changed files with 696 additions and 683 deletions

View File

@ -1,27 +1,27 @@
using EonaCat.Connections;
using EonaCat.Connections.Models;
using System.Text;
namespace EonaCat.Connections.Client.Example
{
public class Program
{
private static NetworkClient _client;
public static async Task Main(string[] args)
{
await CreateClientAsync().ConfigureAwait(false);
while (true)
{
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().ConfigureAwait(false);
break;
}
using EonaCat.Connections;
using EonaCat.Connections.Models;
using System.Text;
namespace EonaCat.Connections.Client.Example
{
public class Program
{
private static NetworkClient _client;
public static async Task Main(string[] args)
{
await CreateClientAsync().ConfigureAwait(false);
while (true)
{
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().ConfigureAwait(false);
break;
}
var jsonUrl = "https://microsoftedge.github.io/Demos/json-dummy-data/5MB-min.json";
try
@ -35,46 +35,47 @@ namespace EonaCat.Connections.Client.Example
catch (Exception ex)
{
Console.WriteLine($"Failed to download large JSON file: {ex.Message}");
}
if (!string.IsNullOrEmpty(message))
{
await _client.SendAsync(message).ConfigureAwait(false);
}
}
}
private static async Task CreateClientAsync()
{
var config = new Configuration
{
Protocol = ProtocolType.TCP,
Host = "127.0.0.1",
Port = 1111,
UseSsl = false,
UseAesEncryption = false,
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
};
_client = new NetworkClient(config);
// Subscribe to events
_client.OnConnected += (sender, e) =>
Console.WriteLine($"Connected to server at {e.RemoteEndPoint}");
_client.OnDataReceived += (sender, e) =>
Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
_client.OnDisconnected += (sender, e) =>
Console.WriteLine("Disconnected from server");
await _client.ConnectAsync();
// Send nickname
await _client.SendNicknameAsync("TestUser");
// Send a message
await _client.SendAsync("Hello server!");
}
}
}
if (!string.IsNullOrEmpty(message))
{
await _client.SendAsync(message).ConfigureAwait(false);
}
}
}
private static async Task CreateClientAsync()
{
var config = new Configuration
{
Protocol = ProtocolType.TCP,
Host = "127.0.0.1",
Port = 1111,
UseSsl = false,
UseAesEncryption = true,
AesPassword = "p@ss",
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
};
_client = new NetworkClient(config);
// Subscribe to events
_client.OnConnected += (sender, e) =>
Console.WriteLine($"Connected to server at {e.RemoteEndPoint}");
_client.OnDataReceived += (sender, e) =>
Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
_client.OnDisconnected += (sender, e) =>
Console.WriteLine("Disconnected from server");
await _client.ConnectAsync();
// Send nickname
await _client.SendNicknameAsync("TestUser");
// Send a message
await _client.SendAsync("Hello server!");
}
}
}

View File

@ -37,8 +37,9 @@ namespace EonaCat.Connections.Server.Example
Protocol = ProtocolType.TCP,
Port = 1111,
UseSsl = false,
UseAesEncryption = false,
UseAesEncryption = true,
MaxConnections = 100000,
AesPassword = "p@ss",
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "p@ss"),
};

View File

@ -13,7 +13,7 @@ namespace EonaCat.Connections.Helpers
/// <param name="stream"></param>
/// <param name="aes"></param>
/// <returns></returns>
public static async Task<Aes> SendAesKeyAsync(Stream stream, Aes aes)
public static async Task<Aes> SendAesKeyAsync(Stream stream, Aes aes, string password = null)
{
var rawKey = aes.Key;
var iv = aes.IV;
@ -29,8 +29,12 @@ namespace EonaCat.Connections.Helpers
await WriteBytesWithLengthAsync(stream, salt);
await stream.FlushAsync();
// Derive stronger key using PBKDF2-SHA256 + salt + pepper
var derivedKey = PBKDF2_SHA256(Combine(rawKey, Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
// Derive key using PBKDF2-SHA256 + salt + password + pepper
if (string.IsNullOrEmpty(password))
{
password = "EonaCat.Connections";
}
var derivedKey = PBKDF2_SHA256(Combine(Combine(rawKey, Encoding.UTF8.GetBytes(password)), Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
aes.Key = derivedKey;
return aes;
@ -41,13 +45,19 @@ namespace EonaCat.Connections.Helpers
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static async Task<Aes> ReceiveAesKeyAsync(Stream stream)
public static async Task<Aes> ReceiveAesKeyAsync(Stream stream, string password = null)
{
var rawKey = await ReadBytesWithLengthAsync(stream);
var iv = await ReadBytesWithLengthAsync(stream);
var salt = await ReadBytesWithLengthAsync(stream);
var derivedKey = PBKDF2_SHA256(Combine(rawKey, Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
if (string.IsNullOrEmpty(password))
{
password = "EonaCat.Connections";
}
// Derived key using PBKDF2-SHA256 + salt + password + pepper
var derivedKey = PBKDF2_SHA256(Combine(Combine(rawKey, Encoding.UTF8.GetBytes(password)), Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32);
Aes _aesEncryption = Aes.Create();
_aesEncryption.Key = derivedKey;

View File

@ -22,6 +22,7 @@ namespace EonaCat.Connections.Models
public bool EnableNagle { get; set; } = false;
// For testing purposes, allow self-signed certificates
public bool IsSelfSignedEnabled { get; set; } = true;
public bool IsSelfSignedEnabled { get; set; } = true;
public string AesPassword { get; set; }
}
}

View File

@ -1,148 +1,148 @@
using EonaCat.Connections.EventArguments;
using EonaCat.Connections.Helpers;
using EonaCat.Connections.Models;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
namespace EonaCat.Connections
{
public class NetworkClient
{
private readonly Configuration _config;
private TcpClient _tcpClient;
private UdpClient _udpClient;
private Stream _stream;
private Aes _aesEncryption;
private CancellationTokenSource _cancellation;
private bool _isConnected;
public event EventHandler<ConnectionEventArgs> OnConnected;
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
public event EventHandler<ConnectionEventArgs> OnDisconnected;
public event EventHandler<ErrorEventArgs> OnSslError;
public event EventHandler<ErrorEventArgs> OnEncryptionError;
public event EventHandler<ErrorEventArgs> OnGeneralError;
public NetworkClient(Configuration config)
{
_config = config;
}
public async Task ConnectAsync()
{
_cancellation = new CancellationTokenSource();
if (_config.Protocol == ProtocolType.TCP)
{
await ConnectTcpAsync();
}
else
{
await ConnectUdp();
}
}
private async Task ConnectTcpAsync()
{
try
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_config.Host, _config.Port);
Stream stream = _tcpClient.GetStream();
// Setup SSL if required
if (_config.UseSsl)
{
try
{
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
await sslStream.AuthenticateAsClientAsync(_config.Host);
stream = sslStream;
}
catch (Exception ex)
{
OnSslError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "SSL authentication failed" });
return;
}
}
// Setup AES encryption if required
if (_config.UseAesEncryption)
{
try
{
_aesEncryption = await AesKeyExchange.ReceiveAesKeyAsync(stream);
}
catch (Exception ex)
{
OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "AES setup failed" });
return;
}
}
_stream = stream;
_isConnected = true;
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
// Start receiving data
_ = Task.Run(() => ReceiveDataAsync(), _cancellation.Token);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect" });
}
}
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
{
if (_config.IsSelfSignedEnabled)
{
return true; // Accept self-signed certificates
}
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; // Certificate is valid
}
// Log or handle the SSL error as needed
OnSslError?.Invoke(this, new ErrorEventArgs
{
Exception = new AuthenticationException("SSL certificate validation failed"),
Message = $"SSL Policy Errors: {sslPolicyErrors}"
});
return false; // Reject the certificate
}
private async Task ConnectUdp()
{
try
{
_udpClient = new UdpClient();
_udpClient.Connect(_config.Host, _config.Port);
_isConnected = true;
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
// Start receiving data
_ = Task.Run(() => ReceiveUdpDataAsync(), _cancellation.Token);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect UDP" });
}
}
using EonaCat.Connections.EventArguments;
using EonaCat.Connections.Helpers;
using EonaCat.Connections.Models;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
namespace EonaCat.Connections
{
public class NetworkClient
{
private readonly Configuration _config;
private TcpClient _tcpClient;
private UdpClient _udpClient;
private Stream _stream;
private Aes _aesEncryption;
private CancellationTokenSource _cancellation;
private bool _isConnected;
public event EventHandler<ConnectionEventArgs> OnConnected;
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
public event EventHandler<ConnectionEventArgs> OnDisconnected;
public event EventHandler<ErrorEventArgs> OnSslError;
public event EventHandler<ErrorEventArgs> OnEncryptionError;
public event EventHandler<ErrorEventArgs> OnGeneralError;
public NetworkClient(Configuration config)
{
_config = config;
}
public async Task ConnectAsync()
{
_cancellation = new CancellationTokenSource();
if (_config.Protocol == ProtocolType.TCP)
{
await ConnectTcpAsync();
}
else
{
await ConnectUdp();
}
}
private async Task ConnectTcpAsync()
{
try
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_config.Host, _config.Port);
Stream stream = _tcpClient.GetStream();
// Setup SSL if required
if (_config.UseSsl)
{
try
{
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
await sslStream.AuthenticateAsClientAsync(_config.Host);
stream = sslStream;
}
catch (Exception ex)
{
OnSslError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "SSL authentication failed" });
return;
}
}
// Setup AES encryption if required
if (_config.UseAesEncryption)
{
try
{
_aesEncryption = await AesKeyExchange.ReceiveAesKeyAsync(stream, _config.AesPassword);
}
catch (Exception ex)
{
OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "AES setup failed" });
return;
}
}
_stream = stream;
_isConnected = true;
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
// Start receiving data
_ = Task.Run(() => ReceiveDataAsync(), _cancellation.Token);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect" });
}
}
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
{
if (_config.IsSelfSignedEnabled)
{
return true; // Accept self-signed certificates
}
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; // Certificate is valid
}
// Log or handle the SSL error as needed
OnSslError?.Invoke(this, new ErrorEventArgs
{
Exception = new AuthenticationException("SSL certificate validation failed"),
Message = $"SSL Policy Errors: {sslPolicyErrors}"
});
return false; // Reject the certificate
}
private async Task ConnectUdp()
{
try
{
_udpClient = new UdpClient();
_udpClient.Connect(_config.Host, _config.Port);
_isConnected = true;
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self", RemoteEndPoint = new IPEndPoint(IPAddress.Parse(_config.Host), _config.Port) });
// Start receiving data
_ = Task.Run(() => ReceiveUdpDataAsync(), _cancellation.Token);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Failed to connect UDP" });
}
}
private async Task ReceiveDataAsync()
{
while (!_cancellation.Token.IsCancellationRequested && _isConnected)
@ -209,29 +209,29 @@ namespace EonaCat.Connections
}
return offset;
}
private async Task ReceiveUdpDataAsync()
{
while (!_cancellation.Token.IsCancellationRequested && _isConnected)
{
try
{
var result = await _udpClient.ReceiveAsync();
await ProcessReceivedDataAsync(result.Buffer);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" });
_isConnected = false;
// Start reconnect
_ = Task.Run(() => AutoReconnectAsync());
break;
}
}
}
private async Task ReceiveUdpDataAsync()
{
while (!_cancellation.Token.IsCancellationRequested && _isConnected)
{
try
{
var result = await _udpClient.ReceiveAsync();
await ProcessReceivedDataAsync(result.Buffer);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" });
_isConnected = false;
// Start reconnect
_ = Task.Run(() => AutoReconnectAsync());
break;
}
}
}
private async Task ProcessReceivedDataAsync(byte[] data)
{
try
@ -269,8 +269,8 @@ namespace EonaCat.Connections
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error processing data" });
}
}
public async Task SendAsync(byte[] data)
{
if (!_isConnected) return;
@ -310,99 +310,99 @@ namespace EonaCat.Connections
else
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error sending data" });
}
}
public async Task SendAsync(string message)
{
await SendAsync(Encoding.UTF8.GetBytes(message));
}
public async Task SendNicknameAsync(string nickname)
{
await SendAsync($"NICKNAME:{nickname}");
}
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
{
using (var encryptor = aes.CreateEncryptor())
using (var ms = new MemoryStream())
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
await cs.WriteAsync(data, 0, data.Length);
cs.FlushFinalBlock();
return ms.ToArray();
}
}
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
{
using (var decryptor = aes.CreateDecryptor())
using (var ms = new MemoryStream(data))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (var result = new MemoryStream())
{
await cs.CopyToAsync(result);
return result.ToArray();
}
}
private async Task AutoReconnectAsync()
{
if (!_config.EnableAutoReconnect)
{
return;
}
int attempt = 0;
while (!_isConnected && (_config.MaxReconnectAttempts == 0 || attempt < _config.MaxReconnectAttempts))
{
attempt++;
try
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Attempting to reconnect (Attempt {attempt})" });
await ConnectAsync();
if (_isConnected)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Reconnected successfully after {attempt} attempt(s)" });
break;
}
}
catch
{
// Ignore exceptions, we'll retry
}
await Task.Delay(_config.ReconnectDelayMs);
}
if (!_isConnected)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = "Failed to reconnect" });
}
}
public async Task DisconnectAsync()
{
_isConnected = false;
_cancellation?.Cancel();
_tcpClient?.Close();
_udpClient?.Close();
_stream?.Dispose();
_aesEncryption?.Dispose();
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self" });
_ = Task.Run(() => AutoReconnectAsync());
}
public void Dispose()
{
DisconnectAsync().Wait();
_cancellation?.Dispose();
}
}
}
public async Task SendAsync(string message)
{
await SendAsync(Encoding.UTF8.GetBytes(message));
}
public async Task SendNicknameAsync(string nickname)
{
await SendAsync($"NICKNAME:{nickname}");
}
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
{
using (var encryptor = aes.CreateEncryptor())
using (var ms = new MemoryStream())
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
await cs.WriteAsync(data, 0, data.Length);
cs.FlushFinalBlock();
return ms.ToArray();
}
}
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
{
using (var decryptor = aes.CreateDecryptor())
using (var ms = new MemoryStream(data))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (var result = new MemoryStream())
{
await cs.CopyToAsync(result);
return result.ToArray();
}
}
private async Task AutoReconnectAsync()
{
if (!_config.EnableAutoReconnect)
{
return;
}
int attempt = 0;
while (!_isConnected && (_config.MaxReconnectAttempts == 0 || attempt < _config.MaxReconnectAttempts))
{
attempt++;
try
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Attempting to reconnect (Attempt {attempt})" });
await ConnectAsync();
if (_isConnected)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = $"Reconnected successfully after {attempt} attempt(s)" });
break;
}
}
catch
{
// Ignore exceptions, we'll retry
}
await Task.Delay(_config.ReconnectDelayMs);
}
if (!_isConnected)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Message = "Failed to reconnect" });
}
}
public async Task DisconnectAsync()
{
_isConnected = false;
_cancellation?.Cancel();
_tcpClient?.Close();
_udpClient?.Close();
_stream?.Dispose();
_aesEncryption?.Dispose();
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = "self" });
_ = Task.Run(() => AutoReconnectAsync());
}
public void Dispose()
{
DisconnectAsync().Wait();
_cancellation?.Dispose();
}
}
}

View File

@ -1,248 +1,248 @@
using EonaCat.Connections.EventArguments;
using EonaCat.Connections.Helpers;
using EonaCat.Connections.Models;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
namespace EonaCat.Connections
{
public class NetworkServer
{
private readonly Configuration _config;
private readonly Stats _stats;
private readonly ConcurrentDictionary<string, Connection> _clients;
private TcpListener _tcpListener;
private UdpClient _udpListener;
private CancellationTokenSource _serverCancellation;
private readonly object _statsLock = new object();
public event EventHandler<ConnectionEventArgs> OnConnected;
public event EventHandler<NicknameConnectionEventArgs> OnConnectedWithNickname;
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
public event EventHandler<ConnectionEventArgs> OnDisconnected;
public event EventHandler<ErrorEventArgs> OnSslError;
public event EventHandler<ErrorEventArgs> OnEncryptionError;
public event EventHandler<ErrorEventArgs> OnGeneralError;
public NetworkServer(Configuration config)
{
_config = config;
_stats = new Stats { StartTime = DateTime.UtcNow };
_clients = new ConcurrentDictionary<string, Connection>();
}
public Stats GetStats()
{
lock (_statsLock)
{
_stats.ActiveConnections = _clients.Count;
return _stats;
}
}
public async Task StartAsync()
{
_serverCancellation = new CancellationTokenSource();
if (_config.Protocol == ProtocolType.TCP)
{
await StartTcpServerAsync();
}
else
{
await StartUdpServerAsync();
}
}
private async Task StartTcpServerAsync()
{
_tcpListener = new TcpListener(IPAddress.Parse(_config.Host), _config.Port);
_tcpListener.Start();
Console.WriteLine($"TCP Server started on {_config.Host}:{_config.Port}");
while (!_serverCancellation.Token.IsCancellationRequested)
{
try
{
var tcpClient = await _tcpListener.AcceptTcpClientAsync();
_ = Task.Run(() => HandleTcpClientAsync(tcpClient), _serverCancellation.Token);
}
catch (ObjectDisposedException)
{
break;
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error accepting TCP client" });
}
}
}
private async Task StartUdpServerAsync()
{
_udpListener = new UdpClient(_config.Port);
Console.WriteLine($"UDP Server started on {_config.Host}:{_config.Port}");
while (!_serverCancellation.Token.IsCancellationRequested)
{
try
{
var result = await _udpListener.ReceiveAsync();
_ = Task.Run(() => HandleUdpDataAsync(result), _serverCancellation.Token);
}
catch (ObjectDisposedException)
{
break;
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving UDP data" });
}
}
}
private async Task HandleTcpClientAsync(TcpClient tcpClient)
{
var clientId = Guid.NewGuid().ToString();
var client = new Connection
{
Id = clientId,
TcpClient = tcpClient,
RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint,
ConnectedAt = DateTime.UtcNow,
CancellationToken = new CancellationTokenSource()
};
try
{
// Configure TCP client
tcpClient.NoDelay = !_config.EnableNagle;
if (_config.EnableKeepAlive)
{
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
}
Stream stream = tcpClient.GetStream();
// Setup SSL if required
if (_config.UseSsl)
{
try
{
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
await sslStream.AuthenticateAsServerAsync(_config.ServerCertificate, false, SslProtocols.Tls12, false);
stream = sslStream;
client.IsSecure = true;
}
catch (Exception ex)
{
OnSslError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "SSL authentication failed" });
return;
}
}
// Setup AES encryption if required
if (_config.UseAesEncryption)
{
try
{
client.AesEncryption = Aes.Create();
client.AesEncryption.GenerateKey();
client.AesEncryption.GenerateIV();
client.IsEncrypted = true;
// Securely send raw AES key + IV + salt
await AesKeyExchange.SendAesKeyAsync(stream, client.AesEncryption);
}
catch (Exception ex)
{
OnEncryptionError?.Invoke(this, new ErrorEventArgs
{
ClientId = clientId,
Exception = ex,
Message = "AES setup failed"
});
return;
}
}
client.Stream = stream;
_clients[clientId] = client;
lock (_statsLock)
{
_stats.TotalConnections++;
}
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
// Handle client communication
await HandleClientCommunicationAsync(client);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error handling TCP client" });
}
finally
{
await DisconnectClientAsync(clientId);
}
}
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
{
if (_config.IsSelfSignedEnabled)
{
return true; // Accept self-signed certificates
}
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; // Certificate is valid
}
// Log or handle the SSL error as needed
OnSslError?.Invoke(this, new ErrorEventArgs
{
Exception = new AuthenticationException("SSL certificate validation failed"),
Message = $"SSL Policy Errors: {sslPolicyErrors}"
});
return false; // Reject the certificate
}
private async Task HandleUdpDataAsync(UdpReceiveResult result)
{
var clientKey = result.RemoteEndPoint.ToString();
if (!_clients.TryGetValue(clientKey, out var client))
{
client = new Connection
{
Id = clientKey,
RemoteEndPoint = result.RemoteEndPoint,
ConnectedAt = DateTime.UtcNow
};
_clients[clientKey] = client;
lock (_statsLock)
{
_stats.TotalConnections++;
}
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientKey, RemoteEndPoint = result.RemoteEndPoint });
}
await ProcessReceivedDataAsync(client, result.Buffer);
}
using EonaCat.Connections.EventArguments;
using EonaCat.Connections.Helpers;
using EonaCat.Connections.Models;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using ErrorEventArgs = EonaCat.Connections.EventArguments.ErrorEventArgs;
namespace EonaCat.Connections
{
public class NetworkServer
{
private readonly Configuration _config;
private readonly Stats _stats;
private readonly ConcurrentDictionary<string, Connection> _clients;
private TcpListener _tcpListener;
private UdpClient _udpListener;
private CancellationTokenSource _serverCancellation;
private readonly object _statsLock = new object();
public event EventHandler<ConnectionEventArgs> OnConnected;
public event EventHandler<NicknameConnectionEventArgs> OnConnectedWithNickname;
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
public event EventHandler<ConnectionEventArgs> OnDisconnected;
public event EventHandler<ErrorEventArgs> OnSslError;
public event EventHandler<ErrorEventArgs> OnEncryptionError;
public event EventHandler<ErrorEventArgs> OnGeneralError;
public NetworkServer(Configuration config)
{
_config = config;
_stats = new Stats { StartTime = DateTime.UtcNow };
_clients = new ConcurrentDictionary<string, Connection>();
}
public Stats GetStats()
{
lock (_statsLock)
{
_stats.ActiveConnections = _clients.Count;
return _stats;
}
}
public async Task StartAsync()
{
_serverCancellation = new CancellationTokenSource();
if (_config.Protocol == ProtocolType.TCP)
{
await StartTcpServerAsync();
}
else
{
await StartUdpServerAsync();
}
}
private async Task StartTcpServerAsync()
{
_tcpListener = new TcpListener(IPAddress.Parse(_config.Host), _config.Port);
_tcpListener.Start();
Console.WriteLine($"TCP Server started on {_config.Host}:{_config.Port}");
while (!_serverCancellation.Token.IsCancellationRequested)
{
try
{
var tcpClient = await _tcpListener.AcceptTcpClientAsync();
_ = Task.Run(() => HandleTcpClientAsync(tcpClient), _serverCancellation.Token);
}
catch (ObjectDisposedException)
{
break;
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error accepting TCP client" });
}
}
}
private async Task StartUdpServerAsync()
{
_udpListener = new UdpClient(_config.Port);
Console.WriteLine($"UDP Server started on {_config.Host}:{_config.Port}");
while (!_serverCancellation.Token.IsCancellationRequested)
{
try
{
var result = await _udpListener.ReceiveAsync();
_ = Task.Run(() => HandleUdpDataAsync(result), _serverCancellation.Token);
}
catch (ObjectDisposedException)
{
break;
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving UDP data" });
}
}
}
private async Task HandleTcpClientAsync(TcpClient tcpClient)
{
var clientId = Guid.NewGuid().ToString();
var client = new Connection
{
Id = clientId,
TcpClient = tcpClient,
RemoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint,
ConnectedAt = DateTime.UtcNow,
CancellationToken = new CancellationTokenSource()
};
try
{
// Configure TCP client
tcpClient.NoDelay = !_config.EnableNagle;
if (_config.EnableKeepAlive)
{
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
}
Stream stream = tcpClient.GetStream();
// Setup SSL if required
if (_config.UseSsl)
{
try
{
var sslStream = new SslStream(stream, false, userCertificateValidationCallback:RemoteCertificateValidationCallback);
await sslStream.AuthenticateAsServerAsync(_config.ServerCertificate, false, SslProtocols.Tls12, false);
stream = sslStream;
client.IsSecure = true;
}
catch (Exception ex)
{
OnSslError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "SSL authentication failed" });
return;
}
}
// Setup AES encryption if required
if (_config.UseAesEncryption)
{
try
{
client.AesEncryption = Aes.Create();
client.AesEncryption.GenerateKey();
client.AesEncryption.GenerateIV();
client.IsEncrypted = true;
// Securely send raw AES key + IV + salt
await AesKeyExchange.SendAesKeyAsync(stream, client.AesEncryption, _config.AesPassword);
}
catch (Exception ex)
{
OnEncryptionError?.Invoke(this, new ErrorEventArgs
{
ClientId = clientId,
Exception = ex,
Message = "AES setup failed"
});
return;
}
}
client.Stream = stream;
_clients[clientId] = client;
lock (_statsLock)
{
_stats.TotalConnections++;
}
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
// Handle client communication
await HandleClientCommunicationAsync(client);
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error handling TCP client" });
}
finally
{
await DisconnectClientAsync(clientId);
}
}
private bool RemoteCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
{
if (_config.IsSelfSignedEnabled)
{
return true; // Accept self-signed certificates
}
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; // Certificate is valid
}
// Log or handle the SSL error as needed
OnSslError?.Invoke(this, new ErrorEventArgs
{
Exception = new AuthenticationException("SSL certificate validation failed"),
Message = $"SSL Policy Errors: {sslPolicyErrors}"
});
return false; // Reject the certificate
}
private async Task HandleUdpDataAsync(UdpReceiveResult result)
{
var clientKey = result.RemoteEndPoint.ToString();
if (!_clients.TryGetValue(clientKey, out var client))
{
client = new Connection
{
Id = clientKey,
RemoteEndPoint = result.RemoteEndPoint,
ConnectedAt = DateTime.UtcNow
};
_clients[clientKey] = client;
lock (_statsLock)
{
_stats.TotalConnections++;
}
OnConnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientKey, RemoteEndPoint = result.RemoteEndPoint });
}
await ProcessReceivedDataAsync(client, result.Buffer);
}
private async Task HandleClientCommunicationAsync(Connection client)
{
var lengthBuffer = new byte[4]; // length prefix
@ -312,8 +312,8 @@ namespace EonaCat.Connections
}
return offset;
}
private async Task ProcessReceivedDataAsync(Connection client, byte[] data)
{
try
@ -367,36 +367,36 @@ namespace EonaCat.Connections
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error processing data" });
}
}
public async Task SendToClientAsync(string clientId, byte[] data)
{
if (_clients.TryGetValue(clientId, out var client))
{
await SendDataAsync(client, data);
}
}
public async Task SendToClientAsync(string clientId, string message)
{
await SendToClientAsync(clientId, Encoding.UTF8.GetBytes(message));
}
public async Task BroadcastAsync(byte[] data)
{
var tasks = new List<Task>();
foreach (var client in _clients.Values)
{
tasks.Add(SendDataAsync(client, data));
}
await Task.WhenAll(tasks);
}
public async Task BroadcastAsync(string message)
{
await BroadcastAsync(Encoding.UTF8.GetBytes(message));
}
public async Task SendToClientAsync(string clientId, byte[] data)
{
if (_clients.TryGetValue(clientId, out var client))
{
await SendDataAsync(client, data);
}
}
public async Task SendToClientAsync(string clientId, string message)
{
await SendToClientAsync(clientId, Encoding.UTF8.GetBytes(message));
}
public async Task BroadcastAsync(byte[] data)
{
var tasks = new List<Task>();
foreach (var client in _clients.Values)
{
tasks.Add(SendDataAsync(client, data));
}
await Task.WhenAll(tasks);
}
public async Task BroadcastAsync(string message)
{
await BroadcastAsync(Encoding.UTF8.GetBytes(message));
}
private async Task SendDataAsync(Connection client, byte[] data)
{
try
@ -442,72 +442,72 @@ namespace EonaCat.Connections
else
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error sending data" });
}
}
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
{
using (var encryptor = aes.CreateEncryptor())
using (var ms = new MemoryStream())
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
await cs.WriteAsync(data, 0, data.Length);
cs.FlushFinalBlock();
return ms.ToArray();
}
}
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
{
using (var decryptor = aes.CreateDecryptor())
using (var ms = new MemoryStream(data))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (var result = new MemoryStream())
{
await cs.CopyToAsync(result);
return result.ToArray();
}
}
private async Task DisconnectClientAsync(string clientId)
{
if (_clients.TryRemove(clientId, out var client))
{
try
{
client.CancellationToken?.Cancel();
client.TcpClient?.Close();
client.Stream?.Dispose();
client.AesEncryption?.Dispose();
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error disconnecting client" });
}
}
}
public void Stop()
{
_serverCancellation?.Cancel();
_tcpListener?.Stop();
_udpListener?.Close();
// Disconnect all clients
var disconnectTasks = new List<Task>();
foreach (var clientId in _clients.Keys.ToArray())
{
disconnectTasks.Add(DisconnectClientAsync(clientId));
}
Task.WaitAll(disconnectTasks.ToArray());
}
public void Dispose()
{
Stop();
_serverCancellation?.Dispose();
}
}
}
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
{
using (var encryptor = aes.CreateEncryptor())
using (var ms = new MemoryStream())
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
await cs.WriteAsync(data, 0, data.Length);
cs.FlushFinalBlock();
return ms.ToArray();
}
}
private async Task<byte[]> DecryptDataAsync(byte[] data, Aes aes)
{
using (var decryptor = aes.CreateDecryptor())
using (var ms = new MemoryStream(data))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (var result = new MemoryStream())
{
await cs.CopyToAsync(result);
return result.ToArray();
}
}
private async Task DisconnectClientAsync(string clientId)
{
if (_clients.TryRemove(clientId, out var client))
{
try
{
client.CancellationToken?.Cancel();
client.TcpClient?.Close();
client.Stream?.Dispose();
client.AesEncryption?.Dispose();
OnDisconnected?.Invoke(this, new ConnectionEventArgs { ClientId = clientId, RemoteEndPoint = client.RemoteEndPoint });
}
catch (Exception ex)
{
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = clientId, Exception = ex, Message = "Error disconnecting client" });
}
}
}
public void Stop()
{
_serverCancellation?.Cancel();
_tcpListener?.Stop();
_udpListener?.Close();
// Disconnect all clients
var disconnectTasks = new List<Task>();
foreach (var clientId in _clients.Keys.ToArray())
{
disconnectTasks.Add(DisconnectClientAsync(clientId));
}
Task.WaitAll(disconnectTasks.ToArray());
}
public void Dispose()
{
Stop();
_serverCancellation?.Dispose();
}
}
}