Pārlūkot izejas kodu

Update based on PR1 changes.

Jim Cartlidge 4 gadi atpakaļ
vecāks
revīzija
b44455ad0d
31 mainītis faili ar 388 papildinājumiem un 263 dzēšanām
  1. 1 1
      Emby.Dlna/Main/DlnaEntryPoint.cs
  2. 51 11
      Emby.Server.Implementations/ApplicationHost.cs
  3. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  4. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
  5. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  6. 1 1
      Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
  7. 1 1
      Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
  8. 1 1
      Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
  9. 1 1
      Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs
  10. 1 1
      Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs
  11. 1 1
      Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs
  12. 1 1
      Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
  13. 1 1
      Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
  14. 1 1
      Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs
  15. 1 1
      Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs
  16. 1 1
      Jellyfin.Api/Controllers/SystemController.cs
  17. 1 1
      Jellyfin.Api/Controllers/UserController.cs
  18. 1 1
      Jellyfin.Api/Helpers/DynamicHlsHelper.cs
  19. 1 1
      Jellyfin.Api/Helpers/MediaInfoHelper.cs
  20. 249 214
      Jellyfin.Networking/Manager/NetworkManager.cs
  21. 2 1
      Jellyfin.Server.Implementations/Users/UserManager.cs
  22. 1 1
      Jellyfin.Server/CoreAppHost.cs
  23. 1 1
      Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
  24. 1 1
      Jellyfin.Server/Middleware/LanFilteringMiddleware.cs
  25. 2 2
      Jellyfin.Server/Program.cs
  26. 2 1
      MediaBrowser.Common/MediaBrowser.Common.csproj
  27. 37 5
      MediaBrowser.Common/Net/INetworkManager.cs
  28. 21 5
      MediaBrowser.Controller/IServerApplicationHost.cs
  29. 1 1
      RSSDP/SsdpCommunicationsServer.cs
  30. 1 1
      RSSDP/SsdpDevicePublisher.cs
  31. 1 1
      tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs

+ 1 - 1
Emby.Dlna/Main/DlnaEntryPoint.cs

@@ -9,7 +9,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Emby.Dlna.PlayTo;
 using Emby.Dlna.Ssdp;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;

+ 51 - 11
Emby.Server.Implementations/ApplicationHost.cs

@@ -160,6 +160,11 @@ namespace Emby.Server.Implementations
             }
         }
 
+        /// <summary>
+        /// Gets the <see cref="INetworkManager"/> singleton instance.
+        /// </summary>
+        public INetworkManager NetManager { get; internal set; }
+
         /// <summary>
         /// Occurs when [has pending restart changed].
         /// </summary>
@@ -189,11 +194,6 @@ namespace Emby.Server.Implementations
         /// <value>The plugins.</value>
         public IReadOnlyList<IPlugin> Plugins => _plugins;
 
-        /// <summary>
-        /// Gets the NetworkManager object.
-        /// </summary>
-        private readonly INetworkManager _networkManager;
-
         /// <summary>
         /// Gets the logger factory.
         /// </summary>
@@ -267,7 +267,7 @@ namespace Emby.Server.Implementations
 
             ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
 
-            _networkManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
+            NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
 
             Logger = LoggerFactory.CreateLogger<ApplicationHost>();
 
@@ -524,7 +524,7 @@ namespace Emby.Server.Implementations
             ServiceCollection.AddSingleton(_fileSystemManager);
             ServiceCollection.AddSingleton<TvdbClientManager>();
 
-            ServiceCollection.AddSingleton(_networkManager);
+            ServiceCollection.AddSingleton(NetManager);
 
             ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
 
@@ -1116,7 +1116,7 @@ namespace Emby.Server.Implementations
         }
 
         public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
-            => _networkManager.GetMacAddresses()
+            => NetManager.GetMacAddresses()
                 .Select(i => new WakeOnLanInfo(i))
                 .ToList();
 
@@ -1138,7 +1138,47 @@ namespace Emby.Server.Implementations
         public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps;
 
         /// <inheritdoc/>
