using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.ApiClient; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Udp { /// /// Provides a Udp Server /// public class UdpServer : IDisposable { /// /// The _logger /// private readonly ILogger _logger; /// /// The _network manager /// private readonly INetworkManager _networkManager; /// /// The _HTTP server /// private readonly IHttpServer _httpServer; /// /// The _server configuration manager /// private readonly IServerConfigurationManager _serverConfigurationManager; private bool _isDisposed; private readonly List>> _responders = new List>>(); private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _json; /// /// Initializes a new instance of the class. /// /// The logger. /// The network manager. /// The server configuration manager. /// The HTTP server. /// The application host. public UdpServer(ILogger logger, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager, IHttpServer httpServer, IServerApplicationHost appHost, IJsonSerializer json) { _logger = logger; _networkManager = networkManager; _serverConfigurationManager = serverConfigurationManager; _httpServer = httpServer; _appHost = appHost; _json = json; AddMessageResponder("who is MediaBrowserServer?", RespondToV1Message); AddMessageResponder("who is MediaBrowserServer_v2?", RespondToV2Message); } private void AddMessageResponder(string message, Action responder) { var expectedMessageBytes = Encoding.UTF8.GetBytes(message); _responders.Add(new Tuple>(expectedMessageBytes, responder)); } /// /// Raises the event. /// /// The instance containing the event data. private void OnMessageReceived(UdpMessageReceivedEventArgs e) { var responder = _responders.FirstOrDefault(i => i.Item1.SequenceEqual(e.Bytes)); if (responder != null) { try { responder.Item2(e.RemoteEndPoint); } catch (Exception ex) { _logger.ErrorException("Error in OnMessageReceived", ex); } } } private async void RespondToV1Message(string endpoint) { var localAddress = GetLocalIpAddress(); if (!string.IsNullOrEmpty(localAddress)) { // Send a response back with our ip address and port var response = String.Format("MediaBrowserServer|{0}:{1}", localAddress, _serverConfigurationManager.Configuration.HttpServerPortNumber); await SendAsync(Encoding.UTF8.GetBytes(response), endpoint); } else { _logger.Warn("Unable to respond to udp request because the local ip address could not be determined."); } } private async void RespondToV2Message(string endpoint) { var localAddress = GetLocalIpAddress(); if (!string.IsNullOrEmpty(localAddress)) { var serverAddress = string.Format("http://{0}:{1}", localAddress, _serverConfigurationManager.Configuration.HttpServerPortNumber); var response = new ServerDiscoveryInfo { Address = serverAddress, Id = _appHost.ServerId, Name = _appHost.Name }; await SendAsync(Encoding.UTF8.GetBytes(_json.SerializeToString(response)), endpoint); } else { _logger.Warn("Unable to respond to udp request because the local ip address could not be determined."); } } /// /// Gets the local ip address. /// /// System.String. private string GetLocalIpAddress() { var localAddresses = _networkManager.GetLocalIpAddresses().ToList(); // Cross-check the local ip addresses with addresses that have been received on with the http server var matchedAddress = _httpServer.LocalEndPoints .ToList() .Select(i => i.Split(':').FirstOrDefault()) .Where(i => !string.IsNullOrEmpty(i)) .FirstOrDefault(i => localAddresses.Contains(i, StringComparer.OrdinalIgnoreCase)); // Return the first matched address, if found, or the first known local address return matchedAddress ?? localAddresses.FirstOrDefault(); } /// /// The _udp client /// private UdpClient _udpClient; /// /// Starts the specified port. /// /// The port. public void Start(int port) { _udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port)); _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); Task.Run(() => StartListening()); } private async void StartListening() { while (!_isDisposed) { try { var result = await GetResult().ConfigureAwait(false); OnMessageReceived(result); } catch (ObjectDisposedException) { break; } catch (Exception ex) { _logger.ErrorException("Error in StartListening", ex); } } } private Task GetResult() { try { return _udpClient.ReceiveAsync(); } catch (ObjectDisposedException) { return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0))); } catch (Exception ex) { _logger.ErrorException("Error receiving udp message", ex); return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0))); } } /// /// Called when [message received]. /// /// The message. private void OnMessageReceived(UdpReceiveResult message) { if (message.RemoteEndPoint.Port == 0) { return; } var bytes = message.Buffer; OnMessageReceived(new UdpMessageReceivedEventArgs { Bytes = bytes, RemoteEndPoint = message.RemoteEndPoint.ToString() }); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Stops this instance. /// public void Stop() { _isDisposed = true; if (_udpClient != null) { _udpClient.Close(); } } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { if (dispose) { Stop(); } } /// /// Sends the async. /// /// The data. /// The ip address. /// The port. /// Task{System.Int32}. /// data public Task SendAsync(string data, string ipAddress, int port) { return SendAsync(Encoding.UTF8.GetBytes(data), ipAddress, port); } /// /// Sends the async. /// /// The bytes. /// The ip address. /// The port. /// Task{System.Int32}. /// bytes public Task SendAsync(byte[] bytes, string ipAddress, int port) { if (bytes == null) { throw new ArgumentNullException("bytes"); } if (string.IsNullOrEmpty(ipAddress)) { throw new ArgumentNullException("ipAddress"); } return _udpClient.SendAsync(bytes, bytes.Length, ipAddress, port); } /// /// Sends the async. /// /// The bytes. /// The remote end point. /// Task. /// /// bytes /// or /// remoteEndPoint /// public async Task SendAsync(byte[] bytes, string remoteEndPoint) { if (bytes == null) { throw new ArgumentNullException("bytes"); } if (string.IsNullOrEmpty(remoteEndPoint)) { throw new ArgumentNullException("remoteEndPoint"); } try { await _udpClient.SendAsync(bytes, bytes.Length, _networkManager.Parse(remoteEndPoint)).ConfigureAwait(false); _logger.Info("Udp message sent to {0}", remoteEndPoint); } catch (Exception ex) { _logger.ErrorException("Error sending message to {0}", ex, remoteEndPoint); } } } }