jellyfin-server/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

214 lines
6.8 KiB
C#
Raw Normal View History

#nullable disable
2019-11-01 17:38:54 +00:00
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
2014-08-21 15:55:35 +00:00
using System.Collections.Generic;
2015-04-29 18:48:34 +00:00
using System.Net;
2019-11-01 20:22:35 +00:00
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Networking.Configuration;
2016-11-11 04:25:21 +00:00
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
2016-10-29 18:46:56 +00:00
using MediaBrowser.Model.Dlna;
using Microsoft.Extensions.Logging;
2016-11-11 04:25:21 +00:00
using Mono.Nat;
2014-02-26 04:38:21 +00:00
namespace Emby.Server.Implementations.EntryPoints
2014-02-26 04:38:21 +00:00
{
2019-11-01 20:25:37 +00:00
/// <summary>
/// Server entrypoint handling external port forwarding.
/// </summary>
2014-02-26 04:38:21 +00:00
public class ExternalPortForwarding : IServerEntryPoint
{
private readonly IServerApplicationHost _appHost;
2020-06-06 00:15:56 +00:00
private readonly ILogger<ExternalPortForwarding> _logger;
2014-02-26 04:38:21 +00:00
private readonly IServerConfigurationManager _config;
2016-09-11 07:33:53 +00:00
private readonly IDeviceDiscovery _deviceDiscovery;
2016-04-04 19:07:43 +00:00
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
2019-11-01 20:25:37 +00:00
private Timer _timer;
private string _configIdentifier;
2019-11-01 20:22:35 +00:00
private bool _disposed = false;
2019-11-01 20:25:37 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appHost">The application host.</param>
/// <param name="config">The configuration manager.</param>
/// <param name="deviceDiscovery">The device discovery.</param>
2019-11-01 20:22:35 +00:00
public ExternalPortForwarding(
ILogger<ExternalPortForwarding> logger,
IServerApplicationHost appHost,
IServerConfigurationManager config,
IDeviceDiscovery deviceDiscovery)
2014-02-26 04:38:21 +00:00
{
2019-11-01 20:22:35 +00:00
_logger = logger;
2014-02-26 04:38:21 +00:00
_appHost = appHost;
_config = config;
2016-09-11 07:33:53 +00:00
_deviceDiscovery = deviceDiscovery;
2018-09-12 17:26:21 +00:00
}
2016-04-04 19:07:43 +00:00
private string GetConfigIdentifier()
{
2019-11-01 20:22:35 +00:00
const char Separator = '|';
var config = _config.GetNetworkConfiguration();
2016-04-04 19:07:43 +00:00
2019-11-01 20:22:35 +00:00
return new StringBuilder(32)
.Append(config.EnableUPnP).Append(Separator)
.Append(config.PublicPort).Append(Separator)
.Append(config.PublicHttpsPort).Append(Separator)
2019-11-01 20:22:35 +00:00
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
.Append(_appHost.ListenWithHttps).Append(Separator)
2019-11-01 20:22:35 +00:00
.Append(config.EnableRemoteAccess).Append(Separator)
.ToString();
2015-01-19 04:29:57 +00:00
}
2014-02-26 04:38:21 +00:00
2019-11-02 09:31:14 +00:00
private void OnConfigurationUpdated(object sender, EventArgs e)
2014-02-26 04:38:21 +00:00
{
var oldConfigIdentifier = _configIdentifier;
_configIdentifier = GetConfigIdentifier();
if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
2016-04-04 19:07:43 +00:00
{
2019-11-01 20:22:35 +00:00
Stop();
2019-11-02 09:31:14 +00:00
Start();
2016-04-04 19:07:43 +00:00
}
2016-03-31 19:22:07 +00:00
}
2014-08-21 15:55:35 +00:00
2019-11-01 20:25:37 +00:00
/// <inheritdoc />
2019-01-27 14:40:37 +00:00
public Task RunAsync()
2016-03-31 19:22:07 +00:00
{
2019-11-02 09:31:14 +00:00
Start();
2016-04-04 19:07:43 +00:00
2019-11-01 20:22:35 +00:00
_config.ConfigurationUpdated += OnConfigurationUpdated;
2019-01-27 14:40:37 +00:00
return Task.CompletedTask;
2014-02-26 04:38:21 +00:00
}
2016-04-04 19:07:43 +00:00
private void Start()
2014-02-26 04:38:21 +00:00
{
var config = _config.GetNetworkConfiguration();
if (!config.EnableUPnP || !config.EnableRemoteAccess)
2019-11-02 09:31:14 +00:00
{
return;
}
_logger.LogInformation("Starting NAT discovery");
2019-11-01 20:22:35 +00:00
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
NatUtility.StartDiscovery();
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
2016-04-04 19:07:43 +00:00
}
2019-11-01 20:22:35 +00:00
private void Stop()
2016-04-04 19:07:43 +00:00
{
_logger.LogInformation("Stopping NAT discovery");
2016-09-11 07:33:53 +00:00
2019-11-01 20:22:35 +00:00
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
2016-10-28 01:07:40 +00:00
2019-11-01 20:22:35 +00:00
_timer?.Dispose();
}
2017-01-02 19:36:54 +00:00
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
2019-11-01 20:22:35 +00:00
{
2016-04-04 19:07:43 +00:00
try
2014-02-26 04:38:21 +00:00
{
await CreateRules(e.Device).ConfigureAwait(false);
2016-04-04 19:07:43 +00:00
}
2019-11-01 20:22:35 +00:00
catch (Exception ex)
2016-04-04 19:07:43 +00:00
{
2019-11-01 20:22:35 +00:00
_logger.LogError(ex, "Error creating port forwarding rules");
2016-03-31 18:46:03 +00:00
}
2014-02-26 04:38:21 +00:00
}
private Task CreateRules(INatDevice device)
{
2017-01-02 19:36:54 +00:00
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
2017-01-02 19:36:54 +00:00
}
2016-03-31 18:46:03 +00:00
// 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
if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
2016-04-04 19:07:43 +00:00
{
return Task.CompletedTask;
2017-02-22 20:58:02 +00:00
}
return Task.WhenAll(CreatePortMaps(device));
}
2016-10-28 01:07:40 +00:00
private IEnumerable<Task> CreatePortMaps(INatDevice device)
{
var config = _config.GetNetworkConfiguration();
yield return CreatePortMap(device, _appHost.HttpPort, config.PublicPort);
2020-04-26 20:55:00 +00:00
if (_appHost.ListenWithHttps)
2016-10-28 01:07:40 +00:00
{
yield return CreatePortMap(device, _appHost.HttpsPort, config.PublicHttpsPort);
2016-10-28 01:07:40 +00:00
}
2016-04-04 19:07:43 +00:00
}
private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
2016-04-04 19:07:43 +00:00
{
2019-11-01 20:22:35 +00:00
_logger.LogDebug(
"Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
2019-11-01 20:22:35 +00:00
privatePort,
publicPort,
device.DeviceEndpoint);
try
{
var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
privatePort,
publicPort,
device.DeviceEndpoint);
}
2014-02-26 04:38:21 +00:00
}
2019-11-01 20:22:35 +00:00
/// <inheritdoc />
2016-03-31 18:46:03 +00:00
public void Dispose()
2014-02-26 04:38:21 +00:00
{
2019-11-01 20:22:35 +00:00
Dispose(true);
GC.SuppressFinalize(this);
2016-03-31 18:46:03 +00:00
}
2014-02-26 04:38:21 +00:00
2019-11-01 20:22:35 +00:00
/// <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)
2016-03-31 18:46:03 +00:00
{
2019-11-01 20:22:35 +00:00
if (_disposed)
2014-02-26 04:38:21 +00:00
{
2019-11-01 20:22:35 +00:00
return;
2016-04-04 19:07:43 +00:00
}
2019-11-02 09:31:14 +00:00
_config.ConfigurationUpdated -= OnConfigurationUpdated;
2019-11-01 20:22:35 +00:00
Stop();
2018-09-12 17:26:21 +00:00
2019-11-01 20:22:35 +00:00
_timer = null;
2018-09-12 17:26:21 +00:00
2019-11-01 20:22:35 +00:00
_disposed = true;
2014-02-26 04:38:21 +00:00
}
}
2018-12-28 15:48:26 +00:00
}