-        public string GetSmartApiUrl(object source)
+        public string GetSmartApiUrl(IPAddress ipAddress, int? port = null)
+        {
+            // Published server ends with a /
+            if (_startupOptions.PublishedServerUrl != null)
+            {
+                // Published server ends with a '/', so we need to remove it.
+                return _startupOptions.PublishedServerUrl.ToString().Trim('/');
+            }
+
+            string smart = NetManager.GetBindInterface(ipAddress, out port);
+            // If the smartAPI doesn't start with http then treat it as a host or ip.
+            if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+            {
+                return smart.Trim('/');
+            }
+
+            return GetLocalApiUrl(smart.Trim('/'), null, port);
+        }
+
+        /// <inheritdoc/>
+        public string GetSmartApiUrl(HttpRequest request, int? port = null)
+        {
+            // Published server ends with a /
+            if (_startupOptions.PublishedServerUrl != null)
+            {
+                // Published server ends with a '/', so we need to remove it.
+                return _startupOptions.PublishedServerUrl.ToString().Trim('/');
+            }
+
+            string smart = NetManager.GetBindInterface(request, out port);
+            // If the smartAPI doesn't start with http then treat it as a host or ip.
+            if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+            {
+                return smart.Trim('/');
+            }
+
+            return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
+        }
+
+        /// <inheritdoc/>
+        public string GetSmartApiUrl(string hostname, int? port = null)
         {
             // Published server ends with a /
             if (_startupOptions.PublishedServerUrl != null)
@@ -1147,7 +1187,7 @@ namespace Emby.Server.Implementations
                 return _startupOptions.PublishedServerUrl.ToString().Trim('/');
             }
 
-            string smart = _networkManager.GetBindInterface(source, out int? port);
+            string smart = NetManager.GetBindInterface(hostname, out port);
 
             // If the smartAPI doesn't start with http then treat it as a host or ip.
             if (smart.StartsWith("http", StringComparison.OrdinalIgnoreCase))
@@ -1155,7 +1195,7 @@ namespace Emby.Server.Implementations
                 return smart.Trim('/');
             }
 
-            return GetLocalApiUrl(smart.Trim('/'), source is HttpRequest request ? request.Scheme : null, port);
+            return GetLocalApiUrl(smart.Trim('/'), null, port);
         }
 
         /// <inheritdoc/>

+ 1 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -10,7 +10,7 @@ using System.Net.Http;
 using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;

+ 1 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs

@@ -7,7 +7,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;

+ 1 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -8,7 +8,7 @@ using System.Linq;
 using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;

+ 1 - 1
Jellyfin.Api/Auth/BaseAuthorizationHandler.cs

@@ -1,7 +1,7 @@
 using System.Security.Claims;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Data.Enums;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;

+ 1 - 1
Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs

@@ -1,5 +1,5 @@
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 1
Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs

@@ -1,5 +1,5 @@
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 1
Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs

@@ -1,5 +1,5 @@
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;

+ 1 - 1
Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs

@@ -1,5 +1,5 @@
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;

+ 1 - 1
Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs

@@ -1,6 +1,6 @@
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;

+ 1 - 1
Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs

@@ -1,5 +1,5 @@
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 1
Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs

@@ -1,6 +1,6 @@
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 1
Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs

@@ -1,5 +1,5 @@
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 1
Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs

@@ -1,6 +1,6 @@
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;

+ 1 - 1
Jellyfin.Api/Controllers/SystemController.cs

@@ -8,7 +8,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;

+ 1 - 1
Jellyfin.Api/Controllers/UserController.cs

@@ -7,7 +7,7 @@ using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Models.UserDtos;
 using Jellyfin.Data.Enums;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Authentication;

+ 1 - 1
Jellyfin.Api/Helpers/DynamicHlsHelper.cs

@@ -8,7 +8,7 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Models.StreamingDtos;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 1 - 1
Jellyfin.Api/Helpers/MediaInfoHelper.cs

@@ -6,7 +6,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 249 - 214
Jellyfin.Networking/Manager/NetworkManager.cs

@@ -7,6 +7,7 @@ using System.Net.NetworkInformation;
 using System.Net.Sockets;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Configuration;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
@@ -19,8 +20,6 @@ namespace Jellyfin.Networking.Manager
     /// </summary>
     public class NetworkManager : INetworkManager, IDisposable
     {
-        private static NetworkManager? _instance;
-
         /// <summary>
         /// Contains the description of the interface along with its index.
         /// </summary>
@@ -48,7 +47,7 @@ namespace Jellyfin.Networking.Manager
         /// <summary>
         /// Holds the bind address overrides.
         /// </summary>
-        private readonly Dictionary<IPNetAddress, string> _overrideUrls;
+        private readonly Dictionary<IPNetAddress, string> _publishedServerUrls;
 
         /// <summary>
         /// Used to stop "event-racing conditions".
@@ -105,7 +104,7 @@ namespace Jellyfin.Networking.Manager
             _interfaceAddresses = new NetCollection(unique: false);
             _macAddresses = new List<PhysicalAddress>();
             _interfaceNames = new SortedList<string, int>();
-            _overrideUrls = new Dictionary<IPNetAddress, string>();
+            _publishedServerUrls = new Dictionary<IPNetAddress, string>();
 
             UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration);
             if (!IsIP6Enabled && !IsIP4Enabled)
