117 lines
3.6 KiB
C#
117 lines
3.6 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace FinalFactory.Rendezvous.Server.Transport;
|
|
|
|
/// <summary>
|
|
/// Owns the cancellable UDP socket used by the future NAT mediator.
|
|
/// </summary>
|
|
public sealed partial class UdpMediatorService : BackgroundService
|
|
{
|
|
private readonly ILogger<UdpMediatorService> _logger;
|
|
private readonly UdpMediatorOptions _options;
|
|
private UdpClient? _udpClient;
|
|
|
|
/// <summary>
|
|
/// Initializes a new UDP mediator service.
|
|
/// </summary>
|
|
public UdpMediatorService(
|
|
IOptions<UdpMediatorOptions> options,
|
|
ILogger<UdpMediatorService> logger)
|
|
{
|
|
_options = options.Value;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the bound endpoint after startup completes.
|
|
/// </summary>
|
|
public IPEndPoint? LocalEndpoint { get; private set; }
|
|
|
|
/// <inheritdoc />
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
await base.StopAsync(cancellationToken).ConfigureAwait(false);
|
|
_udpClient?.Dispose();
|
|
_udpClient = null;
|
|
LocalEndpoint = null;
|
|
LogMediatorStopped(_logger);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Dispose()
|
|
{
|
|
_udpClient?.Dispose();
|
|
_udpClient = null;
|
|
LocalEndpoint = null;
|
|
base.Dispose();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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);
|
|
}
|