diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 298f68a28e..c5f3593da8 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -4,8 +4,6 @@ using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; using Rssdp; using Rssdp.Infrastructure; @@ -15,13 +13,14 @@ namespace Emby.Dlna.Ssdp { private bool _disposed; - private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private event EventHandler> DeviceDiscoveredInternal; private int _listenerCount; private object _syncLock = new object(); + + /// public event EventHandler> DeviceDiscovered { add @@ -31,6 +30,7 @@ namespace Emby.Dlna.Ssdp _listenerCount++; DeviceDiscoveredInternal += value; } + StartInternal(); } remove @@ -43,21 +43,16 @@ namespace Emby.Dlna.Ssdp } } + /// public event EventHandler> DeviceLeft; private SsdpDeviceLocator _deviceLocator; - private readonly ISocketFactory _socketFactory; private ISsdpCommunicationsServer _commsServer; - public DeviceDiscovery( - ILoggerFactory loggerFactory, - IServerConfigurationManager config, - ISocketFactory socketFactory) + public DeviceDiscovery(IServerConfigurationManager config) { - _logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery)); _config = config; - _socketFactory = socketFactory; } // Call this method from somewhere in your code to start the search. @@ -82,8 +77,8 @@ namespace Emby.Dlna.Ssdp //_DeviceLocator.NotificationFilter = "upnp:rootdevice"; // Connect our event handler so we process devices as they are found - _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; - _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; + _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable; + _deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable; var dueTime = TimeSpan.FromSeconds(5); var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds); @@ -94,7 +89,7 @@ namespace Emby.Dlna.Ssdp } // Process each found device in the event handler - void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e) + private void OnDeviceLocatorDeviceAvailable(object sender, DeviceAvailableEventArgs e) { var originalHeaders = e.DiscoveredDevice.ResponseHeaders; @@ -115,7 +110,7 @@ namespace Emby.Dlna.Ssdp DeviceDiscoveredInternal?.Invoke(this, args); } - private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e) + private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEventArgs e) { var originalHeaders = e.DiscoveredDevice.ResponseHeaders; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f7fe2bd638..7cb7aa7486 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -866,8 +866,7 @@ namespace Emby.Server.Implementations NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); serviceCollection.AddSingleton(NotificationManager); - serviceCollection.AddSingleton( - new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); + serviceCollection.AddSingleton(new DeviceDiscovery(ServerConfigurationManager)); ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); @@ -1730,7 +1729,7 @@ namespace Emby.Server.Implementations /// dns is prefixed with a valid Uri prefix. /// /// The external dns prefix to get the hostname of. - /// The hostname in + /// The hostname in . private static string GetHostnameFromExternalDns(string externalDns) { if (string.IsNullOrEmpty(externalDns)) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d6ca19e3f4..45607dc098 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -10,7 +10,6 @@ - @@ -32,6 +31,7 @@ + diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index d55dc2f18b..08041eb59f 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; @@ -15,179 +14,106 @@ using Mono.Nat; namespace Emby.Server.Implementations.EntryPoints { + /// + /// Server entrypoint handling external port forwarding. + /// public class ExternalPortForwarding : IServerEntryPoint { private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; - private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _config; private readonly IDeviceDiscovery _deviceDiscovery; - private Timer _timer; - - private NatManager _natManager; - private readonly object _createdRulesLock = new object(); - private List _createdRules = new List(); - private readonly object _usnsHandledLock = new object(); - private List _usnsHandled = new List(); + private List _createdRules = new List(); + private Timer _timer; + private string _lastConfigIdentifier; - public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient) + private bool _disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The application host. + /// The configuration manager. + /// The device discovery. + public ExternalPortForwarding( + ILogger logger, + IServerApplicationHost appHost, + IServerConfigurationManager config, + IDeviceDiscovery deviceDiscovery) { - _logger = loggerFactory.CreateLogger("PortMapper"); + _logger = logger; _appHost = appHost; _config = config; _deviceDiscovery = deviceDiscovery; - _httpClient = httpClient; - _config.ConfigurationUpdated += _config_ConfigurationUpdated1; } - private void _config_ConfigurationUpdated1(object sender, EventArgs e) - { - _config_ConfigurationUpdated(sender, e); - } - - private string _lastConfigIdentifier; private string GetConfigIdentifier() { - var values = new List(); + const char Separator = '|'; var config = _config.Configuration; - values.Add(config.EnableUPnP.ToString()); - values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture)); - values.Add(_appHost.EnableHttps.ToString()); - values.Add((config.EnableRemoteAccess).ToString()); - - return string.Join("|", values.ToArray()); + return new StringBuilder(32) + .Append(config.EnableUPnP).Append(Separator) + .Append(config.PublicPort).Append(Separator) + .Append(_appHost.HttpPort).Append(Separator) + .Append(_appHost.HttpsPort).Append(Separator) + .Append(_appHost.EnableHttps).Append(Separator) + .Append(config.EnableRemoteAccess).Append(Separator) + .ToString(); } - private async void _config_ConfigurationUpdated(object sender, EventArgs e) + private void OnConfigurationUpdated(object sender, EventArgs e) { if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) { - DisposeNat(); + Stop(); - await RunAsync(); + Start(); } } + /// public Task RunAsync() { - if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess) - { - Start(); - } + Start(); - _config.ConfigurationUpdated -= _config_ConfigurationUpdated; - _config.ConfigurationUpdated += _config_ConfigurationUpdated; + _config.ConfigurationUpdated += OnConfigurationUpdated; return Task.CompletedTask; } private void Start() { - _logger.LogDebug("Starting NAT discovery"); - if (_natManager == null) + if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess) { - _natManager = new NatManager(_logger, _httpClient); - _natManager.DeviceFound += NatUtility_DeviceFound; - _natManager.StartDiscovery(); + return; } + _logger.LogDebug("Starting NAT discovery"); + + NatUtility.DeviceFound += OnNatUtilityDeviceFound; + NatUtility.StartDiscovery(); + _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); - _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; + _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered; _lastConfigIdentifier = GetConfigIdentifier(); } - private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) + private void Stop() { - if (_disposed) - { - return; - } + _logger.LogDebug("Stopping NAT discovery"); - var info = e.Argument; + NatUtility.StopDiscovery(); + NatUtility.DeviceFound -= OnNatUtilityDeviceFound; - if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty; + _timer?.Dispose(); - if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty; - - // Filter device type - if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1) - { - return; - } - - var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn; - - if (info.Location == null) - { - return; - } - - lock (_usnsHandledLock) - { - if (_usnsHandled.Contains(identifier)) - { - return; - } - - _usnsHandled.Add(identifier); - } - - _logger.LogDebug("Found NAT device: " + identifier); - - if (IPAddress.TryParse(info.Location.Host, out var address)) - { - // The Handle method doesn't need the port - var endpoint = new IPEndPoint(address, info.Location.Port); - - IPAddress localAddress = null; - - try - { - var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false); - - if (Uri.TryCreate(localAddressString, UriKind.Absolute, out var uri)) - { - localAddressString = uri.Host; - - if (!IPAddress.TryParse(localAddressString, out localAddress)) - { - return; - } - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error"); - return; - } - - if (_disposed) - { - return; - } - - // This should never happen, but the Handle method will throw ArgumentNullException if it does - if (localAddress == null) - { - return; - } - - var natManager = _natManager; - if (natManager != null) - { - await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false); - } - } + _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered; } private void ClearCreatedRules(object state) @@ -196,30 +122,24 @@ namespace Emby.Server.Implementations.EntryPoints { _createdRules.Clear(); } - - lock (_usnsHandledLock) - { - _usnsHandled.Clear(); - } } - void NatUtility_DeviceFound(object sender, DeviceEventArgs e) + private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs e) { - if (_disposed) - { - return; - } + NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp); + } + private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e) + { try { var device = e.Device; CreateRules(device); } - catch + catch (Exception ex) { - // Commenting out because users are reporting problems out of our control - //_logger.LogError(ex, "Error creating port forwarding rules"); + _logger.LogError(ex, "Error creating port forwarding rules"); } } @@ -232,15 +152,13 @@ namespace Emby.Server.Implementations.EntryPoints // On some systems the device discovered event seems to fire repeatedly // This check will help ensure we're not trying to port map the same device over and over - var address = device.LocalAddress; - - var addressString = address.ToString(); + var address = device.DeviceEndpoint; lock (_createdRulesLock) { - if (!_createdRules.Contains(addressString)) + if (!_createdRules.Contains(address)) { - _createdRules.Add(addressString); + _createdRules.Add(address); } else { @@ -268,54 +186,43 @@ namespace Emby.Server.Implementations.EntryPoints } } - private Task CreatePortMap(INatDevice device, int privatePort, int publicPort) + private Task CreatePortMap(INatDevice device, int privatePort, int publicPort) { - _logger.LogDebug("Creating port map on local port {0} to public port {1} with device {2}", privatePort, publicPort, device.LocalAddress.ToString()); + _logger.LogDebug( + "Creating port map on local port {0} to public port {1} with device {2}", + privatePort, + publicPort, + device.DeviceEndpoint); - return device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort) - { - Description = _appHost.Name - }); + return device.CreatePortMapAsync( + new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name)); } - private bool _disposed = false; + /// public void Dispose() { - _disposed = true; - DisposeNat(); + Dispose(true); + GC.SuppressFinalize(this); } - private void DisposeNat() + /// + /// 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) { - _logger.LogDebug("Stopping NAT discovery"); - - if (_timer != null) + if (_disposed) { - _timer.Dispose(); - _timer = null; + return; } - _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; + _config.ConfigurationUpdated -= OnConfigurationUpdated; - var natManager = _natManager; + Stop(); - if (natManager != null) - { - _natManager = null; + _timer = null; - using (natManager) - { - try - { - natManager.StopDiscovery(); - natManager.DeviceFound -= NatUtility_DeviceFound; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error stopping NAT Discovery"); - } - } - } + _disposed = true; } } } diff --git a/MediaBrowser.sln b/MediaBrowser.sln index dd4e9f8a69..3b0353800d 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -33,8 +33,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" @@ -129,10 +127,6 @@ Global {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs deleted file mode 100644 index 1241170c1c..0000000000 --- a/Mono.Nat/AbstractNatDevice.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; -using System.Threading.Tasks; - -namespace Mono.Nat -{ - public abstract class AbstractNatDevice : INatDevice - { - private DateTime lastSeen; - - protected AbstractNatDevice() - { - } - - public abstract IPAddress LocalAddress { get; } - - public DateTime LastSeen - { - get { return lastSeen; } - set { lastSeen = value; } - } - - public abstract Task CreatePortMap(Mapping mapping); - } -} diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs deleted file mode 100644 index 54480598d2..0000000000 --- a/Mono.Nat/Enums/ProtocolType.cs +++ /dev/null @@ -1,36 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; - -namespace Mono.Nat -{ - public enum Protocol - { - Tcp, - Udp - } -} diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs deleted file mode 100644 index 6358a0c29f..0000000000 --- a/Mono.Nat/EventArgs/DeviceEventArgs.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; - -namespace Mono.Nat -{ - public class DeviceEventArgs : EventArgs - { - private INatDevice device; - - public DeviceEventArgs(INatDevice device) - { - this.device = device; - } - - public INatDevice Device - { - get { return this.device; } - } - } -} diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs deleted file mode 100644 index 6a15090710..0000000000 --- a/Mono.Nat/INatDevice.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; -using System.Threading.Tasks; - -namespace Mono.Nat -{ - public interface INatDevice - { - Task CreatePortMap (Mapping mapping); - - IPAddress LocalAddress { get; } - - DateTime LastSeen { get; set; } - } -} diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs deleted file mode 100644 index 95c2f2aa90..0000000000 --- a/Mono.Nat/ISearcher.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans -// Nicholas Terry -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net.Sockets; -using System.Net; - -namespace Mono.Nat -{ - internal interface ISearcher - { - event EventHandler DeviceFound; - - void Search(); - void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint); - } -} diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs deleted file mode 100644 index 5b15d4e14f..0000000000 --- a/Mono.Nat/Mapping.cs +++ /dev/null @@ -1,121 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; - -namespace Mono.Nat -{ - public class Mapping - { - private string description; - private DateTime expiration; - private int lifetime; - private int privatePort; - private Protocol protocol; - private int publicPort; - - public Mapping(Protocol protocol, int privatePort, int publicPort) - : this (protocol, privatePort, publicPort, 0) - { - } - - public Mapping(Protocol protocol, int privatePort, int publicPort, int lifetime) - { - this.protocol = protocol; - this.privatePort = privatePort; - this.publicPort = publicPort; - this.lifetime = lifetime; - - if (lifetime == int.MaxValue) - this.expiration = DateTime.MaxValue; - else if (lifetime == 0) - this.expiration = DateTime.Now; - else - this.expiration = DateTime.Now.AddSeconds (lifetime); - } - - public string Description - { - get { return description; } - set { description = value; } - } - - public Protocol Protocol - { - get { return protocol; } - internal set { protocol = value; } - } - - public int PrivatePort - { - get { return privatePort; } - internal set { privatePort = value; } - } - - public int PublicPort - { - get { return publicPort; } - internal set { publicPort = value; } - } - - public int Lifetime - { - get { return lifetime; } - internal set { lifetime = value; } - } - - public DateTime Expiration - { - get { return expiration; } - internal set { expiration = value; } - } - - public bool IsExpired() - { - return expiration < DateTime.Now; - } - - public override bool Equals(object obj) - { - var other = obj as Mapping; - return other == null ? false : this.protocol == other.protocol && - this.privatePort == other.privatePort && this.publicPort == other.publicPort; - } - - public override int GetHashCode() - { - return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode(); - } - - public override string ToString() - { - return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}", - this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime ); - } - } -} diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj deleted file mode 100644 index c143000b37..0000000000 --- a/Mono.Nat/Mono.Nat.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - netstandard2.1 - false - - - diff --git a/Mono.Nat/NatManager.cs b/Mono.Nat/NatManager.cs deleted file mode 100644 index 3ed01a6b3c..0000000000 --- a/Mono.Nat/NatManager.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Net; -using System.Collections.Generic; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Dlna; -using Microsoft.Extensions.Logging; -using System.Linq; - -namespace Mono.Nat -{ - public class NatManager : IDisposable - { - public event EventHandler DeviceFound; - - private List controllers = new List(); - - private ILogger Logger; - private IHttpClient HttpClient; - - public NatManager(ILogger logger, IHttpClient httpClient) - { - Logger = logger; - HttpClient = httpClient; - } - - private object _runSyncLock = new object(); - public void StartDiscovery() - { - lock (_runSyncLock) - { - if (controllers.Count > 0) - { - return; - } - - controllers.Add(new PmpSearcher(Logger)); - - foreach (var searcher in controllers) - { - searcher.DeviceFound += Searcher_DeviceFound; - } - } - } - - public void StopDiscovery() - { - lock (_runSyncLock) - { - var disposables = controllers.OfType().ToList(); - controllers.Clear(); - - foreach (var disposable in disposables) - { - disposable.Dispose(); - } - } - } - - public void Dispose() - { - StopDiscovery(); - } - - public Task Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol) - { - switch (protocol) - { - case NatProtocol.Upnp: - var searcher = new UpnpSearcher(Logger, HttpClient); - searcher.DeviceFound += Searcher_DeviceFound; - return searcher.Handle(localAddress, deviceInfo, endpoint); - default: - throw new ArgumentException("Unexpected protocol: " + protocol); - } - } - - private void Searcher_DeviceFound(object sender, DeviceEventArgs e) - { - if (DeviceFound != null) - { - DeviceFound(sender, e); - } - } - } -} diff --git a/Mono.Nat/NatProtocol.cs b/Mono.Nat/NatProtocol.cs deleted file mode 100644 index 2768f133c5..0000000000 --- a/Mono.Nat/NatProtocol.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Mono.Nat -{ - public enum NatProtocol - { - Upnp = 0, - Pmp = 1 - } -} diff --git a/Mono.Nat/Pmp/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs deleted file mode 100644 index 83fa8e07cd..0000000000 --- a/Mono.Nat/Pmp/PmpConstants.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Authors: -// Ben Motmans -// -// Copyright (C) 2007 Ben Motmans -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; - -namespace Mono.Nat.Pmp -{ - internal static class PmpConstants - { - public const byte Version = (byte)0; - - public const byte OperationCode = (byte)0; - public const byte OperationCodeUdp = (byte)1; - public const byte OperationCodeTcp = (byte)2; - public const byte ServerNoop = (byte)128; - - public const int ClientPort = 5350; - public const int ServerPort = 5351; - - public const int RetryDelay = 250; - public const int RetryAttempts = 9; - - public const int RecommendedLeaseTime = 60 * 60; - public const int DefaultLeaseTime = RecommendedLeaseTime; - - public const short ResultCodeSuccess = 0; - public const short ResultCodeUnsupportedVersion = 1; - public const short ResultCodeNotAuthorized = 2; - public const short ResultCodeNetworkFailure = 3; - public const short ResultCodeOutOfResources = 4; - public const short ResultCodeUnsupportedOperationCode = 5; - } -} diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs deleted file mode 100644 index 95bd72a6cc..0000000000 --- a/Mono.Nat/Pmp/PmpNatDevice.cs +++ /dev/null @@ -1,217 +0,0 @@ -// -// Authors: -// Ben Motmans -// -// Copyright (C) 2007 Ben Motmans -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Collections.Generic; -using System.Threading.Tasks; -using MediaBrowser.Model.Extensions; -using Microsoft.Extensions.Logging; - -namespace Mono.Nat.Pmp -{ - internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable - { - private IPAddress localAddress; - private IPAddress publicAddress; - private ILogger _logger; - - internal PmpNatDevice(IPAddress localAddress, IPAddress publicAddress, ILogger logger) - { - if (localAddress == null) - { - throw new ArgumentNullException(nameof(localAddress)); - } - - this.localAddress = localAddress; - this.publicAddress = publicAddress; - _logger = logger; - } - - public override IPAddress LocalAddress - { - get { return localAddress; } - } - - public override Task CreatePortMap(Mapping mapping) - { - return InternalCreatePortMapAsync(mapping, true); - } - - public override bool Equals(object obj) - { - var device = obj as PmpNatDevice; - return (device == null) ? false : this.Equals(device); - } - - public override int GetHashCode() - { - return this.publicAddress.GetHashCode(); - } - - public bool Equals(PmpNatDevice other) - { - return (other == null) ? false : this.publicAddress.Equals(other.publicAddress); - } - - private async Task InternalCreatePortMapAsync(Mapping mapping, bool create) - { - var package = new List(); - - package.Add(PmpConstants.Version); - package.Add(mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp); - package.Add(0); //reserved - package.Add(0); //reserved - package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)mapping.PrivatePort))); - package.AddRange( - BitConverter.GetBytes(create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0)); - package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(mapping.Lifetime))); - - try - { - byte[] buffer = package.ToArray(); - int attempt = 0; - int delay = PmpConstants.RetryDelay; - - using (var udpClient = new UdpClient()) - { - var cancellationTokenSource = new CancellationTokenSource(); - - while (attempt < PmpConstants.RetryAttempts) - { - await udpClient.SendAsync(buffer, buffer.Length, new IPEndPoint(LocalAddress, PmpConstants.ServerPort)); - - if (attempt == 0) - { - await Task.Run(() => CreatePortMapListen(udpClient, mapping, cancellationTokenSource.Token)); - } - - attempt++; - delay *= 2; - await Task.Delay(delay).ConfigureAwait(false); - } - - cancellationTokenSource.Cancel(); - } - } - catch (OperationCanceledException) - { - - } - catch (Exception e) - { - string type = create ? "create" : "delete"; - string message = String.Format("Failed to {0} portmap (protocol={1}, private port={2}) {3}", - type, - mapping.Protocol, - mapping.PrivatePort, - e.Message); - _logger.LogDebug(message); - throw e; - } - - return mapping; - } - - private async void CreatePortMapListen(UdpClient udpClient, Mapping mapping, CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - var result = await udpClient.ReceiveAsync().ConfigureAwait(false); - var endPoint = result.RemoteEndPoint; - byte[] data = data = result.Buffer; - - if (data.Length < 16) - continue; - - if (data[0] != PmpConstants.Version) - continue; - - var opCode = (byte)(data[1] & 127); - - var protocol = Protocol.Tcp; - if (opCode == PmpConstants.OperationCodeUdp) - protocol = Protocol.Udp; - - short resultCode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 2)); - int epoch = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 4)); - - short privatePort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 8)); - short publicPort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10)); - - var lifetime = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 12)); - - if (privatePort < 0 || publicPort < 0 || resultCode != PmpConstants.ResultCodeSuccess) - { - var errors = new[] - { - "Success", - "Unsupported Version", - "Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off)" - , - "Network Failure (e.g. NAT box itself has not obtained a DHCP lease)", - "Out of resources (NAT box cannot create any more mappings at this time)", - "Unsupported opcode" - }; - - var errorMsg = errors[resultCode]; - _logger.LogDebug("Error in CreatePortMapListen: " + errorMsg); - return; - } - - if (lifetime == 0) return; //mapping was deleted - - //mapping was created - //TODO: verify that the private port+protocol are a match - mapping.PublicPort = publicPort; - mapping.Protocol = protocol; - mapping.Expiration = DateTime.Now.AddSeconds(lifetime); - return; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in CreatePortMapListen"); - return; - } - } - } - - /// - /// Overridden. - /// - /// - public override string ToString() - { - return String.Format("PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}", - this.localAddress, this.publicAddress, this.LastSeen); - } - } -} diff --git a/Mono.Nat/Pmp/PmpSearcher.cs b/Mono.Nat/Pmp/PmpSearcher.cs deleted file mode 100644 index 46c2e9d800..0000000000 --- a/Mono.Nat/Pmp/PmpSearcher.cs +++ /dev/null @@ -1,235 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans -// Nicholas Terry -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; -using Mono.Nat.Pmp; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using System.Linq; - -namespace Mono.Nat -{ - internal class PmpSearcher : ISearcher, IDisposable - { - private ILogger _logger; - - private int timeout = 250; - private DateTime nextSearch; - public event EventHandler DeviceFound; - - public PmpSearcher(ILogger logger) - { - _logger = logger; - - CreateSocketsAndAddGateways(); - } - - public void Dispose() - { - var list = sockets.ToList(); - sockets.Clear(); - - foreach (var s in list) - { - using (s) - { - s.Close(); - } - } - } - - private List sockets; - private Dictionary> gatewayLists; - - private void CreateSocketsAndAddGateways() - { - sockets = new List(); - gatewayLists = new Dictionary>(); - - try - { - foreach (var n in NetworkInterface.GetAllNetworkInterfaces()) - { - if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown) - continue; - IPInterfaceProperties properties = n.GetIPProperties(); - var gatewayList = new List(); - - foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses) - { - if (gateway.Address.AddressFamily == AddressFamily.InterNetwork) - { - gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort)); - } - } - if (gatewayList.Count == 0) - { - /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */ - foreach (var gw2 in properties.DnsAddresses) - { - if (gw2.AddressFamily == AddressFamily.InterNetwork) - { - gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort)); - } - } - foreach (UnicastIPAddressInformation unicast in properties.UnicastAddresses) - { - if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred - && unicast.AddressPreferredLifetime != UInt32.MaxValue - && */unicast.Address.AddressFamily == AddressFamily.InterNetwork) - { - var bytes = unicast.Address.GetAddressBytes(); - bytes[3] = 1; - gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort)); - } - } - } - - if (gatewayList.Count > 0) - { - foreach (var address in properties.UnicastAddresses) - { - if (address.Address.AddressFamily == AddressFamily.InterNetwork) - { - UdpClient client; - - try - { - client = new UdpClient(new IPEndPoint(address.Address, 0)); - } - catch (SocketException) - { - continue; // Move on to the next address. - } - - gatewayLists.Add(client, gatewayList); - sockets.Add(client); - } - } - } - } - } - catch (Exception) - { - // NAT-PMP does not use multicast, so there isn't really a good fallback. - } - } - - public async void Search() - { - foreach (UdpClient s in sockets) - { - try - { - await Search(s).ConfigureAwait(false); - } - catch - { - // Ignore any search errors - } - } - } - - async Task Search(UdpClient client) - { - // Sort out the time for the next search first. The spec says the - // timeout should double after each attempt. Once it reaches 64 seconds - // (and that attempt fails), assume no devices available - nextSearch = DateTime.Now.AddMilliseconds(timeout); - timeout *= 2; - - // We've tried 9 times as per spec, try searching again in 5 minutes - if (timeout == 128 * 1000) - { - timeout = 250; - nextSearch = DateTime.Now.AddMinutes(10); - return; - } - - // The nat-pmp search message. Must be sent to GatewayIP:53531 - byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode }; - foreach (IPEndPoint gatewayEndpoint in gatewayLists[client]) - { - await client.SendAsync(buffer, buffer.Length, gatewayEndpoint).ConfigureAwait(false); - } - } - - bool IsSearchAddress(IPAddress address) - { - foreach (var gatewayList in gatewayLists.Values) - foreach (var gatewayEndpoint in gatewayList) - if (gatewayEndpoint.Address.Equals(address)) - return true; - return false; - } - - public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) - { - if (!IsSearchAddress(endpoint.Address)) - return; - if (response.Length != 12) - return; - if (response[0] != PmpConstants.Version) - return; - if (response[1] != PmpConstants.ServerNoop) - return; - int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); - if (errorcode != 0) - _logger.LogDebug("Non zero error: {0}", errorcode); - - var publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] }); - nextSearch = DateTime.Now.AddMinutes(5); - timeout = 250; - - OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp, _logger))); - } - - public DateTime NextSearch - { - get { return nextSearch; } - } - private void OnDeviceFound(DeviceEventArgs args) - { - if (DeviceFound != null) - DeviceFound(this, args); - } - - public NatProtocol Protocol - { - get { return NatProtocol.Pmp; } - } - } -} diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs deleted file mode 100644 index dc47f2ffeb..0000000000 --- a/Mono.Nat/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Mono.Nat")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2006 Alan McGovern. Copyright © 2007 Ben Motmans. Code releases under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs deleted file mode 100644 index f619f5ca41..0000000000 --- a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Net; -using MediaBrowser.Common.Net; - -namespace Mono.Nat.Upnp -{ - internal class GetServicesMessage : MessageBase - { - private string _servicesDescriptionUrl; - private EndPoint _hostAddress; - - public GetServicesMessage(string description, EndPoint hostAddress) - : base(null) - { - if (string.IsNullOrEmpty(description)) - { - throw new ArgumentException("Description is null/empty", nameof(description)); - } - - this._servicesDescriptionUrl = description; - this._hostAddress = hostAddress ?? throw new ArgumentNullException(nameof(hostAddress)); - } - - public override string Method => "GET"; - - public override HttpRequestOptions Encode() - { - var req = new HttpRequestOptions() - { - Url = $"http://{this._hostAddress}{this._servicesDescriptionUrl}" - }; - - req.RequestHeaders.Add("ACCEPT-LANGUAGE", "en"); - - return req; - } - } -} diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs deleted file mode 100644 index 7d6844e32a..0000000000 --- a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs +++ /dev/null @@ -1,75 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System.Net; -using System.IO; -using System.Globalization; -using System.Text; -using System.Xml; -using MediaBrowser.Common.Net; - -namespace Mono.Nat.Upnp -{ - internal class CreatePortMappingMessage : MessageBase - { - #region Private Fields - - private IPAddress localIpAddress; - private Mapping mapping; - - #endregion - - - #region Constructors - public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device) - : base(device) - { - this.mapping = mapping; - this.localIpAddress = localIpAddress; - } - #endregion - - public override HttpRequestOptions Encode() - { - var culture = CultureInfo.InvariantCulture; - - var builder = new StringBuilder(256); - XmlWriter writer = CreateWriter(builder); - - WriteFullElement(writer, "NewRemoteHost", string.Empty); - WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture)); - WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"); - WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture)); - WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString()); - WriteFullElement(writer, "NewEnabled", "1"); - WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description); - WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString()); - - writer.Flush(); - return CreateRequest("AddPortMapping", builder.ToString()); - } - } -} diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs deleted file mode 100644 index d47241d4a1..0000000000 --- a/Mono.Nat/Upnp/Messages/UpnpMessage.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// -// Copyright (C) 2006 Alan McGovern -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System.Xml; -using System.Text; -using MediaBrowser.Common.Net; - -namespace Mono.Nat.Upnp -{ - internal abstract class MessageBase - { - protected UpnpNatDevice device; - - protected MessageBase(UpnpNatDevice device) - { - this.device = device; - } - - protected HttpRequestOptions CreateRequest(string upnpMethod, string methodParameters) - { - var req = new HttpRequestOptions() - { - Url = $"http://{this.device.HostEndPoint}{this.device.ControlUrl}", - EnableKeepAlive = false, - RequestContentType = "text/xml", - RequestContent = "" - + "" - + "" - + methodParameters - + "" - + "" - + "\r\n\r\n" - }; - - req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); - - return req; - } - - public abstract HttpRequestOptions Encode(); - - public virtual string Method => "POST"; - - protected void WriteFullElement(XmlWriter writer, string element, string value) - { - writer.WriteStartElement(element); - writer.WriteString(value); - writer.WriteEndElement(); - } - - protected XmlWriter CreateWriter(StringBuilder sb) - { - var settings = new XmlWriterSettings(); - settings.ConformanceLevel = ConformanceLevel.Fragment; - return XmlWriter.Create(sb, settings); - } - } -} diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs deleted file mode 100644 index 3b54c4680e..0000000000 --- a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs +++ /dev/null @@ -1,111 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans -// Nicholas Terry -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// Copyright (C) 2014 Nicholas Terry -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; -using Mono.Nat.Upnp; -using System.Diagnostics; -using System.Net.Sockets; -using System.Net.NetworkInformation; -using MediaBrowser.Common.Net; -using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Dlna; -using System.Threading.Tasks; - -namespace Mono.Nat -{ - internal class UpnpSearcher : ISearcher - { - public event EventHandler DeviceFound; - - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - - public UpnpSearcher(ILogger logger, IHttpClient httpClient) - { - _logger = logger; - _httpClient = httpClient; - } - - public void Search() - { - } - - public async Task Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint) - { - if (localAddress == null) - { - throw new ArgumentNullException(nameof(localAddress)); - } - - try - { - /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. - * Any other device type is no good to us for this purpose. See the IGP overview paper - * page 5 for an overview of device types and their hierarchy. - * http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ - - /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which - * version it is and apply the correct URN. */ - - /* Some routers don't correctly implement the version ID on the URN, so we only search for the type - * prefix. */ - - // We have an internet gateway device now - var d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty, _logger, _httpClient); - - await d.GetServicesList().ConfigureAwait(false); - - OnDeviceFound(new DeviceEventArgs(d)); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error decoding device response"); - } - } - - public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) - { - } - - private void OnDeviceFound(DeviceEventArgs args) - { - if (DeviceFound != null) - DeviceFound(this, args); - } - - public NatProtocol Protocol - { - get { return NatProtocol.Upnp; } - } - } -} diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs deleted file mode 100644 index 3ff1eeb90e..0000000000 --- a/Mono.Nat/Upnp/UpnpNatDevice.cs +++ /dev/null @@ -1,267 +0,0 @@ -// -// Authors: -// Alan McGovern alan.mcgovern@gmail.com -// Ben Motmans -// -// Copyright (C) 2006 Alan McGovern -// Copyright (C) 2007 Ben Motmans -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -using System; -using System.Net; -using System.Xml; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Dlna; - -namespace Mono.Nat.Upnp -{ - public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable - { - private EndPoint hostEndPoint; - private IPAddress localAddress; - private string serviceDescriptionUrl; - private string controlUrl; - private string serviceType; - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - - public override IPAddress LocalAddress - { - get { return localAddress; } - } - - internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger, IHttpClient httpClient) - { - if (localAddress == null) - { - throw new ArgumentNullException(nameof(localAddress)); - } - - this.LastSeen = DateTime.Now; - this.localAddress = localAddress; - - // Split the string at the "location" section so i can extract the ipaddress and service description url - string locationDetails = deviceInfo.Location.ToString(); - this.serviceType = serviceType; - _logger = logger; - _httpClient = httpClient; - - // Make sure we have no excess whitespace - locationDetails = locationDetails.Trim(); - - // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address - // Are we going to get addresses with the "http://" attached? - if (locationDetails.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) - { - _logger.LogDebug("Found device at: {0}", locationDetails); - // This bit strings out the "http://" from the string - locationDetails = locationDetails.Substring(7); - - this.hostEndPoint = hostEndPoint; - - // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip - // and port information - this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); - } - else - { - _logger.LogDebug("Couldn't decode address. Please send following string to the developer: "); - } - } - - public async Task GetServicesList() - { - // Create a HTTPWebRequest to download the list of services the device offers - var message = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint); - - using (var response = await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false)) - { - OnServicesReceived(response); - } - } - - private void OnServicesReceived(HttpResponseInfo response) - { - int abortCount = 0; - int bytesRead = 0; - byte[] buffer = new byte[10240]; - var servicesXml = new StringBuilder(); - var xmldoc = new XmlDocument(); - - using (var s = response.Content) - { - if (response.StatusCode != HttpStatusCode.OK) - { - _logger.LogDebug("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode); - return; // FIXME: This the best thing to do?? - } - - while (true) - { - bytesRead = s.Read(buffer, 0, buffer.Length); - servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); - try - { - xmldoc.LoadXml(servicesXml.ToString()); - break; - } - catch (XmlException) - { - // If we can't receive the entire XML within 500ms, then drop the connection - // Unfortunately not all routers supply a valid ContentLength (mine doesn't) - // so this hack is needed to keep testing our recieved data until it gets successfully - // parsed by the xmldoc. Without this, the code will never pick up my router. - if (abortCount++ > 50) - { - return; - } - _logger.LogDebug("{0}: Couldn't parse services list", HostEndPoint); - System.Threading.Thread.Sleep(10); - } - } - - var ns = new XmlNamespaceManager(xmldoc.NameTable); - ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0"); - XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns); - - foreach (XmlNode node in nodes) - { - //Go through each service there - foreach (XmlNode service in node.ChildNodes) - { - //If the service is a WANIPConnection, then we have what we want - string type = service["serviceType"].InnerText; - _logger.LogDebug("{0}: Found service: {1}", HostEndPoint, type); - - // TODO: Add support for version 2 of UPnP. - if (string.Equals(type, "urn:schemas-upnp-org:service:WANPPPConnection:1", StringComparison.OrdinalIgnoreCase) || - string.Equals(type, "urn:schemas-upnp-org:service:WANIPConnection:1", StringComparison.OrdinalIgnoreCase)) - { - this.controlUrl = service["controlURL"].InnerText; - _logger.LogDebug("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl); - - Uri u; - if (Uri.TryCreate(controlUrl, UriKind.RelativeOrAbsolute, out u)) - { - if (u.IsAbsoluteUri) - { - var old = hostEndPoint; - IPAddress parsedHostIpAddress; - if (IPAddress.TryParse(u.Host, out parsedHostIpAddress)) - { - this.hostEndPoint = new IPEndPoint(parsedHostIpAddress, u.Port); - //_logger.LogDebug("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint); - this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length); - //_logger.LogDebug("{0}: New control url: {1}", HostEndPoint, controlUrl); - } - } - } - else - { - _logger.LogDebug("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl); - } - return; - } - } - } - - //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding - //So we don't invoke the callback, so this device is never added to our lists - } - } - - /// - /// The EndPoint that the device is at - /// - internal EndPoint HostEndPoint - { - get { return this.hostEndPoint; } - } - - /// - /// The relative url of the xml file that describes the list of services is at - /// - internal string ServiceDescriptionUrl - { - get { return this.serviceDescriptionUrl; } - } - - /// - /// The relative url that we can use to control the port forwarding - /// - internal string ControlUrl - { - get { return this.controlUrl; } - } - - /// - /// The service type we're using on the device - /// - public string ServiceType - { - get { return serviceType; } - } - - public override async Task CreatePortMap(Mapping mapping) - { - var message = new CreatePortMappingMessage(mapping, localAddress, this); - using (await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false)) - { - - } - } - - public override bool Equals(object obj) - { - var device = obj as UpnpNatDevice; - return (device == null) ? false : this.Equals((device)); - } - - - public bool Equals(UpnpNatDevice other) - { - return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint) - //&& this.controlUrl == other.controlUrl - && this.serviceDescriptionUrl == other.serviceDescriptionUrl); - } - - public override int GetHashCode() - { - return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode()); - } - - /// - /// Overridden. - /// - /// - public override string ToString() - { - //GetExternalIP is blocking and can throw exceptions, can't use it here. - return String.Format( - "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}", - this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen); - } - } -}