@@ -117,8 +116,6 @@ namespace Jellyfin.Networking.Manager
             NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
 
             _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
-
-            Instance = this;
         }
 #pragma warning restore CS8618 // Non-nullable field is uninitialized.
 
@@ -127,19 +124,6 @@ namespace Jellyfin.Networking.Manager
         /// </summary>
         public event EventHandler? NetworkChanged;
 
-        /// <summary>
-        /// Gets the singleton of this object.
-        /// </summary>
-        public static NetworkManager Instance
-        {
-            get => GetInstance();
-
-            internal set
-            {
-                _instance = value;
-            }
-        }
-
         /// <summary>
         /// Gets the unique network location signature, which is updated on every network change.
         /// </summary>
@@ -176,7 +160,7 @@ namespace Jellyfin.Networking.Manager
         /// <summary>
         /// Gets the Published server override list.
         /// </summary>
-        public Dictionary<IPNetAddress, string> PublishedServerOverrides => _overrideUrls;
+        public Dictionary<IPNetAddress, string> PublishedServerUrls => _publishedServerUrls;
 
         /// <inheritdoc/>
         public void Dispose()
@@ -198,9 +182,12 @@ namespace Jellyfin.Networking.Manager
         /// <inheritdoc/>
         public bool IsGatewayInterface(object? addressObj)
         {
-            var address = (addressObj is IPAddress addressIP) ?
-                addressIP : (addressObj is IPObject addressIPObj) ?
-                    addressIPObj.Address : IPAddress.None;
+            var address = addressObj switch
+            {
+                IPAddress addressIp => addressIp,
+                IPObject addressIpObj => addressIpObj.Address,
+                _ => IPAddress.None
+            };
 
             lock (_intLock)
             {
@@ -320,243 +307,127 @@ namespace Jellyfin.Networking.Manager
         }
 
         /// <inheritdoc/>
-        public string GetBindInterface(object? source, out int? port)
+        public string GetBindInterface(string source, out int? port)
         {
-            bool chromeCast = false;
-            port = null;
-            // Parse the source object in an attempt to discover where the request originated.
-            IPObject sourceAddr;
-            if (source is HttpRequest sourceReq)
+            if (!string.IsNullOrEmpty(source))
             {
-                port = sourceReq.Host.Port;
-                if (IPHost.TryParse(sourceReq.Host.Host, out IPHost host))
+                if (string.Equals(source, "chromecast", StringComparison.OrdinalIgnoreCase))
                 {
-                    sourceAddr = host;
-                }
-                else
-                {
-                    // Assume it's external, as we cannot resolve the host.
-                    sourceAddr = IPHost.None;
-                }
-            }
-            else if (source is string sourceStr && !string.IsNullOrEmpty(sourceStr))
-            {
-                if (string.Equals(sourceStr, "chromecast", StringComparison.OrdinalIgnoreCase))
-                {
-                    chromeCast = true;
                     // Just assign a variable so has source = true;
-                    sourceAddr = IPNetAddress.IP4Loopback;
+                    return GetBindInterface(IPNetAddress.IP4Loopback, out port);
                 }
 
-                if (IPHost.TryParse(sourceStr, out IPHost host))
+                if (IPHost.TryParse(source, out IPHost host))
                 {
-                    sourceAddr = host;
-                }
-                else
-                {
-                    // Assume it's external, as we cannot resolve the host.
-                    sourceAddr = IPHost.None;
+                    return GetBindInterface(host, out port);
                 }
             }
-            else if (source is IPAddress sourceIP)
+
+            return GetBindInterface(IPHost.None, out port);
+        }
+
+        /// <inheritdoc/>
+        public string GetBindInterface(IPAddress source, out int? port)
+        {
+            return GetBindInterface(new IPNetAddress(source), out port);
+        }
+
+        /// <inheritdoc/>
+        public string GetBindInterface(HttpRequest source, out int? port)
+        {
+            string result;
+
+            if (source != null && IPHost.TryParse(source.Host.Host, out IPHost host))
             {
-                sourceAddr = new IPNetAddress(sourceIP);
+                result = GetBindInterface(host, out port);
+                port ??= source.Host.Port;
             }
             else
             {
-                // If we have no idea, then assume it came from an external address.
-                sourceAddr = IPHost.None;
+                result = GetBindInterface(IPNetAddress.None, out port);
+                port ??= source?.Host.Port;
             }
 
+            return result;
+        }
+
+        /// <inheritdoc/>
+        public string GetBindInterface(IPObject source, out int? port)
+        {
+            port = null;
+            bool isChromeCast = source == IPNetAddress.IP4Loopback;
             // Do we have a source?
-            bool haveSource = !sourceAddr.Address.Equals(IPAddress.None);
+            bool haveSource = !source.Address.Equals(IPAddress.None);
+            bool isExternal = false;
 
             if (haveSource)
             {
-                if (!IsIP6Enabled && sourceAddr.AddressFamily == AddressFamily.InterNetworkV6)
+                if (!IsIP6Enabled && source.AddressFamily == AddressFamily.InterNetworkV6)
                 {
                     _logger.LogWarning("IPv6 is disabled in JellyFin, but enabled in the OS. This may affect how the interface is selected.");
                 }
 
-                if (!IsIP4Enabled && sourceAddr.AddressFamily == AddressFamily.InterNetwork)
+                if (!IsIP4Enabled && source.AddressFamily == AddressFamily.InterNetwork)
                 {
                     _logger.LogWarning("IPv4 is disabled in JellyFin, but enabled in the OS. This may affect how the interface is selected.");
                 }
-            }
 
-            bool isExternal = haveSource && !IsInLocalNetwork(sourceAddr);
+                isExternal = !IsInLocalNetwork(source);
 
-            string bindPreference = string.Empty;
-            if (haveSource)
-            {
-                // Check for user override.
-                foreach (var addr in _overrideUrls)
+                if (MatchesPublishedServerUrl(source, isExternal, isChromeCast, out string result, out port))
                 {
-                    // Remaining. Match anything.
-                    if (addr.Key.Equals(IPAddress.Broadcast))
-                    {
-                        bindPreference = addr.Value;
-                        break;
-                    }
-                    else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && (isExternal || chromeCast))
-                    {
-                        // External.
-                        bindPreference = addr.Value;
-                        break;
-                    }
-                    else if (addr.Key.Contains(sourceAddr))
-                    {
-                        // Match ip address.
-                        bindPreference = addr.Value;
-                        break;
-                    }
+                    _logger.LogInformation("{0}: Using BindAddress {1}:{2}", source, result, port);
+                    return result;
                 }
             }
 
             _logger.LogDebug("GetBindInterface: Souce: {0}, External: {1}:", haveSource, isExternal);
 
-            if (!string.IsNullOrEmpty(bindPreference))
-            {
-                // Has it got a port defined?
-                var parts = bindPreference.Split(':');
-                if (parts.Length > 1)
-                {
-                    if (int.TryParse(parts[1], out int p))
-                    {
-                        bindPreference = parts[0];
-                        port = p;
-                    }
-                }
-
-                _logger.LogInformation("{0}: Using BindAddress {1}:{2}", sourceAddr, bindPreference, port);
-                return bindPreference;
-            }
-
-            string ipresult;
-
             // No preference given, so move on to bind addresses.
             lock (_intLock)
             {
-                var nc = _bindAddresses.Exclude(_bindExclusions).Where(p => !p.IsLoopback());
-
-                int count = nc.Count();
-                if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses.Equals(IPAddress.IPv6Any)))
-                {
-                    // Ignore IPAny addresses.
-                    count = 0;
-                }
-
-                if (count != 0)
+                if (MatchesBindInterface(source, isExternal, out string result))
                 {
-                    // Check to see if any of the bind interfaces are in the same subnet.
-
-                    IEnumerable<IPObject> bindResult;
-                    IPAddress? defaultGateway = null;
-
-                    if (isExternal)
-                    {
-                        // Find all external bind addresses. Store the default gateway, but check to see if there is a better match first.
-                        bindResult = nc.Where(p => !IsInLocalNetwork(p)).OrderBy(p => p.Tag);
-                        defaultGateway = bindResult.FirstOrDefault()?.Address;
-                        bindResult = bindResult.Where(p => p.Contains(sourceAddr)).OrderBy(p => p.Tag);
-                    }
-                    else
-                    {
-                        // Look for the best internal address.
-                        bindResult = nc.Where(p => IsInLocalNetwork(p) && p.Contains(sourceAddr)).OrderBy(p => p.Tag);
-                    }
-
-                    if (bindResult.Any())
-                    {
-                        ipresult = FormatIP6String(bindResult.First().Address);
-                        _logger.LogDebug("{0}: GetBindInterface: Has source, found a match bind interface subnets. {1}", sourceAddr, ipresult);
-                        return ipresult;
-                    }
-
-                    if (isExternal && defaultGateway != null)
-                    {
-                        ipresult = FormatIP6String(defaultGateway);
-                        _logger.LogDebug("{0}: GetBindInterface: Using first user defined external interface. {1}", sourceAddr, ipresult);
-                        return ipresult;
-                    }
-
-                    ipresult = FormatIP6String(nc.First().Address);
-                    _logger.LogDebug("{0}: GetBindInterface: Selected first user defined interface. {1}", sourceAddr, ipresult);
-
-                    if (isExternal)
-                    {
-                        // TODO: remove this after testing.
-                        _logger.LogWarning("{0}: External request received, however, only an internal interface bind found.", sourceAddr);
-                    }
-
-                    return ipresult;
+                    return result;
                 }
 
