mirror of https://github.com/jellyfin/jellyfin.git
Use Mono.Nat Nuget package
This commit is contained in:
parent
3bfb36a67d
commit
b0a25c4237
|
@ -4,8 +4,6 @@ using System.Linq;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Rssdp;
|
using Rssdp;
|
||||||
using Rssdp.Infrastructure;
|
using Rssdp.Infrastructure;
|
||||||
|
|
||||||
|
@ -15,13 +13,14 @@ namespace Emby.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
||||||
|
|
||||||
private int _listenerCount;
|
private int _listenerCount;
|
||||||
private object _syncLock = new object();
|
private object _syncLock = new object();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
|
||||||
{
|
{
|
||||||
add
|
add
|
||||||
|
@ -31,6 +30,7 @@ namespace Emby.Dlna.Ssdp
|
||||||
_listenerCount++;
|
_listenerCount++;
|
||||||
DeviceDiscoveredInternal += value;
|
DeviceDiscoveredInternal += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartInternal();
|
StartInternal();
|
||||||
}
|
}
|
||||||
remove
|
remove
|
||||||
|
@ -43,21 +43,16 @@ namespace Emby.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||||
|
|
||||||
private SsdpDeviceLocator _deviceLocator;
|
private SsdpDeviceLocator _deviceLocator;
|
||||||
|
|
||||||
private readonly ISocketFactory _socketFactory;
|
|
||||||
private ISsdpCommunicationsServer _commsServer;
|
private ISsdpCommunicationsServer _commsServer;
|
||||||
|
|
||||||
public DeviceDiscovery(
|
public DeviceDiscovery(IServerConfigurationManager config)
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IServerConfigurationManager config,
|
|
||||||
ISocketFactory socketFactory)
|
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
|
|
||||||
_config = config;
|
_config = config;
|
||||||
_socketFactory = socketFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this method from somewhere in your code to start the search.
|
// Call this method from somewhere in your code to start the search.
|
||||||
|
@ -82,8 +77,8 @@ namespace Emby.Dlna.Ssdp
|
||||||
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
|
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
|
||||||
|
|
||||||
// Connect our event handler so we process devices as they are found
|
// Connect our event handler so we process devices as they are found
|
||||||
_deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
|
_deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
|
||||||
_deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
|
_deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable;
|
||||||
|
|
||||||
var dueTime = TimeSpan.FromSeconds(5);
|
var dueTime = TimeSpan.FromSeconds(5);
|
||||||
var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
|
var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
|
||||||
|
@ -94,7 +89,7 @@ namespace Emby.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each found device in the event handler
|
// 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;
|
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
|
||||||
|
|
||||||
|
@ -115,7 +110,7 @@ namespace Emby.Dlna.Ssdp
|
||||||
DeviceDiscoveredInternal?.Invoke(this, args);
|
DeviceDiscoveredInternal?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
|
private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
|
||||||
{
|
{
|
||||||
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
|
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
|
||||||
|
|
||||||
|
|
|
@ -866,8 +866,7 @@ namespace Emby.Server.Implementations
|
||||||
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
|
||||||
serviceCollection.AddSingleton(NotificationManager);
|
serviceCollection.AddSingleton(NotificationManager);
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDeviceDiscovery>(
|
serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
|
||||||
new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
|
|
||||||
|
|
||||||
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
|
||||||
serviceCollection.AddSingleton(ChapterManager);
|
serviceCollection.AddSingleton(ChapterManager);
|
||||||
|
@ -1730,7 +1729,7 @@ namespace Emby.Server.Implementations
|
||||||
/// dns is prefixed with a valid Uri prefix.
|
/// dns is prefixed with a valid Uri prefix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
|
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
|
||||||
/// <returns>The hostname in <paramref name="externalDns"/></returns>
|
/// <returns>The hostname in <paramref name="externalDns"/>.</returns>
|
||||||
private static string GetHostnameFromExternalDns(string externalDns)
|
private static string GetHostnameFromExternalDns(string externalDns)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(externalDns))
|
if (string.IsNullOrEmpty(externalDns))
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
|
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
|
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
|
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
|
||||||
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
|
|
||||||
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
|
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
|
||||||
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
|
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
|
||||||
|
@ -32,6 +31,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
|
@ -19,57 +18,51 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
|
|
||||||
private NatManager _natManager;
|
|
||||||
|
|
||||||
private readonly object _createdRulesLock = new object();
|
private readonly object _createdRulesLock = new object();
|
||||||
private List<string> _createdRules = new List<string>();
|
private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
|
||||||
private readonly object _usnsHandledLock = new object();
|
private string _lastConfigIdentifier;
|
||||||
private List<string> _usnsHandled = new List<string>();
|
|
||||||
|
|
||||||
public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient)
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
public ExternalPortForwarding(
|
||||||
|
ILogger<ExternalPortForwarding> logger,
|
||||||
|
IServerApplicationHost appHost,
|
||||||
|
IServerConfigurationManager config,
|
||||||
|
IDeviceDiscovery deviceDiscovery)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger("PortMapper");
|
_logger = logger;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_config = config;
|
_config = config;
|
||||||
_deviceDiscovery = deviceDiscovery;
|
_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()
|
private string GetConfigIdentifier()
|
||||||
{
|
{
|
||||||
var values = new List<string>();
|
const char Separator = '|';
|
||||||
var config = _config.Configuration;
|
var config = _config.Configuration;
|
||||||
|
|
||||||
values.Add(config.EnableUPnP.ToString());
|
return new StringBuilder(32)
|
||||||
values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
|
.Append(config.EnableUPnP).Append(Separator)
|
||||||
values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
|
.Append(config.PublicPort).Append(Separator)
|
||||||
values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
|
.Append(_appHost.HttpPort).Append(Separator)
|
||||||
values.Add(_appHost.EnableHttps.ToString());
|
.Append(_appHost.HttpsPort).Append(Separator)
|
||||||
values.Add((config.EnableRemoteAccess).ToString());
|
.Append(_appHost.EnableHttps).Append(Separator)
|
||||||
|
.Append(config.EnableRemoteAccess).Append(Separator)
|
||||||
return string.Join("|", values.ToArray());
|
.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void _config_ConfigurationUpdated(object sender, EventArgs e)
|
private async void OnConfigurationUpdated(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
DisposeNat();
|
Stop();
|
||||||
|
|
||||||
await RunAsync();
|
await RunAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +73,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
Start();
|
Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
_config.ConfigurationUpdated += OnConfigurationUpdated;
|
||||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
@ -89,105 +81,27 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Starting NAT discovery");
|
_logger.LogDebug("Starting NAT discovery");
|
||||||
if (_natManager == null)
|
|
||||||
{
|
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
|
||||||
_natManager = new NatManager(_logger, _httpClient);
|
NatUtility.StartDiscovery();
|
||||||
_natManager.DeviceFound += NatUtility_DeviceFound;
|
|
||||||
_natManager.StartDiscovery();
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
||||||
|
|
||||||
_lastConfigIdentifier = GetConfigIdentifier();
|
_lastConfigIdentifier = GetConfigIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
private void Stop()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
_logger.LogDebug("Stopping NAT discovery");
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearCreatedRules(object state)
|
private void ClearCreatedRules(object state)
|
||||||
|
@ -196,30 +110,24 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
_createdRules.Clear();
|
_createdRules.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_usnsHandledLock)
|
|
||||||
{
|
|
||||||
_usnsHandled.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
|
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var device = e.Device;
|
var device = e.Device;
|
||||||
|
|
||||||
CreateRules(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 +140,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
// On some systems the device discovered event seems to fire repeatedly
|
// 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
|
// This check will help ensure we're not trying to port map the same device over and over
|
||||||
var address = device.LocalAddress;
|
var address = device.DeviceEndpoint;
|
||||||
|
|
||||||
var addressString = address.ToString();
|
|
||||||
|
|
||||||
lock (_createdRulesLock)
|
lock (_createdRulesLock)
|
||||||
{
|
{
|
||||||
if (!_createdRules.Contains(addressString))
|
if (!_createdRules.Contains(address))
|
||||||
{
|
{
|
||||||
_createdRules.Add(addressString);
|
_createdRules.Add(address);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -268,54 +174,41 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
private Task<Mapping> 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)
|
return device.CreatePortMapAsync(
|
||||||
{
|
new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
|
||||||
Description = _appHost.Name
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _disposed = false;
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
Dispose(true);
|
||||||
DisposeNat();
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeNat()
|
/// <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)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Stopping NAT discovery");
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stop();
|
||||||
|
|
||||||
if (_timer != null)
|
|
||||||
{
|
|
||||||
_timer.Dispose();
|
|
||||||
_timer = null;
|
_timer = null;
|
||||||
}
|
|
||||||
|
|
||||||
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
_disposed = true;
|
||||||
|
|
||||||
var natManager = _natManager;
|
|
||||||
|
|
||||||
if (natManager != null)
|
|
||||||
{
|
|
||||||
_natManager = null;
|
|
||||||
|
|
||||||
using (natManager)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
natManager.StopDiscovery();
|
|
||||||
natManager.DeviceFound -= NatUtility_DeviceFound;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error stopping NAT Discovery");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}"
|
||||||
EndProject
|
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}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = 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
|
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Alan McGovern alan.mcgovern@gmail.com
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
//
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Alan McGovern alan.mcgovern@gmail.com
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
//
|
|
||||||
// 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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Alan McGovern alan.mcgovern@gmail.com
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
// Nicholas Terry <nick.i.terry@gmail.com>
|
|
||||||
//
|
|
||||||
// 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<DeviceEventArgs> DeviceFound;
|
|
||||||
|
|
||||||
void Search();
|
|
||||||
void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Alan McGovern alan.mcgovern@gmail.com
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
//
|
|
||||||
// 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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="..\SharedVersion.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -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<DeviceEventArgs> DeviceFound;
|
|
||||||
|
|
||||||
private List<ISearcher> controllers = new List<ISearcher>();
|
|
||||||
|
|
||||||
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<IDisposable>().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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Mono.Nat
|
|
||||||
{
|
|
||||||
public enum NatProtocol
|
|
||||||
{
|
|
||||||
Upnp = 0,
|
|
||||||
Pmp = 1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,217 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
//
|
|
||||||
// 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<PmpNatDevice>
|
|
||||||
{
|
|
||||||
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<Mapping> InternalCreatePortMapAsync(Mapping mapping, bool create)
|
|
||||||
{
|
|
||||||
var package = new List<byte>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overridden.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return String.Format("PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}",
|
|
||||||
this.localAddress, this.publicAddress, this.LastSeen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Alan McGovern alan.mcgovern@gmail.com
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
// Nicholas Terry <nick.i.terry@gmail.com>
|
|
||||||
//
|
|
||||||
// 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<DeviceEventArgs> 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<UdpClient> sockets;
|
|
||||||
private Dictionary<UdpClient, List<IPEndPoint>> gatewayLists;
|
|
||||||
|
|
||||||
private void CreateSocketsAndAddGateways()
|
|
||||||
{
|
|
||||||
sockets = new List<UdpClient>();
|
|
||||||
gatewayLists = new Dictionary<UdpClient, List<IPEndPoint>>();
|
|
||||||
|
|
||||||
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<IPEndPoint>();
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)]
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 = "<s:Envelope "
|
|
||||||
+ "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
|
|
||||||
+ "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
|
||||||
+ "<s:Body>"
|
|
||||||
+ "<u:" + upnpMethod + " "
|
|
||||||
+ "xmlns:u=\"" + device.ServiceType + "\">"
|
|
||||||
+ methodParameters
|
|
||||||
+ "</u:" + upnpMethod + ">"
|
|
||||||
+ "</s:Body>"
|
|
||||||
+ "</s:Envelope>\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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Alan McGovern alan.mcgovern@gmail.com
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
// Nicholas Terry <nick.i.terry@gmail.com>
|
|
||||||
//
|
|
||||||
// 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<DeviceEventArgs> 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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,267 +0,0 @@
|
||||||
//
|
|
||||||
// Authors:
|
|
||||||
// Alan McGovern alan.mcgovern@gmail.com
|
|
||||||
// Ben Motmans <ben.motmans@gmail.com>
|
|
||||||
//
|
|
||||||
// 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<UpnpNatDevice>
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The EndPoint that the device is at
|
|
||||||
/// </summary>
|
|
||||||
internal EndPoint HostEndPoint
|
|
||||||
{
|
|
||||||
get { return this.hostEndPoint; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The relative url of the xml file that describes the list of services is at
|
|
||||||
/// </summary>
|
|
||||||
internal string ServiceDescriptionUrl
|
|
||||||
{
|
|
||||||
get { return this.serviceDescriptionUrl; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The relative url that we can use to control the port forwarding
|
|
||||||
/// </summary>
|
|
||||||
internal string ControlUrl
|
|
||||||
{
|
|
||||||
get { return this.controlUrl; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The service type we're using on the device
|
|
||||||
/// </summary>
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overridden.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue