From 27649724c87ad35d88c09c41c055491d7c2144d3 Mon Sep 17 00:00:00 2001 From: AeonLucid Date: Fri, 7 Jan 2022 07:09:55 +0100 Subject: [PATCH] Add QoS beacon --- .../Services/Qos/QosServer.cs | 79 +++++++++++++++++++ .../Services/Qos/QosService.cs | 30 +++++++ src/Prospect.Server.Api/Startup.cs | 4 + 3 files changed, 113 insertions(+) create mode 100644 src/Prospect.Server.Api/Services/Qos/QosServer.cs create mode 100644 src/Prospect.Server.Api/Services/Qos/QosService.cs diff --git a/src/Prospect.Server.Api/Services/Qos/QosServer.cs b/src/Prospect.Server.Api/Services/Qos/QosServer.cs new file mode 100644 index 0000000..e632904 --- /dev/null +++ b/src/Prospect.Server.Api/Services/Qos/QosServer.cs @@ -0,0 +1,79 @@ +using System.Net; +using System.Net.Sockets; + +namespace Prospect.Server.Api.Services.Qos; + +public class QosServer : IAsyncDisposable +{ + private readonly ILogger _logger; + private readonly UdpClient _client; + + private bool _isStopping; + private Task? _runTask; + + public QosServer(ILogger logger) + { + _logger = logger; + _client = new UdpClient(new IPEndPoint(IPAddress.Any, 3075)); + } + + public void Start() + { + _runTask = RunAsync(); + } + + public async Task StopAsync() + { + _isStopping = true; + _client.Dispose(); + + if (_runTask != null) + { + await _runTask; + } + } + + private async Task RunAsync() + { + try + { + while (true) + { + var result = await _client.ReceiveAsync(); + + _logger.LogInformation("Received {Data} from {Address}", result.Buffer, result.RemoteEndPoint); + + if (result.Buffer.Length == 22) + { + // The server will reply with a single datagram, with the message contents having the + // first 2 bytes "flipped" to 0x0000 (0000 0000 0000 0000). + // The rest of the datagram contents will be copied from the initial ping. + result.Buffer[0] = 0x00; + result.Buffer[1] = 0x00; + result.Buffer[2] = 0x00; + result.Buffer[3] = 0x00; + + await _client.SendAsync(result.Buffer, result.RemoteEndPoint); + } + } + } + catch (SocketException e) when (e.SocketErrorCode == SocketError.OperationAborted || + e.SocketErrorCode == SocketError.Interrupted || + e.SocketErrorCode == SocketError.InvalidArgument && !OperatingSystem.IsWindows()) + { + if (!_isStopping) + { + _logger.LogError(e, "SocketException caught in QosServer"); + } + } + catch (Exception e) + { + _logger.LogError(e, "Exception caught in QosServer"); + } + } + + public async ValueTask DisposeAsync() + { + await StopAsync(); + } +} \ No newline at end of file diff --git a/src/Prospect.Server.Api/Services/Qos/QosService.cs b/src/Prospect.Server.Api/Services/Qos/QosService.cs new file mode 100644 index 0000000..58e4f5b --- /dev/null +++ b/src/Prospect.Server.Api/Services/Qos/QosService.cs @@ -0,0 +1,30 @@ +namespace Prospect.Server.Api.Services.Qos; + +/// +/// Implementation of a QoS beacon. +/// https://docs.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/using-qos-beacons-to-measure-player-latency-to-azure#quality-of-service-beacons +/// +public class QosService : IHostedService +{ + private readonly ILogger _logger; + private readonly QosServer _server; + + public QosService(ILogger logger, QosServer server) + { + _logger = logger; + _server = server; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Starting QoS beacon"); + _server.Start(); + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Stopping QoS beacon"); + await _server.StopAsync(); + } +} \ No newline at end of file diff --git a/src/Prospect.Server.Api/Startup.cs b/src/Prospect.Server.Api/Startup.cs index 88893c8..7c8c966 100644 --- a/src/Prospect.Server.Api/Startup.cs +++ b/src/Prospect.Server.Api/Startup.cs @@ -3,6 +3,7 @@ using Prospect.Server.Api.Converters; using Prospect.Server.Api.Middleware; using Prospect.Server.Api.Services.Auth; using Prospect.Server.Api.Services.Database; +using Prospect.Server.Api.Services.Qos; using Prospect.Server.Api.Services.UserData; using Serilog; @@ -32,6 +33,9 @@ public class Startup services.AddSingleton(); + services.AddHostedService(); + services.AddSingleton(); + services.AddAuthentication(_ => {