-                if (isExternal)
+                if (isExternal && MatchesExternalInterface(source, out result))
                 {
-                    // Get the first WAN interface address that isn't a loopback.
-                    var extResult = _interfaceAddresses
-                        .Exclude(_bindExclusions)
-                        .Where(p => !IsInLocalNetwork(p))
-                        .OrderBy(p => p.Tag);
-
-                    if (extResult.Any())
-                    {
-                        // Does the request originate in one of the interface subnets?
-                        // (For systems with multiple internal network cards, and multiple subnets)
-                        foreach (var intf in extResult)
-                        {
-                            if (!IsInLocalNetwork(intf) && intf.Contains(sourceAddr))
-                            {
-                                ipresult = FormatIP6String(intf.Address);
-                                _logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", sourceAddr, ipresult);
-                                return ipresult;
-                            }
-                        }
-
-                        ipresult = FormatIP6String(extResult.First().Address);
-                        _logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", sourceAddr, ipresult);
-                        return ipresult;
-                    }
-
-                    // Have to return something, so return an internal address
-
-                    // TODO: remove this after testing.
-                    _logger.LogWarning("{0}: External request received, however, no WAN interface found.", sourceAddr);
+                    return result;
                 }
 
                 // Get the first LAN interface address that isn't a loopback.
