using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Options; namespace FinalFactory.Rendezvous.Server.Transport; /// /// Owns the cancellable UDP socket used by the future NAT mediator. /// public sealed partial class UdpMediatorService : BackgroundService { private readonly ILogger _logger; private readonly UdpMediatorOptions _options; private UdpClient? _udpClient; /// /// Initializes a new UDP mediator service. /// public UdpMediatorService( IOptions options, ILogger logger) { _options = options.Value; _logger = logger; } /// /// Gets the bound endpoint after startup completes. /// public IPEndPoint? LocalEndpoint { get; private set; } /// public override Task StartAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_udpClient is not null) { throw new InvalidOperationException("The UDP mediator is already running."); } IPAddress listenAddress = IPAddress.Parse(_options.ListenAddress); UdpClient udpClient = new(new IPEndPoint(listenAddress, _options.Port)); _udpClient = udpClient; IPEndPoint localEndpoint = (IPEndPoint?)udpClient.Client.LocalEndPoint ?? throw new InvalidOperationException("The UDP socket did not expose its bound endpoint."); LocalEndpoint = localEndpoint; LogMediatorListening(_logger, localEndpoint.Address, localEndpoint.Port); return base.StartAsync(cancellationToken); } /// public override async Task StopAsync(CancellationToken cancellationToken) { await base.StopAsync(cancellationToken).ConfigureAwait(false); _udpClient?.Dispose(); _udpClient = null; LocalEndpoint = null; LogMediatorStopped(_logger); } /// public override void Dispose() { _udpClient?.Dispose(); _udpClient = null; LocalEndpoint = null; base.Dispose(); } /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { UdpClient udpClient = _udpClient ?? throw new InvalidOperationException("The UDP mediator socket was not initialized."); try { while (!stoppingToken.IsCancellationRequested) { _ = await udpClient.ReceiveAsync(stoppingToken).ConfigureAwait(false); // Bootstrap deliberately emits no UDP response. Protocol handling lands in #11. } } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { // Expected during normal shutdown. } catch (ObjectDisposedException) when (stoppingToken.IsCancellationRequested) { // Disposing the socket is the fallback that releases a blocked receive. } finally { LocalEndpoint = null; } } [LoggerMessage( EventId = 1, Level = LogLevel.Information, Message = "UDP mediator listening on {ListenAddress}:{ListenPort}")] private static partial void LogMediatorListening( ILogger logger, IPAddress listenAddress, int listenPort); [LoggerMessage( EventId = 2, Level = LogLevel.Information, Message = "UDP mediator stopped")] private static partial void LogMediatorStopped(ILogger logger); }