diff options
Diffstat (limited to 'src/Jellyfin.Networking')
| -rw-r--r-- | src/Jellyfin.Networking/Jellyfin.Networking.csproj | 3 | ||||
| -rw-r--r-- | src/Jellyfin.Networking/Manager/NetworkManager.cs | 82 | ||||
| -rw-r--r-- | src/Jellyfin.Networking/PortForwardingHost.cs | 192 |
3 files changed, 30 insertions, 247 deletions
diff --git a/src/Jellyfin.Networking/Jellyfin.Networking.csproj b/src/Jellyfin.Networking/Jellyfin.Networking.csproj index 472cdb7ef..1a146549d 100644 --- a/src/Jellyfin.Networking/Jellyfin.Networking.csproj +++ b/src/Jellyfin.Networking/Jellyfin.Networking.csproj @@ -14,7 +14,4 @@ <ProjectReference Include="..\..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> </ItemGroup> - <ItemGroup> - <PackageReference Include="Mono.Nat" /> - </ItemGroup> </Project> diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index 10aed673b..6f6ee5146 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -27,7 +27,7 @@ public class NetworkManager : INetworkManager, IDisposable /// <summary> /// Threading lock for network properties. /// </summary> - private readonly object _initLock; + private readonly Lock _initLock; private readonly ILogger<NetworkManager> _logger; @@ -35,7 +35,7 @@ public class NetworkManager : INetworkManager, IDisposable private readonly IConfiguration _startupConfig; - private readonly object _networkEventLock; + private readonly Lock _networkEventLock; /// <summary> /// Holds the published server URLs and the IPs to use them on. @@ -50,11 +50,6 @@ public class NetworkManager : INetworkManager, IDisposable private bool _eventfire; /// <summary> - /// List of all interface MAC addresses. - /// </summary> - private IReadOnlyList<PhysicalAddress> _macAddresses; - - /// <summary> /// Dictionary containing interface addresses and their subnets. /// </summary> private List<IPData> _interfaces; @@ -91,9 +86,8 @@ public class NetworkManager : INetworkManager, IDisposable _startupConfig = startupConfig; _initLock = new(); _interfaces = new List<IPData>(); - _macAddresses = new List<PhysicalAddress>(); _publishedServerUrls = new List<PublishedServerUriOverride>(); - _networkEventLock = new object(); + _networkEventLock = new(); _remoteAddressFilter = new List<IPNetwork>(); _ = bool.TryParse(startupConfig[DetectNetworkChangeKey], out var detectNetworkChange); @@ -215,7 +209,6 @@ public class NetworkManager : INetworkManager, IDisposable /// <summary> /// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state. - /// Generate a list of all active mac addresses that aren't loopback addresses. /// </summary> private void InitializeInterfaces() { @@ -224,7 +217,6 @@ public class NetworkManager : INetworkManager, IDisposable _logger.LogDebug("Refreshing interfaces."); var interfaces = new List<IPData>(); - var macAddresses = new List<PhysicalAddress>(); try { @@ -236,13 +228,6 @@ public class NetworkManager : INetworkManager, IDisposable try { var ipProperties = adapter.GetIPProperties(); - var mac = adapter.GetPhysicalAddress(); - - // Populate MAC list - if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && !PhysicalAddress.None.Equals(mac)) - { - macAddresses.Add(mac); - } // Populate interface list foreach (var info in ipProperties.UnicastAddresses) @@ -302,7 +287,6 @@ public class NetworkManager : INetworkManager, IDisposable _logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count); _logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString())); - _macAddresses = macAddresses; _interfaces = interfaces; } } @@ -689,10 +673,10 @@ public class NetworkManager : INetworkManager, IDisposable { // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. // If left blank, all remote addresses will be allowed. - if (_remoteAddressFilter.Any() && !_lanSubnets.Any(x => x.Contains(remoteIP))) + if (_remoteAddressFilter.Any() && !IsInLocalNetwork(remoteIP)) { // remoteAddressFilter is a whitelist or blacklist. - var matches = _remoteAddressFilter.Count(remoteNetwork => remoteNetwork.Contains(remoteIP)); + var matches = _remoteAddressFilter.Count(remoteNetwork => NetworkUtils.SubnetContainsAddress(remoteNetwork, remoteIP)); if ((!config.IsRemoteIPFilterBlacklist && matches > 0) || (config.IsRemoteIPFilterBlacklist && matches == 0)) { @@ -702,7 +686,7 @@ public class NetworkManager : INetworkManager, IDisposable return false; } } - else if (!_lanSubnets.Any(x => x.Contains(remoteIP))) + else if (!IsInLocalNetwork(remoteIP)) { // Remote not enabled. So everyone should be LAN. return false; @@ -712,13 +696,6 @@ public class NetworkManager : INetworkManager, IDisposable } /// <inheritdoc/> - public IReadOnlyList<PhysicalAddress> GetMacAddresses() - { - // Populated in construction - so always has values. - return _macAddresses; - } - - /// <inheritdoc/> public IReadOnlyList<IPData> GetLoopbacks() { if (!IsIPv4Enabled && !IsIPv6Enabled) @@ -816,7 +793,7 @@ public class NetworkManager : INetworkManager, IDisposable _logger.LogWarning("IPv4 is disabled in Jellyfin, but enabled in the OS. This may affect how the interface is selected."); } - bool isExternal = !_lanSubnets.Any(network => network.Contains(source)); + bool isExternal = !IsInLocalNetwork(source); _logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExternal); if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result)) @@ -863,7 +840,7 @@ public class NetworkManager : INetworkManager, IDisposable // (For systems with multiple internal network cards, and multiple subnets) foreach (var intf in availableInterfaces) { - if (intf.Subnet.Contains(source)) + if (NetworkUtils.SubnetContainsAddress(intf.Subnet, source)) { result = NetworkUtils.FormatIPString(intf.Address); _logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", source, result); @@ -891,21 +868,11 @@ public class NetworkManager : INetworkManager, IDisposable { if (NetworkUtils.TryParseToSubnet(address, out var subnet)) { - return IPAddress.IsLoopback(subnet.Prefix) || (_lanSubnets.Any(x => x.Contains(subnet.Prefix)) && !_excludedSubnets.Any(x => x.Contains(subnet.Prefix))); - } - - if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled)) - { - foreach (var ept in addresses) - { - if (IPAddress.IsLoopback(ept) || (_lanSubnets.Any(x => x.Contains(ept)) && !_excludedSubnets.Any(x => x.Contains(ept)))) - { - return true; - } - } + return IsInLocalNetwork(subnet.Prefix); } - return false; + return NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled) + && addresses.Any(IsInLocalNetwork); } /// <summary> @@ -940,6 +907,11 @@ public class NetworkManager : INetworkManager, IDisposable return CheckIfLanAndNotExcluded(address); } + /// <summary> + /// Check if the address is in the LAN and not excluded. + /// </summary> + /// <param name="address">The IP address to check. The caller should make sure this is not an IPv4MappedToIPv6 address.</param> + /// <returns>Boolean indicates whether the address is in LAN.</returns> private bool CheckIfLanAndNotExcluded(IPAddress address) { foreach (var lanSubnet in _lanSubnets) @@ -973,13 +945,13 @@ public class NetworkManager : INetworkManager, IDisposable bindPreference = string.Empty; int? port = null; - // Only consider subnets including the source IP, prefering specific overrides + // Only consider subnets including the source IP, preferring specific overrides List<PublishedServerUriOverride> validPublishedServerUrls; if (!isInExternalSubnet) { // Only use matching internal subnets // Prefer more specific (bigger subnet prefix) overrides - validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && x.Data.Subnet.Contains(source)) + validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && NetworkUtils.SubnetContainsAddress(x.Data.Subnet, source)) .OrderByDescending(x => x.Data.Subnet.PrefixLength) .ToList(); } @@ -987,7 +959,7 @@ public class NetworkManager : INetworkManager, IDisposable { // Only use matching external subnets // Prefer more specific (bigger subnet prefix) overrides - validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && x.Data.Subnet.Contains(source)) + validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && NetworkUtils.SubnetContainsAddress(x.Data.Subnet, source)) .OrderByDescending(x => x.Data.Subnet.PrefixLength) .ToList(); } @@ -995,9 +967,11 @@ public class NetworkManager : INetworkManager, IDisposable foreach (var data in validPublishedServerUrls) { // Get interface matching override subnet - var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address)); + var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => NetworkUtils.SubnetContainsAddress(data.Data.Subnet, x.Address)); - if (intf?.Address is not null) + if (intf?.Address is not null + || (data.Data.AddressFamily == AddressFamily.InterNetwork && data.Data.Address.Equals(IPAddress.Any)) + || (data.Data.AddressFamily == AddressFamily.InterNetworkV6 && data.Data.Address.Equals(IPAddress.IPv6Any))) { // If matching interface is found, use override bindPreference = data.OverrideUri; @@ -1025,6 +999,7 @@ public class NetworkManager : INetworkManager, IDisposable } _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference); + return true; } @@ -1055,6 +1030,7 @@ public class NetworkManager : INetworkManager, IDisposable if (isInExternalSubnet) { var externalInterfaces = _interfaces.Where(x => !IsInLocalNetwork(x.Address)) + .Where(x => !IsLinkLocalAddress(x.Address)) .OrderBy(x => x.Index) .ToList(); if (externalInterfaces.Count > 0) @@ -1062,7 +1038,8 @@ public class NetworkManager : INetworkManager, IDisposable // Check to see if any of the external bind interfaces are in the same subnet as the source. // If none exists, this will select the first external interface if there is one. bindAddress = externalInterfaces - .OrderByDescending(x => x.Subnet.Contains(source)) + .OrderByDescending(x => NetworkUtils.SubnetContainsAddress(x.Subnet, source)) + .ThenByDescending(x => x.Subnet.PrefixLength) .ThenBy(x => x.Index) .Select(x => x.Address) .First(); @@ -1079,7 +1056,8 @@ public class NetworkManager : INetworkManager, IDisposable // Check to see if any of the internal bind interfaces are in the same subnet as the source. // If none exists, this will select the first internal interface if there is one. bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address)) - .OrderByDescending(x => x.Subnet.Contains(source)) + .OrderByDescending(x => NetworkUtils.SubnetContainsAddress(x.Subnet, source)) + .ThenByDescending(x => x.Subnet.PrefixLength) .ThenBy(x => x.Index) .Select(x => x.Address) .FirstOrDefault(); @@ -1122,7 +1100,7 @@ public class NetworkManager : INetworkManager, IDisposable // (For systems with multiple network cards and/or multiple subnets) foreach (var intf in extResult) { - if (intf.Subnet.Contains(source)) + if (NetworkUtils.SubnetContainsAddress(intf.Subnet, source)) { result = NetworkUtils.FormatIPString(intf.Address); _logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Result}", source, result); diff --git a/src/Jellyfin.Networking/PortForwardingHost.cs b/src/Jellyfin.Networking/PortForwardingHost.cs deleted file mode 100644 index d01343624..000000000 --- a/src/Jellyfin.Networking/PortForwardingHost.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Mono.Nat; - -namespace Jellyfin.Networking; - -/// <summary> -/// <see cref="IHostedService"/> responsible for UPnP port forwarding. -/// </summary> -public sealed class PortForwardingHost : IHostedService, IDisposable -{ - private readonly IServerApplicationHost _appHost; - private readonly ILogger<PortForwardingHost> _logger; - private readonly IServerConfigurationManager _config; - private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new(); - - private Timer? _timer; - private string? _configIdentifier; - private bool _disposed; - - /// <summary> - /// Initializes a new instance of the <see cref="PortForwardingHost"/> class. - /// </summary> - /// <param name="logger">The logger.</param> - /// <param name="appHost">The application host.</param> - /// <param name="config">The configuration manager.</param> - public PortForwardingHost( - ILogger<PortForwardingHost> logger, - IServerApplicationHost appHost, - IServerConfigurationManager config) - { - _logger = logger; - _appHost = appHost; - _config = config; - } - - private string GetConfigIdentifier() - { - const char Separator = '|'; - var config = _config.GetNetworkConfiguration(); - - return new StringBuilder(32) - .Append(config.EnableUPnP).Append(Separator) - .Append(config.PublicHttpPort).Append(Separator) - .Append(config.PublicHttpsPort).Append(Separator) - .Append(_appHost.HttpPort).Append(Separator) - .Append(_appHost.HttpsPort).Append(Separator) - .Append(_appHost.ListenWithHttps).Append(Separator) - .Append(config.EnableRemoteAccess).Append(Separator) - .ToString(); - } - - private void OnConfigurationUpdated(object? sender, EventArgs e) - { - var oldConfigIdentifier = _configIdentifier; - _configIdentifier = GetConfigIdentifier(); - - if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase)) - { - Stop(); - Start(); - } - } - - /// <inheritdoc /> - public Task StartAsync(CancellationToken cancellationToken) - { - Start(); - - _config.ConfigurationUpdated += OnConfigurationUpdated; - - return Task.CompletedTask; - } - - /// <inheritdoc /> - public Task StopAsync(CancellationToken cancellationToken) - { - Stop(); - - return Task.CompletedTask; - } - - private void Start() - { - var config = _config.GetNetworkConfiguration(); - if (!config.EnableUPnP || !config.EnableRemoteAccess) - { - return; - } - - _logger.LogInformation("Starting NAT discovery"); - - NatUtility.DeviceFound += OnNatUtilityDeviceFound; - NatUtility.StartDiscovery(); - - _timer?.Dispose(); - _timer = new Timer(_ => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); - } - - private void Stop() - { - _logger.LogInformation("Stopping NAT discovery"); - - NatUtility.StopDiscovery(); - NatUtility.DeviceFound -= OnNatUtilityDeviceFound; - - _timer?.Dispose(); - _timer = null; - } - - private async void OnNatUtilityDeviceFound(object? sender, DeviceEventArgs e) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - try - { - // 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(e.Device.DeviceEndpoint, 0)) - { - return; - } - - await Task.WhenAll(CreatePortMaps(e.Device)).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error creating port forwarding rules"); - } - } - - private IEnumerable<Task> CreatePortMaps(INatDevice device) - { - var config = _config.GetNetworkConfiguration(); - yield return CreatePortMap(device, _appHost.HttpPort, config.PublicHttpPort); - - if (_appHost.ListenWithHttps) - { - yield return CreatePortMap(device, _appHost.HttpsPort, config.PublicHttpsPort); - } - } - - private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort) - { - _logger.LogDebug( - "Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}", - 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); - } - } - - /// <inheritdoc /> - public void Dispose() - { - if (_disposed) - { - return; - } - - _config.ConfigurationUpdated -= OnConfigurationUpdated; - - _timer?.Dispose(); - _timer = null; - - _disposed = true; - } -} |