-                var result = _interfaceAddresses
+                var interfaces = new NetCollection(_interfaceAddresses
                     .Exclude(_bindExclusions)
                     .Where(p => IsInLocalNetwork(p))
-                    .OrderBy(p => p.Tag);
+                    .OrderBy(p => p.Tag));
 
-                if (result.Any())
+                if (interfaces.Count > 0)
                 {
                     if (haveSource)
                     {
                         // Does the request originate in one of the interface subnets?
                         // (For systems with multiple internal network cards, and multiple subnets)
-                        foreach (var intf in result)
+                        foreach (var intf in interfaces)
                         {
-                            if (intf.Contains(sourceAddr))
+                            if (intf.Contains(source))
                             {
-                                ipresult = FormatIP6String(intf.Address);
-                                _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", sourceAddr, ipresult);
-                                return ipresult;
+                                result = FormatIP6String(intf.Address);
+                                _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", source, result);
+                                return result;
                             }
                         }
                     }
 
-                    ipresult = FormatIP6String(result.First().Address);
-                    _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", sourceAddr, ipresult);
-                    return ipresult;
+                    result = FormatIP6String(interfaces.First().Address);
+                    _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", source, result);
+                    return result;
                 }
 
                 // There isn't any others, so we'll use the loopback.
-                ipresult = IsIP6Enabled ? "::" : "127.0.0.1";
-                _logger.LogWarning("{0}: GetBindInterface: Loopback return.", sourceAddr, ipresult);
-                return ipresult;
+                result = IsIP6Enabled ? "::" : "127.0.0.1";
+                _logger.LogWarning("{0}: GetBindInterface: Loopback return.", source, result);
+                return result;
             }
         }
 
@@ -771,16 +642,6 @@ namespace Jellyfin.Networking.Manager
             }
         }
 
