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

View File

@ -37,8 +37,9 @@ namespace EonaCat.Connections.Server.Example
Protocol = ProtocolType.TCP, Protocol = ProtocolType.TCP,
Port = 1111, Port = 1111,
UseSsl = false, UseSsl = false,
UseAesEncryption = false, UseAesEncryption = true,
MaxConnections = 100000, MaxConnections = 100000,
AesPassword = "p@ss",
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "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="stream"></param>
/// <param name="aes"></param> /// <param name="aes"></param>
/// <returns></returns> /// <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 rawKey = aes.Key;
var iv = aes.IV; var iv = aes.IV;
@ -29,8 +29,12 @@ namespace EonaCat.Connections.Helpers
await WriteBytesWithLengthAsync(stream, salt); await WriteBytesWithLengthAsync(stream, salt);
await stream.FlushAsync(); await stream.FlushAsync();
// Derive stronger key using PBKDF2-SHA256 + salt + pepper // Derive key using PBKDF2-SHA256 + salt + password + pepper
var derivedKey = PBKDF2_SHA256(Combine(rawKey, Encoding.UTF8.GetBytes(Pepper)), salt, 100_000, 32); 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; aes.Key = derivedKey;
return aes; return aes;
@ -41,13 +45,19 @@ namespace EonaCat.Connections.Helpers
/// </summary> /// </summary>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <returns></returns> /// <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 rawKey = await ReadBytesWithLengthAsync(stream);
var iv = await ReadBytesWithLengthAsync(stream); var iv = await ReadBytesWithLengthAsync(stream);
var salt = 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(); Aes _aesEncryption = Aes.Create();
_aesEncryption.Key = derivedKey; _aesEncryption.Key = derivedKey;

View File

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

View File

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