diff options
Diffstat (limited to 'Emby.Server.Implementations/Udp/UdpServer.cs')
| -rw-r--r-- | Emby.Server.Implementations/Udp/UdpServer.cs | 298 |
1 files changed, 75 insertions, 223 deletions
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index bd86c6cdc..bf51c3968 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,293 +1,145 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Udp { /// <summary> - /// Provides a Udp Server + /// Provides a Udp Server. /// </summary> - public class UdpServer : IDisposable + public sealed class UdpServer : IDisposable { /// <summary> - /// The _logger + /// Address Override Configuration Key. /// </summary> - private readonly ILogger _logger; + public const string AddressOverrideConfigKey = "PublishedServerUrl"; - private bool _isDisposed; + /// <summary> + /// The _logger. + /// </summary> + private readonly ILogger _logger; + private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _config; - private readonly List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>> _responders = new List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>>(); + private readonly byte[] _receiveBuffer = new byte[8192]; - private readonly IServerApplicationHost _appHost; - private readonly IJsonSerializer _json; + private Socket _udpSocket; + private IPEndPoint _endpoint; + private bool _disposed = false; /// <summary> /// Initializes a new instance of the <see cref="UdpServer" /> class. /// </summary> - public UdpServer(ILogger logger, IServerApplicationHost appHost, IJsonSerializer json, ISocketFactory socketFactory) + /// <param name="logger">The logger.</param> + /// <param name="appHost">The application host.</param> + /// <param name="configuration">The configuration manager.</param> + /// <param name="port">The port.</param> + public UdpServer( + ILogger logger, + IServerApplicationHost appHost, + IConfiguration configuration, + int port) { _logger = logger; _appHost = appHost; - _json = json; - _socketFactory = socketFactory; + _config = configuration; - AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message); - AddMessageResponder("who is EmbyServer?", true, RespondToV2Message); - AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message); - } + _endpoint = new IPEndPoint(IPAddress.Any, port); - private void AddMessageResponder(string message, bool isSubstring, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task> responder) - { - _responders.Add(new Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>(message, isSubstring, responder)); + _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); } - /// <summary> - /// Raises the <see cref="E:MessageReceived" /> event. - /// </summary> - private async void OnMessageReceived(GenericEventArgs<SocketReceiveResult> e) + private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken) { - var message = e.Argument; - - var encoding = Encoding.UTF8; - var responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding); - - if (responder == null) - { - encoding = Encoding.Unicode; - responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding); - } - - if (responder != null) + string? localUrl = _config[AddressOverrideConfigKey]; + if (string.IsNullOrEmpty(localUrl)) { - var cancellationToken = CancellationToken.None; - - try - { - await responder.Item2.Item3(responder.Item1, message.RemoteEndPoint, encoding, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in OnMessageReceived"); - } + localUrl = _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address); } - } - - private Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding) - { - var text = encoding.GetString(buffer, 0, bytesReceived); - var responder = _responders.FirstOrDefault(i => - { - if (i.Item2) - { - return text.IndexOf(i.Item1, StringComparison.OrdinalIgnoreCase) != -1; - } - return string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase); - }); - if (responder == null) + if (string.IsNullOrEmpty(localUrl)) { - return null; + _logger.LogWarning("Unable to respond to udp request because the local ip address could not be determined."); + return; } - return new Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>>(text, responder); - } - private async Task RespondToV2Message(string messageText, IpEndPointInfo endpoint, Encoding encoding, CancellationToken cancellationToken) - { - var parts = messageText.Split('|'); - - var localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName); - if (!string.IsNullOrEmpty(localUrl)) + try { - var response = new ServerDiscoveryInfo - { - Address = localUrl, - Id = _appHost.SystemId, - Name = _appHost.FriendlyName - }; - - await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint, cancellationToken).ConfigureAwait(false); - - if (parts.Length > 1) - { - _appHost.EnableLoopback(parts[1]); - } + await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint).ConfigureAwait(false); } - else + catch (SocketException ex) { - _logger.LogWarning("Unable to respond to udp request because the local ip address could not be determined."); + _logger.LogError(ex, "Error sending response message"); } } /// <summary> - /// The _udp client - /// </summary> - private ISocket _udpClient; - private readonly ISocketFactory _socketFactory; - - /// <summary> /// Starts the specified port. /// </summary> - /// <param name="port">The port.</param> - public void Start(int port) + /// <param name="cancellationToken">The cancellation token to cancel operation.</param> + public void Start(CancellationToken cancellationToken) { - _udpClient = _socketFactory.CreateUdpSocket(port); + _udpSocket.Bind(_endpoint); - Task.Run(() => BeginReceive()); + _ = Task.Run(async () => await BeginReceiveAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } - private readonly byte[] _receiveBuffer = new byte[8192]; - - private void BeginReceive() + private async Task BeginReceiveAsync(CancellationToken cancellationToken) { - if (_isDisposed) - { - return; - } - - try + var infiniteTask = Task.Delay(-1, cancellationToken); + while (!cancellationToken.IsCancellationRequested) { - var result = _udpClient.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, OnReceiveResult); - - if (result.CompletedSynchronously) + try { - OnReceiveResult(result); - } - } - catch (ObjectDisposedException) - { - //TODO Investigate and properly fix. - } - catch (Exception ex) - { - _logger.LogError(ex, "Error receiving udp message"); - } - } - - private void OnReceiveResult(IAsyncResult result) - { - if (_isDisposed) - { - return; - } - - try - { - var socketResult = _udpClient.EndReceive(result); - - OnMessageReceived(socketResult); - } - catch (ObjectDisposedException) - { - //TODO Investigate and properly fix. - } - catch (Exception ex) - { - _logger.LogError(ex, "Error receiving udp message"); - } - - BeginReceive(); - } + var task = _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint); + await Task.WhenAny(task, infiniteTask).ConfigureAwait(false); - /// <summary> - /// Called when [message received]. - /// </summary> - /// <param name="message">The message.</param> - private void OnMessageReceived(SocketReceiveResult message) - { - if (_isDisposed) - { - return; - } + if (!task.IsCompleted) + { + return; + } - if (message.RemoteEndPoint.Port == 0) - { - return; - } + var result = task.Result; - try - { - OnMessageReceived(new GenericEventArgs<SocketReceiveResult> + var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes); + if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase)) + { + await RespondToV2Message(text, result.RemoteEndPoint, cancellationToken).ConfigureAwait(false); + } + } + catch (SocketException ex) { - Argument = message - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error handling UDP message"); - } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _isDisposed = true; - - if (_udpClient != null) + _logger.LogError(ex, "Failed to receive data from socket"); + } + catch (OperationCanceledException) { - _udpClient.Dispose(); + // Don't throw } } } - public async Task SendAsync(byte[] bytes, IpEndPointInfo remoteEndPoint, CancellationToken cancellationToken) + /// <inheritdoc /> + public void Dispose() { - if (_isDisposed) + if (_disposed) { - throw new ObjectDisposedException(GetType().Name); - } - - if (bytes == null) - { - throw new ArgumentNullException(nameof(bytes)); - } - - if (remoteEndPoint == null) - { - throw new ArgumentNullException(nameof(remoteEndPoint)); + return; } - try - { - await _udpClient.SendToAsync(bytes, 0, bytes.Length, remoteEndPoint, cancellationToken).ConfigureAwait(false); - - _logger.LogInformation("Udp message sent to {remoteEndPoint}", remoteEndPoint); - } - catch (OperationCanceledException) - { + _udpSocket?.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error sending message to {remoteEndPoint}", remoteEndPoint); - } + GC.SuppressFinalize(this); } } - } |
