diff --git a/Meum.Client.CLI/Meum.Client.CLI.csproj b/Meum.Client.CLI/Meum.Client.CLI.csproj index 903d585..4b9d26a 100644 --- a/Meum.Client.CLI/Meum.Client.CLI.csproj +++ b/Meum.Client.CLI/Meum.Client.CLI.csproj @@ -1,15 +1,14 @@  - net9.0 Exe - 1.0.0 + 0.0.1 + net9.0 + enable + disable - - - diff --git a/Meum.Client.CLI/Program.cs b/Meum.Client.CLI/Program.cs index c4ede7c..cda9f4d 100644 --- a/Meum.Client.CLI/Program.cs +++ b/Meum.Client.CLI/Program.cs @@ -4,11 +4,11 @@ global using System.Threading; global using System.Threading.Tasks; global using Meum.Core; using System.Net; -using System.Net.Quic; using System.Reflection; using DTLib.Console; using DTLib.Demystifier; using DTLib.Extensions; +using DTLib.Logging; namespace Meum.Client.CLI; @@ -34,15 +34,15 @@ class Program { Console.OutputEncoding = StringConverter.UTF8; Console.InputEncoding = StringConverter.UTF8; - ColoredConsole.ResetColor(); + ColoredConsole.Clear(); + var loggerRoot = new ConsoleLogger(); try { var v = Assembly.GetExecutingAssembly().GetName().Version; string title = $"Meum CLI v{v?.ToString(3) ?? "Null"}"; Console.Title = title; - if (!QuicConnection.IsSupported) - throw new Exception("Quic is not supported, check for presence of libmsquic and openssl"); + Functions.InitMsQuic(loggerRoot); ColoredConsole.WriteTitle(title, '=', fg: ConsoleColor.Cyan); ColoredConsole.WriteLine(greeting_art, fg: ConsoleColor.Magenta); @@ -57,17 +57,18 @@ class Program { if (userAddress == null) { - var addrstr = - ColoredConsole.ReadLine("enter user address (name@server.xyz)", ConsoleColor.Blue); + ColoredConsole.Write("enter user address (name@server.xyz): ", ConsoleColor.Blue); + var addrstr = ColoredConsole.ReadLine(ConsoleColor.Cyan); if (string.IsNullOrEmpty(addrstr)) continue; userAddress = new(addrstr); } - Client client = new(); + Client client = new(loggerRoot); if (serverEndPoint == null) { - serverAddress = ColoredConsole.ReadLine("enter server address (server.xyz)", ConsoleColor.Blue); + ColoredConsole.Write("enter server address (server.xyz): ", ConsoleColor.Blue); + serverAddress = ColoredConsole.ReadLine(ConsoleColor.Cyan); if (string.IsNullOrEmpty(serverAddress)) { ColoredConsole.WriteLine("null address", ConsoleColor.Red); @@ -82,7 +83,6 @@ class Program var conn = await client.ConnectToServerAsync(serverEndPoint); ColoredConsole.WriteLine("Connected to server", ConsoleColor.Green); - await conn.PingAsync(); await Task.Delay(-1); } catch (Exception ex) diff --git a/Meum.Client/Client.cs b/Meum.Client/Client.cs index ce186d4..b255b47 100644 --- a/Meum.Client/Client.cs +++ b/Meum.Client/Client.cs @@ -1,5 +1,6 @@ global using System; global using System.Collections.Generic; +global using System.Threading; global using System.Threading.Tasks; global using Meum.Core; using System.Net; @@ -36,7 +37,7 @@ public class Client return Task.CompletedTask; } - public async Task ConnectToServerAsync(DnsEndPoint serverEndPoint) + public async Task ConnectToServerAsync(DnsEndPoint serverEndPoint, CancellationToken ct = default) { var quicConn = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions { @@ -49,8 +50,15 @@ public class Client ApplicationProtocols = Constants.ApplicationProtocols, TargetHost = serverEndPoint.Host } - }); - var serv = new ServerConnection(quicConn, serverEndPoint, _logger); + }, ct); + + var timeOutCts = CancellationTokenSource.CreateLinkedTokenSource(ct); + timeOutCts.CancelAfter(Constants.ConnectionTimeout); + var serv = await ServerConnection.OpenAsync(quicConn, + serverEndPoint, + _logger, + timeOutCts.Token); + if(!_connectedServers.Add(serv)) throw new Exception($"Is already connected to server '{serverEndPoint.Host}'"); return serv; diff --git a/Meum.Client/Meum.Client.csproj b/Meum.Client/Meum.Client.csproj index 42a8226..c6d1d0d 100644 --- a/Meum.Client/Meum.Client.csproj +++ b/Meum.Client/Meum.Client.csproj @@ -2,17 +2,12 @@ net9.0 - enable enable + disable - - - - - diff --git a/Meum.Client/ServerConnection.cs b/Meum.Client/ServerConnection.cs index 51903a3..080fdf2 100644 --- a/Meum.Client/ServerConnection.cs +++ b/Meum.Client/ServerConnection.cs @@ -1,7 +1,5 @@ using System.Net; using System.Net.Quic; -using System.Net.Security; -using DTLib.Extensions; using DTLib.Logging; namespace Meum.Client; @@ -14,14 +12,25 @@ public class ServerConnection : IAsyncDisposable public DnsEndPoint ServerEndPoint { get; } - public ServerConnection(QuicConnection quicConnection, DnsEndPoint serverEndPoint, ILogger logger) + private ServerConnection(QuicConnection quicConnection, DnsEndPoint serverEndPoint, ILogger logger) { ServerEndPoint = serverEndPoint; _quicConnection = quicConnection; _logger = logger; } - + public static async Task OpenAsync(QuicConnection quicConnection, + DnsEndPoint serverEndPoint, + ILogger logger, + CancellationToken ct) + { + var serverConnection = new ServerConnection(quicConnection, serverEndPoint, logger); + + var systemStream = await quicConnection.OpenStreamAsync(QuicStreamType.Bidirectional, ct); + await systemStream.SendPingReceivePong(); + return serverConnection; + } + public override int GetHashCode() { return _quicConnection.RemoteEndPoint.GetHashCode(); diff --git a/Meum.Core/Constants.cs b/Meum.Core/Constants.cs index f64031d..fa2a23b 100644 --- a/Meum.Core/Constants.cs +++ b/Meum.Core/Constants.cs @@ -2,7 +2,6 @@ global using System.Collections.Generic; global using System.Threading; global using System.Threading.Tasks; -using System.Net; using System.Net.Security; namespace Meum.Core; @@ -17,4 +16,5 @@ public static class Constants public const int ServerPortDefault = 9320; public const long DefaultStreamErrorCode = 0xA; public const long DefaultCloseErrorCode = 0xB; + public static readonly TimeSpan ConnectionTimeout = TimeSpan.FromSeconds(3); } \ No newline at end of file diff --git a/Meum.Core/Functions.cs b/Meum.Core/Functions.cs index 7fe6ab7..38d597b 100644 --- a/Meum.Core/Functions.cs +++ b/Meum.Core/Functions.cs @@ -1,9 +1,25 @@ using System.Net; +using System.Net.Quic; +using DTLib.Logging; +using Unofficial.MsQuic; namespace Meum.Core; public static class Functions { + public static void InitMsQuic(ILogger? logger) + { + if (logger != null) + { + HarmonyMsQuicLoadFix.Apply(msg => logger.LogInfo(nameof(HarmonyMsQuicLoadFix), msg)); + using var netEventListener = new NetEventListener(logger); + } + else HarmonyMsQuicLoadFix.Apply(); + + if (!QuicConnection.IsSupported) + throw new Exception("Quic is not supported, check for presence of libmsquic and openssl"); + } + public static bool IsValidDomainName(string name) { return Uri.CheckHostName(name) != UriHostNameType.Unknown; diff --git a/Meum.Core/Meum.Core.csproj b/Meum.Core/Meum.Core.csproj index f835e5a..a7a0380 100644 --- a/Meum.Core/Meum.Core.csproj +++ b/Meum.Core/Meum.Core.csproj @@ -1,9 +1,12 @@  net9.0 + enable + disable true - + + diff --git a/Meum.Core/NetEventListener.cs b/Meum.Core/NetEventListener.cs new file mode 100644 index 0000000..00e96e1 --- /dev/null +++ b/Meum.Core/NetEventListener.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.Tracing; +using System.Linq; +using DTLib.Logging; + +namespace Meum.Core; + +internal class NetEventListener(ILogger _logger) : EventListener +{ + protected override void OnEventSourceCreated(EventSource eventSource) + { + // Filter for NetEventSource + if (eventSource.Name.Contains("System.Net")) + { + EnableEvents(eventSource, EventLevel.LogAlways, EventKeywords.All); + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + LogSeverity severity = eventData.Level switch + { + EventLevel.LogAlways => LogSeverity.Info, + EventLevel.Critical => LogSeverity.Error, + EventLevel.Error => LogSeverity.Error, + EventLevel.Warning => LogSeverity.Warn, + EventLevel.Informational => LogSeverity.Info, + EventLevel.Verbose => LogSeverity.Debug, + _ => throw new ArgumentOutOfRangeException(eventData.Level.ToString()) + }; + IEnumerable payload = eventData.Payload ?? Enumerable.Empty(); + var message = string.Join(", ", payload); + string context = eventData.EventSource.Name; + if (context.Contains("System.Net.Quic")) + context = "MsQuic"; + _logger.Log(context, severity, message); + } +} \ No newline at end of file diff --git a/Meum.Core/QuicExtensions.cs b/Meum.Core/QuicExtensions.cs new file mode 100644 index 0000000..c23976c --- /dev/null +++ b/Meum.Core/QuicExtensions.cs @@ -0,0 +1,24 @@ +using System.Net.Quic; + +namespace Meum.Core; + +public static class QuicExtensions +{ + public static async Task AcceptStreamAsync(this QuicConnection conn, + QuicStreamType streamType, CancellationToken ct = default) + { + var s = await conn.AcceptInboundStreamAsync(ct); + if (s.Type != streamType) + throw new Exception($"Accepted stream type is invalid: {s.Type} instead of {streamType}"); + var w = new QuicStreamWrapper(s); + return w; + } + + public static async Task OpenStreamAsync(this QuicConnection conn, + QuicStreamType streamType, CancellationToken ct = default) + { + var s = await conn.OpenOutboundStreamAsync(streamType, ct); + var w = new QuicStreamWrapper(s); + return w; + } +} \ No newline at end of file diff --git a/Meum.Core/QuicStreamWrapper.cs b/Meum.Core/QuicStreamWrapper.cs index d6fce98..b7a32ef 100644 --- a/Meum.Core/QuicStreamWrapper.cs +++ b/Meum.Core/QuicStreamWrapper.cs @@ -74,8 +74,23 @@ public class QuicStreamWrapper : IAsyncDisposable return m; } - //TODO - // public async ValueTask<> + + public async Task ReceivePingSendPong() + { + var messageCode = await ReadCodeMessageAsync(); + if(messageCode != MessageTypeCode.Ping) + throw new Exception($"Failed to test application protocol: expected Ping, got {messageCode}"); + await WriteCodeMessageAsync(MessageTypeCode.Pong); + } + + + public async Task SendPingReceivePong() + { + await WriteCodeMessageAsync(MessageTypeCode.Ping); + var messageCode = await ReadCodeMessageAsync(); + if(messageCode != MessageTypeCode.Pong) + throw new Exception($"Failed to test application protocol: expected Pong, got {messageCode}"); + } public async ValueTask DisposeAsync() { diff --git a/Meum.Server/ClientConnection.cs b/Meum.Server/ClientConnection.cs index e34de14..5e2380b 100644 --- a/Meum.Server/ClientConnection.cs +++ b/Meum.Server/ClientConnection.cs @@ -1,5 +1,4 @@ -using System.IO; -using System.Net.Quic; +using System.Net.Quic; using DTLib.Logging; using Meum.Core.Messages; @@ -8,52 +7,49 @@ namespace Meum.Server; public class ClientConnection : IAsyncDisposable { private readonly QuicConnection _quicConnection; - private QuicStreamWrapper? _systemStream; + private QuicStreamWrapper _systemStream; private ILogger _logger; - public ClientConnection(QuicConnection quicConnection, ILogger logger) + private ClientConnection(QuicConnection quicConnection, QuicStreamWrapper systemStream, ILogger logger) { _quicConnection = quicConnection; + _systemStream = systemStream; _logger = logger; } - private async Task AcceptStreamAsync(QuicStreamType streamType, CancellationToken ct = default) + public static async Task OpenAsync( + QuicConnection quicConnection, + ILogger logger, + CancellationToken ct) { - var s = await _quicConnection.AcceptInboundStreamAsync(ct); - if (s.Type != streamType) - throw new Exception($"Accepted stream type is invalid: {s.Type} instead of {streamType}"); - var w = new QuicStreamWrapper(s); - return w; - } - - public async void HandleClientRequestsAsync(CancellationToken ct = default) - { - if(_systemStream != null) - throw new Exception("Already connected to client"); + var systemStream = await quicConnection.AcceptStreamAsync(QuicStreamType.Bidirectional, ct); + await systemStream.ReceivePingSendPong(); + var clientConnection = new ClientConnection(quicConnection, systemStream, logger); - _systemStream = await AcceptStreamAsync(QuicStreamType.Bidirectional, ct); - - // receive "Ping" - if(await _systemStream.ReadCodeMessageAsync(ct) != MessageTypeCode.Ping) - throw new Exception("Failed to test application protocol"); - // send "Pong" - await _systemStream.WriteCodeMessageAsync(MessageTypeCode.Pong, ct); - - DataMessageHeader header = await _systemStream.ReadDataMessageHeaderAsync(ct); + DataMessageHeader header = await systemStream.ReadDataMessageHeaderAsync(ct); switch (header.type_code) { case MessageTypeCode.RegistrationRequest: break; case MessageTypeCode.AuthorizationRequest: - + // if (authorized) + // clientConnection.HandleClientRequestsAsync() break; default: throw new Exception($"New connection sent unexpected message: {header.type_code}"); } + + return clientConnection; } + + // private async void HandleClientRequestsAsync(CancellationToken ct = default) + // { + // + // } + public async ValueTask DisposeAsync() { await _quicConnection.DisposeAsync(); diff --git a/Meum.Server/Meum.Server.csproj b/Meum.Server/Meum.Server.csproj index 2dbf323..7c378a0 100644 --- a/Meum.Server/Meum.Server.csproj +++ b/Meum.Server/Meum.Server.csproj @@ -1,15 +1,14 @@  - net9.0 Exe + 0.0.1 + net9.0 + enable + disable - - - - diff --git a/Meum.Server/Program.cs b/Meum.Server/Program.cs index 0da3f41..bcde490 100644 --- a/Meum.Server/Program.cs +++ b/Meum.Server/Program.cs @@ -3,15 +3,8 @@ global using System.Collections.Generic; global using System.Threading; global using System.Threading.Tasks; global using Meum.Core; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Quic; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; using DTLib.Console; using DTLib.Demystifier; -using DTLib.Extensions; using DTLib.Filesystem; using DTLib.Logging; @@ -25,10 +18,10 @@ static class Program { try { - if (!QuicConnection.IsSupported) - throw new Exception("Quic is not supported, check for presence of libmsquic and openssl"); var config = ServerConfig.LoadOrCreate(config_path); var logger = new ConsoleLogger(); + Functions.InitMsQuic(logger); + var server = new Server(config, logger); await server.ListenAsync(); while (true) diff --git a/Meum.Server/README.md b/Meum.Server/README.md index 5a0ecc0..5847fbe 100644 --- a/Meum.Server/README.md +++ b/Meum.Server/README.md @@ -1,4 +1,4 @@ ## Create self-signed certificate ```sh -dotnet dev-certs https -ep bin/Debug/net8.0/self-signed.pem --trust --format PEM --no-password +dotnet dev-certs https -ep bin/Debug/net9.0/self-signed.pem --trust --format PEM --no-password ``` \ No newline at end of file diff --git a/Meum.Server/Server.cs b/Meum.Server/Server.cs index 52c4e5e..3f88e76 100644 --- a/Meum.Server/Server.cs +++ b/Meum.Server/Server.cs @@ -54,8 +54,12 @@ public class Server { ct.ThrowIfCancellationRequested(); var quicConnection = await _listener.AcceptConnectionAsync(ct); - var clientConnection = new ClientConnection(quicConnection, _logger); - await clientConnection.HandleClientRequestsAsync(ct); + var timeOutCts = CancellationTokenSource.CreateLinkedTokenSource(ct); + timeOutCts.CancelAfter(Constants.ConnectionTimeout); + var clientConnection = await ClientConnection.OpenAsync( + quicConnection, + _logger, + timeOutCts.Token); return clientConnection; } } diff --git a/Meum.Server/ServerConfig.cs b/Meum.Server/ServerConfig.cs index 83c8494..d85af76 100644 --- a/Meum.Server/ServerConfig.cs +++ b/Meum.Server/ServerConfig.cs @@ -1,7 +1,5 @@ using System.Text.Json; -using System.Text.Json.Serialization; using DTLib.Filesystem; -using Newtonsoft.Json; namespace Meum.Server; @@ -12,9 +10,14 @@ public class ServerConfig public string certificate_path { get; set; } = "self-signed.pem"; public string? key_path { get; set; } = "self-signed.key"; + private static JsonSerializerOptions _serializerOptions = new() + { + WriteIndented = true, + }; + public void Save(IOPath file_path) { - string serialized = JsonConvert.SerializeObject(this); + string serialized = JsonSerializer.Serialize(this, _serializerOptions); if (string.IsNullOrEmpty(serialized)) throw new Exception("can't serialize config"); File.WriteAllText(file_path, serialized); @@ -25,7 +28,7 @@ public class ServerConfig if(File.Exists(file_path)) { string serialized = File.ReadAllText(file_path); - return JsonConvert.DeserializeObject(serialized) + return JsonSerializer.Deserialize(serialized) ?? throw new Exception("can't deserialize config"); }