< Summary - Jellyfin

Information
Class: Jellyfin.Networking.PortForwardingHost
Assembly: Jellyfin.Networking
File(s): /srv/git/jellyfin/src/Jellyfin.Networking/PortForwardingHost.cs
Line coverage
85%
Covered lines: 41
Uncovered lines: 7
Coverable lines: 48
Total lines: 192
Line coverage: 85.4%
Branch coverage
50%
Covered branches: 7
Total branches: 14
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetConfigIdentifier()100%11100%
OnConfigurationUpdated(...)100%22100%
StartAsync(...)100%11100%
StopAsync(...)100%11100%
Start()33.33%16.67633.33%
Stop()50%22100%
Dispose()50%4.05485.71%

File(s)

/srv/git/jellyfin/src/Jellyfin.Networking/PortForwardingHost.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Net;
 5using System.Text;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using MediaBrowser.Common.Net;
 9using MediaBrowser.Controller;
 10using MediaBrowser.Controller.Configuration;
 11using Microsoft.Extensions.Hosting;
 12using Microsoft.Extensions.Logging;
 13using Mono.Nat;
 14
 15namespace Jellyfin.Networking;
 16
 17/// <summary>
 18/// <see cref="IHostedService"/> responsible for UPnP port forwarding.
 19/// </summary>
 20public sealed class PortForwardingHost : IHostedService, IDisposable
 21{
 22    private readonly IServerApplicationHost _appHost;
 23    private readonly ILogger<PortForwardingHost> _logger;
 24    private readonly IServerConfigurationManager _config;
 2225    private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new();
 26
 27    private Timer? _timer;
 28    private string? _configIdentifier;
 29    private bool _disposed;
 30
 31    /// <summary>
 32    /// Initializes a new instance of the <see cref="PortForwardingHost"/> class.
 33    /// </summary>
 34    /// <param name="logger">The logger.</param>
 35    /// <param name="appHost">The application host.</param>
 36    /// <param name="config">The configuration manager.</param>
 37    public PortForwardingHost(
 38        ILogger<PortForwardingHost> logger,
 39        IServerApplicationHost appHost,
 40        IServerConfigurationManager config)
 41    {
 2242        _logger = logger;
 2243        _appHost = appHost;
 2244        _config = config;
 2245    }
 46
 47    private string GetConfigIdentifier()
 48    {
 49        const char Separator = '|';
 1850        var config = _config.GetNetworkConfiguration();
 51
 1852        return new StringBuilder(32)
 1853            .Append(config.EnableUPnP).Append(Separator)
 1854            .Append(config.PublicHttpPort).Append(Separator)
 1855            .Append(config.PublicHttpsPort).Append(Separator)
 1856            .Append(_appHost.HttpPort).Append(Separator)
 1857            .Append(_appHost.HttpsPort).Append(Separator)
 1858            .Append(_appHost.ListenWithHttps).Append(Separator)
 1859            .Append(config.EnableRemoteAccess).Append(Separator)
 1860            .ToString();
 61    }
 62
 63    private void OnConfigurationUpdated(object? sender, EventArgs e)
 64    {
 1865        var oldConfigIdentifier = _configIdentifier;
 1866        _configIdentifier = GetConfigIdentifier();
 67
 1868        if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
 69        {
 1770            Stop();
 1771            Start();
 72        }
 1873    }
 74
 75    /// <inheritdoc />
 76    public Task StartAsync(CancellationToken cancellationToken)
 77    {
 2278        Start();
 79
 2280        _config.ConfigurationUpdated += OnConfigurationUpdated;
 81
 2282        return Task.CompletedTask;
 83    }
 84
 85    /// <inheritdoc />
 86    public Task StopAsync(CancellationToken cancellationToken)
 87    {
 2288        Stop();
 89
 2290        return Task.CompletedTask;
 91    }
 92
 93    private void Start()
 94    {
 3995        var config = _config.GetNetworkConfiguration();
 3996        if (!config.EnableUPnP || !config.EnableRemoteAccess)
 97        {
 3998            return;
 99        }
 100
 0101        _logger.LogInformation("Starting NAT discovery");
 102
 0103        NatUtility.DeviceFound += OnNatUtilityDeviceFound;
 0104        NatUtility.StartDiscovery();
 105
 0106        _timer?.Dispose();
 0107        _timer = new Timer(_ => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
 0108    }
 109
 110    private void Stop()
 111    {
 39112        _logger.LogInformation("Stopping NAT discovery");
 113
 39114        NatUtility.StopDiscovery();
 39115        NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
 116
 39117        _timer?.Dispose();
 39118        _timer = null;
 39119    }
 120
 121    private async void OnNatUtilityDeviceFound(object? sender, DeviceEventArgs e)
 122    {
 123        ObjectDisposedException.ThrowIf(_disposed, this);
 124
 125        try
 126        {
 127            // On some systems the device discovered event seems to fire repeatedly
 128            // This check will help ensure we're not trying to port map the same device over and over
 129            if (!_createdRules.TryAdd(e.Device.DeviceEndpoint, 0))
 130            {
 131                return;
 132            }
 133
 134            await Task.WhenAll(CreatePortMaps(e.Device)).ConfigureAwait(false);
 135        }
 136        catch (Exception ex)
 137        {
 138            _logger.LogError(ex, "Error creating port forwarding rules");
 139        }
 140    }
 141
 142    private IEnumerable<Task> CreatePortMaps(INatDevice device)
 143    {
 144        var config = _config.GetNetworkConfiguration();
 145        yield return CreatePortMap(device, _appHost.HttpPort, config.PublicHttpPort);
 146
 147        if (_appHost.ListenWithHttps)
 148        {
 149            yield return CreatePortMap(device, _appHost.HttpsPort, config.PublicHttpsPort);
 150        }
 151    }
 152
 153    private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
 154    {
 155        _logger.LogDebug(
 156            "Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
 157            privatePort,
 158            publicPort,
 159            device.DeviceEndpoint);
 160
 161        try
 162        {
 163            var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
 164            await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
 165        }
 166        catch (Exception ex)
 167        {
 168            _logger.LogError(
 169                ex,
 170                "Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoi
 171                privatePort,
 172                publicPort,
 173                device.DeviceEndpoint);
 174        }
 175    }
 176
 177    /// <inheritdoc />
 178    public void Dispose()
 179    {
 22180        if (_disposed)
 181        {
 0182            return;
 183        }
 184
 22185        _config.ConfigurationUpdated -= OnConfigurationUpdated;
 186
 22187        _timer?.Dispose();
 22188        _timer = null;
 189
 22190        _disposed = true;
 22191    }
 192}