-        private static NetworkManager GetInstance()
-        {
-            if (_instance == null)
-            {
-                throw new ApplicationException("NetworkManager is not initialised.");
-            }
-
-            return _instance;
-        }
-
         private void ConfigurationUpdated(object? sender, EventArgs args)
         {
             UpdateSettings((ServerConfiguration)_configurationManager.CommonConfiguration);
@@ -944,7 +805,7 @@ namespace Jellyfin.Networking.Manager
             {
                 lock (_intLock)
                 {
-                    _overrideUrls.Clear();
+                    _publishedServerUrls.Clear();
                 }
 
                 return;
@@ -952,7 +813,7 @@ namespace Jellyfin.Networking.Manager
 
             lock (_intLock)
             {
-                _overrideUrls.Clear();
+                _publishedServerUrls.Clear();
 
                 foreach (var entry in overrides)
                 {
@@ -966,15 +827,15 @@ namespace Jellyfin.Networking.Manager
                         var replacement = parts[1].Trim();
                         if (string.Equals(parts[0], "remaining", StringComparison.OrdinalIgnoreCase))
                         {
-                            _overrideUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement;
+                            _publishedServerUrls[new IPNetAddress(IPAddress.Broadcast)] = replacement;
                         }
                         else if (string.Equals(parts[0], "external", StringComparison.OrdinalIgnoreCase))
                         {
-                            _overrideUrls[new IPNetAddress(IPAddress.Any)] = replacement;
+                            _publishedServerUrls[new IPNetAddress(IPAddress.Any)] = replacement;
                         }
                         else if (TryParseInterface(parts[0], out IPNetAddress address))
                         {
-                            _overrideUrls[address] = replacement;
+                            _publishedServerUrls[address] = replacement;
                         }
                         else
                         {
@@ -1199,5 +1060,179 @@ namespace Jellyfin.Networking.Manager
                 }
             }
         }
+
+        /// <summary>
+        /// Attempts to match the source against a user defined bind interface.
+        /// </summary>
+        /// <param name="source">IP source address to use.</param>
+        /// <param name="isExternal">True if the source is in the external subnet.</param>
+        /// <param name="isChromeCast">True if the request is for a chromecast device.</param>
+        /// <param name="bindPreference">The published server url that matches the source address.</param>
+        /// <param name="port">The resultant port, if one exists.</param>
+        /// <returns>True if a match is found.</returns>
+        private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, bool isChromeCast, out string bindPreference, out int? port)
+        {
+            bindPreference = string.Empty;
+            port = null;
+
+            // Check for user override.
+            foreach (var addr in _publishedServerUrls)
+            {
+                // Remaining. Match anything.
+                if (addr.Key.Equals(IPAddress.Broadcast))
+                {
+                    bindPreference = addr.Value;
+                    break;
+                }
+                else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && (isExternal || isChromeCast))
+                {
+                    // External.
+                    bindPreference = addr.Value;
+                    break;
+                }
+                else if (addr.Key.Contains(source))
+                {
+                    // Match ip address.
+                    bindPreference = addr.Value;
+                    break;
+                }
+            }
+
+            if (!string.IsNullOrEmpty(bindPreference))
+            {
+                // Has it got a port defined?
+                var parts = bindPreference.Split(':');
+                if (parts.Length > 1)
+                {
+                    if (int.TryParse(parts[1], out int p))
+                    {
+                        bindPreference = parts[0];
+                        port = p;
+                    }
+                }
+
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Attempts to match the source against a user defined bind interface.
+        /// </summary>
+        /// <param name="source">IP source address to use.</param>
+        /// <param name="isExternal">True if the source is in the external subnet.</param>
+        /// <param name="result">The result, if a match is found.</param>
+        /// <returns>True if a match is found.</returns>
+        private bool MatchesBindInterface(IPObject source, bool isExternal, out string result)
+        {
+            result = string.Empty;
+            var nc = new NetCollection(_bindAddresses.Exclude(_bindExclusions).Where(p => !p.IsLoopback()));
+
+            int count = nc.Count;
+            if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any)))
+            {
+                // Ignore IPAny addresses.
+                count = 0;
+            }
+
+            if (count != 0)
+            {
+                // Check to see if any of the bind interfaces are in the same subnet.
+
+                NetCollection bindResult;
+                IPAddress? defaultGateway = null;
+                IPAddress? bindAddress;
+
+                if (isExternal)
+                {
+                    // Find all external bind addresses. Store the default gateway, but check to see if there is a better match first.
+                    bindResult = new NetCollection(nc
+                        .Where(p => !IsInLocalNetwork(p))
+                        .OrderBy(p => p.Tag));
+                    defaultGateway = bindResult.FirstOrDefault()?.Address;
+                    bindAddress = bindResult
+                        .Where(p => p.Contains(source))
+                        .OrderBy(p => p.Tag)
+                        .FirstOrDefault()?.Address;
+                }
+                else
+                {
+                    // Look for the best internal address.
+                    bindAddress = nc
+                        .Where(p => IsInLocalNetwork(p) && (p.Contains(source) || p.Equals(IPAddress.None)))
+                        .OrderBy(p => p.Tag)
+                        .FirstOrDefault()?.Address;
+                }
+
+                if (bindAddress != null)
+                {
+                    result = FormatIP6String(bindAddress);
+                    _logger.LogDebug("{0}: GetBindInterface: Has source, found a match bind interface subnets. {1}", source, result);
+                    return true;
+                }
+
+                if (isExternal && defaultGateway != null)
+                {
+                    result = FormatIP6String(defaultGateway);
+                    _logger.LogDebug("{0}: GetBindInterface: Using first user defined external interface. {1}", source, result);
+                    return true;
+                }
+
+                result = FormatIP6String(nc.First().Address);
+                _logger.LogDebug("{0}: GetBindInterface: Selected first user defined interface. {1}", source, result);
+
+                if (isExternal)
+                {
+                    // TODO: remove this after testing.
+                    _logger.LogWarning("{0}: External request received, however, only an internal interface bind found.", source);
+                }
+
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Attempts to match the source against am external interface.
+        /// </summary>
+        /// <param name="source">IP source address to use.</param>
+        /// <param name="result">The result, if a match is found.</param>
+        /// <returns>True if a match is found.</returns>
+        private bool MatchesExternalInterface(IPObject source, out string result)
+        {
+            result = string.Empty;
+            // Get the first WAN interface address that isn't a loopback.
+            var extResult = new NetCollection(_interfaceAddresses
+                .Exclude(_bindExclusions)
+                .Where(p => !IsInLocalNetwork(p))
+                .OrderBy(p => p.Tag));
+
+            if (extResult.Count > 0)
+            {
+                // Does the request originate in one of the interface subnets?
+                // (For systems with multiple internal network cards, and multiple subnets)
+                foreach (var intf in extResult)
+                {
+                    if (!IsInLocalNetwork(intf) && intf.Contains(source))
+                    {
+                        result = FormatIP6String(intf.Address);
+                        _logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", source, result);
+                        return true;
+                    }
+                }
+
+                result = FormatIP6String(extResult.First().Address);
+                _logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", source, result);
+                return true;
+            }
+
+            // Have to return something, so return an internal address
+
+            // TODO: remove this after testing.
+            _logger.LogWarning("{0}: External request received, however, no WAN interface found.", source);
+            return false;
+        }
     }
 }

+ 2 - 1
Jellyfin.Server.Implementations/Users/UserManager.cs

@@ -12,10 +12,11 @@ using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Events;
 using Jellyfin.Data.Events.Users;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common;
 using MediaBrowser.Common.Cryptography;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Events;

+ 1 - 1
Jellyfin.Server/CoreAppHost.cs

@@ -5,7 +5,7 @@ using System.Reflection;
 using Emby.Drawing;
 using Emby.Server.Implementations;
 using Jellyfin.Drawing.Skia;
-using Jellyfin.Networking.Manager;
+
 using Jellyfin.Server.Implementations;
 using Jellyfin.Server.Implementations.Activity;
 using Jellyfin.Server.Implementations.Events;

+ 1 - 1
Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs

@@ -1,6 +1,6 @@
 using System.Linq;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 1 - 1
Jellyfin.Server/Middleware/LanFilteringMiddleware.cs

@@ -2,7 +2,7 @@ using System;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 2 - 2
Jellyfin.Server/Program.cs

@@ -13,7 +13,7 @@ using CommandLine;
 using Emby.Server.Implementations;
 using Emby.Server.Implementations.IO;
 using Jellyfin.Api.Controllers;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Extensions;
 using Microsoft.AspNetCore.Hosting;
@@ -272,7 +272,7 @@ namespace Jellyfin.Server
             return builder
                 .UseKestrel((builderContext, options) =>
                 {
-                    NetCollection addresses = NetworkManager.Instance.GetAllBindInterfaces();
+                    NetCollection addresses = appHost.NetManager.GetAllBindInterfaces();
 
                     bool flagged = false;
                     foreach (IPObject netAdd in addresses)

+ 2 - 1
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -20,8 +20,9 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.7" />
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
+    <PackageReference Include="NetworkCollection" Version="1.0.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 37 - 5
Jellyfin.Networking/Manager/INetworkManager.cs → MediaBrowser.Common/Net/INetworkManager.cs

@@ -1,11 +1,13 @@
+#nullable enable
 using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Net.NetworkInformation;
 using MediaBrowser.Model.Configuration;
+using Microsoft.AspNetCore.Http;
 using NetworkCollection;
 
-namespace Jellyfin.Networking.Manager
+namespace MediaBrowser.Common.Net
 {
     /// <summary>
     /// Interface for the NetworkManager class.
@@ -18,9 +20,9 @@ namespace Jellyfin.Networking.Manager
         event EventHandler NetworkChanged;
 
         /// <summary>
-        /// Gets the Published server override list.
+        /// Gets the published server urls list.
         /// </summary>
-        Dictionary<IPNetAddress, string> PublishedServerOverrides { get; }
+        Dictionary<IPNetAddress, string> PublishedServerUrls { get; }
 
         /// <summary>
         /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
@@ -28,7 +30,7 @@ namespace Jellyfin.Networking.Manager
         public bool TrustAllIP6Interfaces { get; }
 
         /// <summary>
-        /// Gets returns the remote address filter.
+        /// Gets the remote address filter.
         /// </summary>
         NetCollection RemoteAddressFilter { get; }
 
@@ -75,7 +77,37 @@ namespace Jellyfin.Networking.Manager
         /// <param name="source">Source of the request.</param>
         /// <param name="port">Optional port returned, if it's part of an override.</param>
         /// <returns>IP Address to use, or loopback address if all else fails.</returns>
-        string GetBindInterface(object? source, out int? port);
+        string GetBindInterface(IPObject source, out int? port);
+
+        /// <summary>
+        /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
+        /// If no bind addresses are specified, an internal interface address is selected.
+        /// (See above).
+        /// </summary>
+        /// <param name="source">Source of the request.</param>
+        /// <param name="port">Optional port returned, if it's part of an override.</param>
+        /// <returns>IP Address to use, or loopback address if all else fails.</returns>
+        string GetBindInterface(HttpRequest source, out int? port);
+
+        /// <summary>
+        /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
+        /// If no bind addresses are specified, an internal interface address is selected.
+        /// (See above).
+        /// </summary>
+        /// <param name="source">IP address of the request.</param>
+        /// <param name="port">Optional port returned, if it's part of an override.</param>
+        /// <returns>IP Address to use, or loopback address if all else fails.</returns>
+        string GetBindInterface(IPAddress source, out int? port);
+
+        /// <summary>
+        /// Retrieves the bind address to use in system url's. (Server Discovery, PlayTo, LiveTV, SystemInfo)
+        /// If no bind addresses are specified, an internal interface address is selected.
+        /// (See above).
+        /// </summary>
+        /// <param name="source">Source of the request.</param>
+        /// <param name="port">Optional port returned, if it's part of an override.</param>
+        /// <returns>IP Address to use, or loopback address if all else fails.</returns>
+        string GetBindInterface(string source, out int? port);
 
         /// <summary>
         /// Checks to see if the ip address is specifically excluded in LocalNetworkAddresses.

+ 21 - 5
MediaBrowser.Controller/IServerApplicationHost.cs

@@ -63,12 +63,28 @@ namespace MediaBrowser.Controller
         PublicSystemInfo GetPublicSystemInfo(IPAddress address);
 
         /// <summary>
-        /// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured
-        /// HTTPS will be preferred when available.
+        /// Gets a URL specific for the request.
         /// </summary>
-        /// <param name="source">The source of the request.</param>
-        /// <returns>The server URL.</returns>
-        string GetSmartApiUrl(object source);
+        /// <param name="request">The <see cref="HttpRequest"/> instance.</param>
+        /// <param name="port">Optional port number.</param>
+        /// <returns>An accessible URL.</returns>
+        string GetSmartApiUrl(HttpRequest request, int? port = null);
+
+        /// <summary>
+        /// Gets a URL specific for the request.
+        /// </summary>
+        /// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param>
+        /// <param name="port">Optional port number.</param>
+        /// <returns>An accessible URL.</returns>
+        string GetSmartApiUrl(IPAddress remoteAddr, int? port = null);
+
+        /// <summary>
+        /// Gets a URL specific for the request.
+        /// </summary>
+        /// <param name="hostname">The hostname used in the connection.</param>
+        /// <param name="port">Optional port number.</param>
+        /// <returns>An accessible URL.</returns>
+        string GetSmartApiUrl(string hostname, int? port = null);
 
         /// <summary>
         /// Gets a localhost URL that can be used to access the API using the loop-back IP address.

+ 1 - 1
RSSDP/SsdpCommunicationsServer.cs

@@ -7,7 +7,7 @@ using System.Net.Sockets;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Net;
 using Microsoft.Extensions.Logging;

+ 1 - 1
RSSDP/SsdpDevicePublisher.cs

@@ -5,7 +5,7 @@ using System.Linq;
 using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Net;
 using NetworkCollection;
 

+ 1 - 1
tests/Jellyfin.Api.Tests/Auth/LocalAccessPolicy/LocalAccessHandlerTests.cs

@@ -4,7 +4,7 @@ using AutoFixture;
 using AutoFixture.AutoMoq;
 using Jellyfin.Api.Auth.LocalAccessPolicy;
 using Jellyfin.Api.Constants;
-using Jellyfin.Networking.Manager;
+
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;