Selaa lähdekoodia

Refactor URI overrides (#10051)

Tim Eisele 1 vuosi sitten
vanhempi
sitoutus
dc27d8f9cd

+ 5 - 4
Emby.Dlna/Main/DlnaEntryPoint.cs

@@ -201,9 +201,10 @@ namespace Emby.Dlna.Main
             {
                 if (_communicationsServer is null)
                 {
-                    var enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
-
-                    _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
+                    _communicationsServer = new SsdpCommunicationsServer(
+                        _socketFactory,
+                        _networkManager,
+                        _logger)
                     {
                         IsShared = true
                     };
@@ -213,7 +214,7 @@ namespace Emby.Dlna.Main
             }
             catch (Exception ex)
             {
-                _logger.LogError(ex, "Error starting ssdp handlers");
+                _logger.LogError(ex, "Error starting SSDP handlers");
             }
         }
 

+ 3 - 3
Emby.Server.Implementations/ApplicationHost.cs

@@ -450,7 +450,7 @@ namespace Emby.Server.Implementations
 
             ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
 
-            NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
+            NetManager = new NetworkManager(ConfigurationManager, _startupConfig, LoggerFactory.CreateLogger<NetworkManager>());
 
             // Initialize runtime stat collection
             if (ConfigurationManager.Configuration.EnableMetrics)
@@ -913,7 +913,7 @@ namespace Emby.Server.Implementations
         /// <inheritdoc/>
         public string GetSmartApiUrl(HttpRequest request)
         {
-            // Return the host in the HTTP request as the API url
+            // Return the host in the HTTP request as the API URL if not configured otherwise
             if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
             {
                 int? requestPort = request.Host.Port;
@@ -948,7 +948,7 @@ namespace Emby.Server.Implementations
         public string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true)
         {
             // With an empty source, the port will be null
-            var smart = NetManager.GetBindAddress(ipAddress, out _, true);
+            var smart = NetManager.GetBindAddress(ipAddress, out _, false);
             var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
             int? port = !allowHttps ? HttpPort : null;
             return GetLocalApiUrl(smart, scheme, port);

+ 19 - 10
Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs

@@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.EntryPoints
 {
     /// <summary>
-    /// Class UdpServerEntryPoint.
+    /// Class responsible for registering all UDP broadcast endpoints and their handlers.
     /// </summary>
     public sealed class UdpServerEntryPoint : IServerEntryPoint
     {
@@ -35,7 +35,6 @@ namespace Emby.Server.Implementations.EntryPoints
         private readonly IConfiguration _config;
         private readonly IConfigurationManager _configurationManager;
         private readonly INetworkManager _networkManager;
-        private readonly bool _enableMultiSocketBinding;
 
         /// <summary>
         /// The UDP server.
@@ -65,7 +64,6 @@ namespace Emby.Server.Implementations.EntryPoints
             _configurationManager = configurationManager;
             _networkManager = networkManager;
             _udpServers = new List<UdpServer>();
-            _enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
         }
 
         /// <inheritdoc />
@@ -80,14 +78,16 @@ namespace Emby.Server.Implementations.EntryPoints
 
             try
             {
-                if (_enableMultiSocketBinding)
+                // Linux needs to bind to the broadcast addresses to get broadcast traffic
+                // Windows receives broadcast fine when binding to just the interface, it is unable to bind to broadcast addresses
+                if (OperatingSystem.IsLinux())
                 {
-                    // Add global broadcast socket
+                    // Add global broadcast listener
                     var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
                     server.Start(_cancellationTokenSource.Token);
                     _udpServers.Add(server);
 
-                    // Add bind address specific broadcast sockets
+                    // Add bind address specific broadcast listeners
                     // IPv6 is currently unsupported
                     var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
                     foreach (var intf in validInterfaces)
@@ -102,9 +102,18 @@ namespace Emby.Server.Implementations.EntryPoints
                 }
                 else
                 {
-                    var server = new UdpServer(_logger, _appHost, _config, IPAddress.Any, PortNumber);
-                    server.Start(_cancellationTokenSource.Token);
-                    _udpServers.Add(server);
+                    // Add bind address specific broadcast listeners
+                    // IPv6 is currently unsupported
+                    var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
+                    foreach (var intf in validInterfaces)
+                    {
+                        var intfAddress = intf.Address;
+                        _logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", intfAddress, PortNumber);
+
+                        var server = new UdpServer(_logger, _appHost, _config, intfAddress, PortNumber);
+                        server.Start(_cancellationTokenSource.Token);
+                        _udpServers.Add(server);
+                    }
                 }
             }
             catch (SocketException ex)
@@ -119,7 +128,7 @@ namespace Emby.Server.Implementations.EntryPoints
         {
             if (_disposed)
             {
-                throw new ObjectDisposedException(this.GetType().Name);
+                throw new ObjectDisposedException(GetType().Name);
             }
         }
 

+ 23 - 10
Emby.Server.Implementations/Net/SocketFactory.cs

@@ -1,12 +1,15 @@
-#pragma warning disable CS1591
-
 using System;
+using System.Linq;
 using System.Net;
+using System.Net.NetworkInformation;
 using System.Net.Sockets;
 using MediaBrowser.Model.Net;
 
 namespace Emby.Server.Implementations.Net
 {
+    /// <summary>
+    /// Factory class to create different kinds of sockets.
+    /// </summary>
     public class SocketFactory : ISocketFactory
     {
         /// <inheritdoc />
@@ -38,7 +41,8 @@ namespace Emby.Server.Implementations.Net
         /// <inheritdoc />
         public Socket CreateSsdpUdpSocket(IPData bindInterface, int localPort)
         {
-            ArgumentNullException.ThrowIfNull(bindInterface.Address);
+            var interfaceAddress = bindInterface.Address;
+            ArgumentNullException.ThrowIfNull(interfaceAddress);
 
             if (localPort < 0)
             {
@@ -49,7 +53,7 @@ namespace Emby.Server.Implementations.Net
             try
             {
                 socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
-                socket.Bind(new IPEndPoint(bindInterface.Address, localPort));
+                socket.Bind(new IPEndPoint(interfaceAddress, localPort));
 
                 return socket;
             }
@@ -82,16 +86,25 @@ namespace Emby.Server.Implementations.Net
 
             try
             {
-                var interfaceIndex = bindInterface.Index;
-                var interfaceIndexSwapped = (int)IPAddress.HostToNetworkOrder(interfaceIndex);
-
                 socket.MulticastLoopback = false;
                 socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                 socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
                 socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
-                socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, interfaceIndexSwapped);
-                socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex));
-                socket.Bind(new IPEndPoint(multicastAddress, localPort));
+
+                if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
+                {
+                    socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));
+                    socket.Bind(new IPEndPoint(multicastAddress, localPort));
+                }
+                else
+                {
+                    // Only create socket if interface supports multicast
+                    var interfaceIndex = bindInterface.Index;
+                    var interfaceIndexSwapped = IPAddress.HostToNetworkOrder(interfaceIndex);
+
+                    socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex));
+                    socket.Bind(new IPEndPoint(bindIPAddress, localPort));
+                }
 
                 return socket;
             }

+ 8 - 3
Emby.Server.Implementations/Udp/UdpServer.cs

@@ -52,7 +52,10 @@ namespace Emby.Server.Implementations.Udp
 
             _endpoint = new IPEndPoint(bindAddress, port);
 
-            _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+            _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
+            {
+                MulticastLoopback = false,
+            };
             _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
         }
 
@@ -74,6 +77,7 @@ namespace Emby.Server.Implementations.Udp
 
             try
             {
+                _logger.LogDebug("Sending AutoDiscovery response");
                 await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint, cancellationToken).ConfigureAwait(false);
             }
             catch (SocketException ex)
@@ -99,7 +103,8 @@ namespace Emby.Server.Implementations.Udp
             {
                 try
                 {
-                    var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint, cancellationToken).ConfigureAwait(false);
+                    var endpoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
+                    var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, endpoint, cancellationToken).ConfigureAwait(false);
                     var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
                     if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
                     {
@@ -112,7 +117,7 @@ namespace Emby.Server.Implementations.Udp
                 }
                 catch (OperationCanceledException)
                 {
-                    // Don't throw
+                    _logger.LogDebug("Broadcast socket operation cancelled");
                 }
             }
         }

+ 8 - 5
Jellyfin.Networking/Extensions/NetworkExtensions.cs

@@ -231,12 +231,12 @@ public static partial class NetworkExtensions
                 }
                 else if (address.AddressFamily == AddressFamily.InterNetwork)
                 {
-                    result = new IPNetwork(address, Network.MinimumIPv4PrefixSize);
+                    result = address.Equals(IPAddress.Any) ? Network.IPv4Any : new IPNetwork(address, Network.MinimumIPv4PrefixSize);
                     return true;
                 }
                 else if (address.AddressFamily == AddressFamily.InterNetworkV6)
                 {
-                    result = new IPNetwork(address, Network.MinimumIPv6PrefixSize);
+                    result = address.Equals(IPAddress.IPv6Any) ? Network.IPv6Any : new IPNetwork(address, Network.MinimumIPv6PrefixSize);
                     return true;
                 }
             }
@@ -284,12 +284,15 @@ public static partial class NetworkExtensions
 
         if (hosts.Count <= 2)
         {
+            var firstPart = hosts[0];
+
             // Is hostname or hostname:port
-            if (FqdnGeneratedRegex().IsMatch(hosts[0]))
+            if (FqdnGeneratedRegex().IsMatch(firstPart))
             {
                 try
                 {
-                    addresses = Dns.GetHostAddresses(hosts[0]);
+                    // .NET automatically filters only supported returned addresses based on OS support.
+                    addresses = Dns.GetHostAddresses(firstPart);
                     return true;
                 }
                 catch (SocketException)
@@ -299,7 +302,7 @@ public static partial class NetworkExtensions
             }
 
             // Is an IPv4 or IPv4:port
-            if (IPAddress.TryParse(hosts[0].AsSpan().LeftPart('/'), out var address))
+            if (IPAddress.TryParse(firstPart.AsSpan().LeftPart('/'), out var address))
             {
                 if (((address.AddressFamily == AddressFamily.InterNetwork) && (!isIPv4Enabled && isIPv6Enabled))
                     || ((address.AddressFamily == AddressFamily.InterNetworkV6) && (isIPv4Enabled && !isIPv6Enabled)))

+ 157 - 87
Jellyfin.Networking/Manager/NetworkManager.cs

@@ -15,7 +15,9 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Net;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
+using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 
 namespace Jellyfin.Networking.Manager
 {
@@ -33,12 +35,14 @@ namespace Jellyfin.Networking.Manager
 
         private readonly IConfigurationManager _configurationManager;
 
+        private readonly IConfiguration _startupConfig;
+
         private readonly object _networkEventLock;
 
         /// <summary>
         /// Holds the published server URLs and the IPs to use them on.
         /// </summary>
-        private IReadOnlyDictionary<IPData, string> _publishedServerUrls;
+        private IReadOnlyList<PublishedServerUriOverride> _publishedServerUrls;
 
         private IReadOnlyList<IPNetwork> _remoteAddressFilter;
 
@@ -76,20 +80,22 @@ namespace Jellyfin.Networking.Manager
         /// <summary>
         /// Initializes a new instance of the <see cref="NetworkManager"/> class.
         /// </summary>
-        /// <param name="configurationManager">IServerConfigurationManager instance.</param>
+        /// <param name="configurationManager">The <see cref="IConfigurationManager"/> instance.</param>
+        /// <param name="startupConfig">The <see cref="IConfiguration"/> instance holding startup parameters.</param>
         /// <param name="logger">Logger to use for messages.</param>
 #pragma warning disable CS8618 // Non-nullable field is uninitialized. : Values are set in UpdateSettings function. Compiler doesn't yet recognise this.
-        public NetworkManager(IConfigurationManager configurationManager, ILogger<NetworkManager> logger)
+        public NetworkManager(IConfigurationManager configurationManager, IConfiguration startupConfig, ILogger<NetworkManager> logger)
         {
             ArgumentNullException.ThrowIfNull(logger);
             ArgumentNullException.ThrowIfNull(configurationManager);
 
             _logger = logger;
             _configurationManager = configurationManager;
+            _startupConfig = startupConfig;
             _initLock = new();
             _interfaces = new List<IPData>();
             _macAddresses = new List<PhysicalAddress>();
-            _publishedServerUrls = new Dictionary<IPData, string>();
+            _publishedServerUrls = new List<PublishedServerUriOverride>();
             _networkEventLock = new object();
             _remoteAddressFilter = new List<IPNetwork>();
 
@@ -130,7 +136,7 @@ namespace Jellyfin.Networking.Manager
         /// <summary>
         /// Gets the Published server override list.
         /// </summary>
-        public IReadOnlyDictionary<IPData, string> PublishedServerUrls => _publishedServerUrls;
+        public IReadOnlyList<PublishedServerUriOverride> PublishedServerUrls => _publishedServerUrls;
 
         /// <inheritdoc/>
         public void Dispose()
@@ -170,7 +176,6 @@ namespace Jellyfin.Networking.Manager
             {
                 if (!_eventfire)
                 {
-                    _logger.LogDebug("Network Address Change Event.");
                     // As network events tend to fire one after the other only fire once every second.
                     _eventfire = true;
                     OnNetworkChange();
@@ -193,11 +198,12 @@ namespace Jellyfin.Networking.Manager
                 }
                 else
                 {
-                    InitialiseInterfaces();
-                    InitialiseLan(networkConfig);
+                    InitializeInterfaces();
+                    InitializeLan(networkConfig);
                     EnforceBindSettings(networkConfig);
                 }
 
+                PrintNetworkInformation(networkConfig);
                 NetworkChanged?.Invoke(this, EventArgs.Empty);
             }
             finally
@@ -210,7 +216,7 @@ namespace Jellyfin.Networking.Manager
         /// 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 InitialiseInterfaces()
+        private void InitializeInterfaces()
         {
             lock (_initLock)
             {
@@ -222,7 +228,7 @@ namespace Jellyfin.Networking.Manager
                 try
                 {
                     var nics = NetworkInterface.GetAllNetworkInterfaces()
-                        .Where(i => i.SupportsMulticast && i.OperationalStatus == OperationalStatus.Up);
+                        .Where(i => i.OperationalStatus == OperationalStatus.Up);
 
                     foreach (NetworkInterface adapter in nics)
                     {
@@ -242,34 +248,36 @@ namespace Jellyfin.Networking.Manager
                             {
                                 if (IsIPv4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
                                 {
-                                    var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
-                                    interfaceObject.Index = ipProperties.GetIPv4Properties().Index;
-                                    interfaceObject.Name = adapter.Name;
+                                    var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
+                                    {
+                                        Index = ipProperties.GetIPv4Properties().Index,
+                                        Name = adapter.Name,
+                                        SupportsMulticast = adapter.SupportsMulticast
+                                    };
 
                                     interfaces.Add(interfaceObject);
                                 }
                                 else if (IsIPv6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
                                 {
-                                    var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name);
-                                    interfaceObject.Index = ipProperties.GetIPv6Properties().Index;
-                                    interfaceObject.Name = adapter.Name;
+                                    var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
+                                    {
+                                        Index = ipProperties.GetIPv6Properties().Index,
+                                        Name = adapter.Name,
+                                        SupportsMulticast = adapter.SupportsMulticast
+                                    };
 
                                     interfaces.Add(interfaceObject);
                                 }
                             }
                         }
-#pragma warning disable CA1031 // Do not catch general exception types
                         catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
                         {
                             // Ignore error, and attempt to continue.
                             _logger.LogError(ex, "Error encountered parsing interfaces.");
                         }
                     }
                 }
-#pragma warning disable CA1031 // Do not catch general exception types
                 catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
                 {
                     _logger.LogError(ex, "Error obtaining interfaces.");
                 }
@@ -279,14 +287,14 @@ namespace Jellyfin.Networking.Manager
                 {
                     _logger.LogWarning("No interface information available. Using loopback interface(s).");
 
-                    if (IsIPv4Enabled && !IsIPv6Enabled)
+                    if (IsIPv4Enabled)
                     {
-                        interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
+                        interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
                     }
 
-                    if (!IsIPv4Enabled && IsIPv6Enabled)
+                    if (IsIPv6Enabled)
                     {
-                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
+                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
                     }
                 }
 
@@ -299,9 +307,9 @@ namespace Jellyfin.Networking.Manager
         }
 
         /// <summary>
-        /// Initialises internal LAN cache.
+        /// Initializes internal LAN cache.
         /// </summary>
-        private void InitialiseLan(NetworkConfiguration config)
+        private void InitializeLan(NetworkConfiguration config)
         {
             lock (_initLock)
             {
@@ -341,10 +349,6 @@ namespace Jellyfin.Networking.Manager
                 _excludedSubnets = NetworkExtensions.TryParseToSubnets(subnets, out var excludedSubnets, true)
                     ? excludedSubnets
                     : new List<IPNetwork>();
-
-                _logger.LogInformation("Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
-                _logger.LogInformation("Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
-                _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
             }
         }
 
@@ -369,12 +373,12 @@ namespace Jellyfin.Networking.Manager
                         .ToHashSet();
                     interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
 
-                    if (bindAddresses.Contains(IPAddress.Loopback))
+                    if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
                     {
                         interfaces.Add(new IPData(IPAddress.Loopback, Network.IPv4RFC5735Loopback, "lo"));
                     }
 
-                    if (bindAddresses.Contains(IPAddress.IPv6Loopback))
+                    if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
                     {
                         interfaces.Add(new IPData(IPAddress.IPv6Loopback, Network.IPv6RFC4291Loopback, "lo"));
                     }
@@ -409,15 +413,14 @@ namespace Jellyfin.Networking.Manager
                     interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
                 }
 
-                _logger.LogInformation("Using bind addresses: {0}", interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
                 _interfaces = interfaces;
             }
         }
 
         /// <summary>
-        /// Initialises the remote address values.
+        /// Initializes the remote address values.
         /// </summary>
-        private void InitialiseRemote(NetworkConfiguration config)
+        private void InitializeRemote(NetworkConfiguration config)
         {
             lock (_initLock)
             {
@@ -455,13 +458,33 @@ namespace Jellyfin.Networking.Manager
         /// format is subnet=ipaddress|host|uri
         /// when subnet = 0.0.0.0, any external address matches.
         /// </summary>
-        private void InitialiseOverrides(NetworkConfiguration config)
+        private void InitializeOverrides(NetworkConfiguration config)
         {
             lock (_initLock)
             {
-                var publishedServerUrls = new Dictionary<IPData, string>();
-                var overrides = config.PublishedServerUriBySubnet;
+                var publishedServerUrls = new List<PublishedServerUriOverride>();
+
+                // Prefer startup configuration.
+                var startupOverrideKey = _startupConfig[AddressOverrideKey];
+                if (!string.IsNullOrEmpty(startupOverrideKey))
+                {
+                    publishedServerUrls.Add(
+                        new PublishedServerUriOverride(
+                            new IPData(IPAddress.Any, Network.IPv4Any),
+                            startupOverrideKey,
+                            true,
+                            true));
+                    publishedServerUrls.Add(
+                        new PublishedServerUriOverride(
+                            new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+                            startupOverrideKey,
+                            true,
+                            true));
+                    _publishedServerUrls = publishedServerUrls;
+                    return;
+                }
 
+                var overrides = config.PublishedServerUriBySubnet;
                 foreach (var entry in overrides)
                 {
                     var parts = entry.Split('=');
@@ -475,31 +498,70 @@ namespace Jellyfin.Networking.Manager
                     var identifier = parts[0];
                     if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
                     {
-                        publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
+                        // Drop any other overrides in case an "all" override exists
+                        publishedServerUrls.Clear();
+                        publishedServerUrls.Add(
+                            new PublishedServerUriOverride(
+                                new IPData(IPAddress.Any, Network.IPv4Any),
+                                replacement,
+                                true,
+                                true));
+                        publishedServerUrls.Add(
+                            new PublishedServerUriOverride(
+                                new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+                                replacement,
+                                true,
+                                true));
+                        break;
                     }
                     else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
                     {
-                        publishedServerUrls[new IPData(IPAddress.Any, Network.IPv4Any)] = replacement;
-                        publishedServerUrls[new IPData(IPAddress.IPv6Any, Network.IPv6Any)] = replacement;
+                        publishedServerUrls.Add(
+                            new PublishedServerUriOverride(
+                                new IPData(IPAddress.Any, Network.IPv4Any),
+                                replacement,
+                                false,
+                                true));
+                        publishedServerUrls.Add(
+                            new PublishedServerUriOverride(
+                                new IPData(IPAddress.IPv6Any, Network.IPv6Any),
+                                replacement,
+                                false,
+                                true));
                     }
                     else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
                     {
                         foreach (var lan in _lanSubnets)
                         {
                             var lanPrefix = lan.Prefix;
-                            publishedServerUrls[new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength))] = replacement;
+                            publishedServerUrls.Add(
+                                new PublishedServerUriOverride(
+                                    new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
+                                    replacement,
+                                    true,
+                                    false));
                         }
                     }
                     else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result is not null)
                     {
                         var data = new IPData(result.Prefix, result);
-                        publishedServerUrls[data] = replacement;
+                        publishedServerUrls.Add(
+                            new PublishedServerUriOverride(
+                                data,
+                                replacement,
+                                true,
+                                true));
                     }
                     else if (TryParseInterface(identifier, out var ifaces))
                     {
                         foreach (var iface in ifaces)
                         {
-                            publishedServerUrls[iface] = replacement;
+                            publishedServerUrls.Add(
+                            new PublishedServerUriOverride(
+                                iface,
+                                replacement,
+                                true,
+                                true));
                         }
                     }
                     else
@@ -521,7 +583,7 @@ namespace Jellyfin.Networking.Manager
         }
 
         /// <summary>
-        /// Reloads all settings and re-initialises the instance.
+        /// Reloads all settings and re-Initializes the instance.
         /// </summary>
         /// <param name="configuration">The <see cref="NetworkConfiguration"/> to use.</param>
         public void UpdateSettings(object configuration)
@@ -531,12 +593,12 @@ namespace Jellyfin.Networking.Manager
             var config = (NetworkConfiguration)configuration;
             HappyEyeballs.HttpClientExtension.UseIPv6 = config.EnableIPv6;
 
-            InitialiseLan(config);
-            InitialiseRemote(config);
+            InitializeLan(config);
+            InitializeRemote(config);
 
             if (string.IsNullOrEmpty(MockNetworkSettings))
             {
-                InitialiseInterfaces();
+                InitializeInterfaces();
             }
             else // Used in testing only.
             {
@@ -552,8 +614,10 @@ namespace Jellyfin.Networking.Manager
                         var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
                         if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
                         {
-                            var data = new IPData(address, subnet, parts[2]);
-                            data.Index = index;
+                            var data = new IPData(address, subnet, parts[2])
+                            {
+                                Index = index
+                            };
                             interfaces.Add(data);
                         }
                     }
@@ -567,7 +631,9 @@ namespace Jellyfin.Networking.Manager
             }
 
             EnforceBindSettings(config);
-            InitialiseOverrides(config);
+            InitializeOverrides(config);
+
+            PrintNetworkInformation(config, false);
         }
 
         /// <summary>
@@ -672,20 +738,13 @@ namespace Jellyfin.Networking.Manager
         /// <inheritdoc/>
         public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
         {
-            if (_interfaces.Count != 0)
+            if (_interfaces.Count > 0 || individualInterfaces)
             {
                 return _interfaces;
             }
 
             // No bind address and no exclusions, so listen on all interfaces.
             var result = new List<IPData>();
-
-            if (individualInterfaces)
-            {
-                result.AddRange(_interfaces);
-                return result;
-            }
-
             if (IsIPv4Enabled && IsIPv6Enabled)
             {
                 // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
@@ -892,31 +951,34 @@ namespace Jellyfin.Networking.Manager
             bindPreference = string.Empty;
             int? port = null;
 
-            var validPublishedServerUrls = _publishedServerUrls.Where(x => x.Key.Address.Equals(IPAddress.Any)
-                                                || x.Key.Address.Equals(IPAddress.IPv6Any)
-                                                || x.Key.Subnet.Contains(source))
-                                            .DistinctBy(x => x.Key)
-                                            .OrderBy(x => x.Key.Address.Equals(IPAddress.Any)
-                                                || x.Key.Address.Equals(IPAddress.IPv6Any))
+            // Only consider subnets including the source IP, prefering 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))
+                                            .OrderByDescending(x => x.Data.Subnet.PrefixLength)
+                                            .ToList();
+            }
+            else
+            {
+                // Only use matching external subnets
+                // Prefer more specific (bigger subnet prefix) overrides
+                validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && x.Data.Subnet.Contains(source))
+                                            .OrderByDescending(x => x.Data.Subnet.PrefixLength)
                                             .ToList();
+            }
 
-            // Check for user override.
             foreach (var data in validPublishedServerUrls)
             {
-                if (isInExternalSubnet && (data.Key.Address.Equals(IPAddress.Any) || data.Key.Address.Equals(IPAddress.IPv6Any)))
-                {
-                    // External.
-                    bindPreference = data.Value;
-                    break;
-                }
-
-                // Get address interface.
-                var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Key.Subnet.Contains(x.Address));
+                // Get interface matching override subnet
+                var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address));
 
                 if (intf?.Address is not null)
                 {
-                    // Match IP address.
-                    bindPreference = data.Value;
+                    // If matching interface is found, use override
+                    bindPreference = data.OverrideUri;
                     break;
                 }
             }
@@ -927,7 +989,7 @@ namespace Jellyfin.Networking.Manager
                 return false;
             }
 
-            // Has it got a port defined?
+            // Handle override specifying port
             var parts = bindPreference.Split(':');
             if (parts.Length > 1)
             {
@@ -935,18 +997,12 @@ namespace Jellyfin.Networking.Manager
                 {
                     bindPreference = parts[0];
                     port = p;
+                    _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
+                    return true;
                 }
             }
 
-            if (port is not null)
-            {
-                _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
-            }
-            else
-            {
-                _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
-            }
-
+            _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
             return true;
         }
 
@@ -1053,5 +1109,19 @@ namespace Jellyfin.Networking.Manager
             _logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
             return true;
         }
+
+        private void PrintNetworkInformation(NetworkConfiguration config, bool debug = true)
+        {
+            var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
+            if (_logger.IsEnabled(logLevel))
+            {
+                _logger.Log(logLevel, "Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
+                _logger.Log(logLevel, "Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
+                _logger.Log(logLevel, "Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
+                _logger.Log(logLevel, "Using bind addresses: {0}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
+                _logger.Log(logLevel, "Remote IP filter is {0}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist");
+                _logger.Log(logLevel, "Filter list: {0}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength));
+            }
+        }
     }
 }

+ 1 - 1
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -282,7 +282,7 @@ namespace Jellyfin.Server.Extensions
                         AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength);
                     }
                 }
-                else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses))
+                else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
                 {
                     foreach (var address in addresses)
                     {

+ 1 - 2
Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs

@@ -3,7 +3,6 @@ using System.IO;
 using System.Net;
 using Jellyfin.Server.Helpers;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Extensions;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Configuration;
@@ -36,7 +35,7 @@ public static class WebHostBuilderExtensions
         return builder
             .UseKestrel((builderContext, options) =>
             {
-                var addresses = appHost.NetManager.GetAllBindInterfaces();
+                var addresses = appHost.NetManager.GetAllBindInterfaces(true);
 
                 bool flagged = false;
                 foreach (var netAdd in addresses)

+ 5 - 0
MediaBrowser.Model/Net/IPData.cs

@@ -47,6 +47,11 @@ public class IPData
     /// </summary>
     public int Index { get; set; }
 
+    /// <summary>
+    /// Gets or sets a value indicating whether the network supports multicast.
+    /// </summary>
+    public bool SupportsMulticast { get; set; } = false;
+
     /// <summary>
     /// Gets or sets the interface name.
     /// </summary>

+ 42 - 0
MediaBrowser.Model/Net/PublishedServerUriOverride.cs

@@ -0,0 +1,42 @@
+namespace MediaBrowser.Model.Net;
+
+/// <summary>
+/// Class holding information for a published server URI override.
+/// </summary>
+public class PublishedServerUriOverride
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PublishedServerUriOverride"/> class.
+    /// </summary>
+    /// <param name="data">The <see cref="IPData"/>.</param>
+    /// <param name="overrideUri">The override.</param>
+    /// <param name="internalOverride">A value indicating whether the override is for internal requests.</param>
+    /// <param name="externalOverride">A value indicating whether the override is for external requests.</param>
+    public PublishedServerUriOverride(IPData data, string overrideUri, bool internalOverride, bool externalOverride)
+    {
+        Data = data;
+        OverrideUri = overrideUri;
+        IsInternalOverride = internalOverride;
+        IsExternalOverride = externalOverride;
+    }
+
+    /// <summary>
+    /// Gets or sets the object's IP address.
+    /// </summary>
+    public IPData Data { get; set; }
+
+    /// <summary>
+    /// Gets or sets the override URI.
+    /// </summary>
+    public string OverrideUri { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the override should be applied to internal requests.
+    /// </summary>
+    public bool IsInternalOverride { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the override should be applied to external requests.
+    /// </summary>
+    public bool IsExternalOverride { get; set; }
+}

+ 60 - 64
RSSDP/SsdpCommunicationsServer.cs

@@ -32,10 +32,10 @@ namespace Rssdp.Infrastructure
          * port to use, we will default to 0 which allows the underlying system to auto-assign a free port.
          */
 
-        private object _BroadcastListenSocketSynchroniser = new object();
+        private object _BroadcastListenSocketSynchroniser = new();
         private List<Socket> _MulticastListenSockets;
 
-        private object _SendSocketSynchroniser = new object();
+        private object _SendSocketSynchroniser = new();
         private List<Socket> _sendSockets;
 
         private HttpRequestParser _RequestParser;
@@ -48,7 +48,6 @@ namespace Rssdp.Infrastructure
         private int _MulticastTtl;
 
         private bool _IsShared;
-        private readonly bool _enableMultiSocketBinding;
 
         /// <summary>
         /// Raised when a HTTPU request message is received by a socket (unicast or multicast).
@@ -64,9 +63,11 @@ namespace Rssdp.Infrastructure
         /// Minimum constructor.
         /// </summary>
         /// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
-        public SsdpCommunicationsServer(ISocketFactory socketFactory,
-            INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
-            : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding)
+        public SsdpCommunicationsServer(
+            ISocketFactory socketFactory,
+            INetworkManager networkManager,
+            ILogger logger)
+            : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger)
         {
 
         }
@@ -76,7 +77,12 @@ namespace Rssdp.Infrastructure
         /// </summary>
         /// <exception cref="ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
         /// <exception cref="ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception>
-        public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
+        public SsdpCommunicationsServer(
+            ISocketFactory socketFactory,
+            int localPort,
+            int multicastTimeToLive,
+            INetworkManager networkManager,
+            ILogger logger)
         {
             if (socketFactory is null)
             {
@@ -88,19 +94,18 @@ namespace Rssdp.Infrastructure
                 throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero.");
             }
 
-            _BroadcastListenSocketSynchroniser = new object();
-            _SendSocketSynchroniser = new object();
+            _BroadcastListenSocketSynchroniser = new();
+            _SendSocketSynchroniser = new();
 
             _LocalPort = localPort;
             _SocketFactory = socketFactory;
 
-            _RequestParser = new HttpRequestParser();
-            _ResponseParser = new HttpResponseParser();
+            _RequestParser = new();
+            _ResponseParser = new();
 
             _MulticastTtl = multicastTimeToLive;
             _networkManager = networkManager;
             _logger = logger;
-            _enableMultiSocketBinding = enableMultiSocketBinding;
         }
 
         /// <summary>
@@ -335,7 +340,7 @@ namespace Rssdp.Infrastructure
             {
                 sockets = sockets.ToList();
 
-                var tasks = sockets.Where(s => (fromlocalIPAddress is null || fromlocalIPAddress.Equals(((IPEndPoint)s.LocalEndPoint).Address)))
+                var tasks = sockets.Where(s => fromlocalIPAddress is null || fromlocalIPAddress.Equals(((IPEndPoint)s.LocalEndPoint).Address))
                     .Select(s => SendFromSocket(s, messageData, destination, cancellationToken));
                 return Task.WhenAll(tasks);
             }
@@ -347,33 +352,26 @@ namespace Rssdp.Infrastructure
         {
             var sockets = new List<Socket>();
             var multicastGroupAddress = IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress);
-            if (_enableMultiSocketBinding)
-            {
-                // IPv6 is currently unsupported
-                var validInterfaces = _networkManager.GetInternalBindAddresses()
-                    .Where(x => x.Address is not null)
-                    .Where(x => x.AddressFamily == AddressFamily.InterNetwork)
-                    .DistinctBy(x => x.Index);
 
-                foreach (var intf in validInterfaces)
+            // IPv6 is currently unsupported
+            var validInterfaces = _networkManager.GetInternalBindAddresses()
+                .Where(x => x.Address is not null)
+                .Where(x => x.SupportsMulticast)
+                .Where(x => x.AddressFamily == AddressFamily.InterNetwork)
+                .DistinctBy(x => x.Index);
+
+            foreach (var intf in validInterfaces)
+            {
+                try
                 {
-                    try
-                    {
-                        var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, intf, _MulticastTtl, SsdpConstants.MulticastPort);
-                        _ = ListenToSocketInternal(socket);
-                        sockets.Add(socket);
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, "Error in CreateMulticastSocketsAndListen. IP address: {0}", intf.Address);
-                    }
+                    var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, intf, _MulticastTtl, SsdpConstants.MulticastPort);
+                    _ = ListenToSocketInternal(socket);
+                    sockets.Add(socket);
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "Failed to create SSDP UDP multicast socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index);
                 }
-            }
-            else
-            {
-                var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, new IPData(IPAddress.Any, null), _MulticastTtl, SsdpConstants.MulticastPort);
-                _ = ListenToSocketInternal(socket);
-                sockets.Add(socket);
             }
 
             return sockets;
@@ -382,34 +380,32 @@ namespace Rssdp.Infrastructure
         private List<Socket> CreateSendSockets()
         {
             var sockets = new List<Socket>();
-            if (_enableMultiSocketBinding)
+
+            // IPv6 is currently unsupported
+            var validInterfaces = _networkManager.GetInternalBindAddresses()
+                .Where(x => x.Address is not null)
+                .Where(x => x.SupportsMulticast)
+                .Where(x => x.AddressFamily == AddressFamily.InterNetwork);
+
+            if (OperatingSystem.IsMacOS())
             {
-                // IPv6 is currently unsupported
-                var validInterfaces = _networkManager.GetInternalBindAddresses()
-                    .Where(x => x.Address is not null)
-                    .Where(x => x.AddressFamily == AddressFamily.InterNetwork);
+                // Manually remove loopback on macOS due to https://github.com/dotnet/runtime/issues/24340
+                validInterfaces = validInterfaces.Where(x => !x.Address.Equals(IPAddress.Loopback));
+            }
 
-                foreach (var intf in validInterfaces)
+            foreach (var intf in validInterfaces)
+            {
+                try
                 {
-                    try
-                    {
-                        var socket = _SocketFactory.CreateSsdpUdpSocket(intf, _LocalPort);
-                        _ = ListenToSocketInternal(socket);
-                        sockets.Add(socket);
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.LogError(ex, "Error in CreateSsdpUdpSocket. IPAddress: {0}", intf.Address);
-                    }
+                    var socket = _SocketFactory.CreateSsdpUdpSocket(intf, _LocalPort);
+                    _ = ListenToSocketInternal(socket);
+                    sockets.Add(socket);
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "Failed to create SSDP UDP sender socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index);
                 }
             }
-            else
-            {
-                var socket = _SocketFactory.CreateSsdpUdpSocket(new IPData(IPAddress.Any, null), _LocalPort);
-                _ = ListenToSocketInternal(socket);
-                sockets.Add(socket);
-            }
-
 
             return sockets;
         }
@@ -423,7 +419,7 @@ namespace Rssdp.Infrastructure
             {
                 try
                 {
-                    var result = await socket.ReceiveMessageFromAsync(receiveBuffer, SocketFlags.None, new IPEndPoint(IPAddress.Any, 0), CancellationToken.None).ConfigureAwait(false);;
+                    var result = await socket.ReceiveMessageFromAsync(receiveBuffer, new IPEndPoint(IPAddress.Any, _LocalPort), CancellationToken.None).ConfigureAwait(false);;
 
                     if (result.ReceivedBytes > 0)
                     {
@@ -431,7 +427,7 @@ namespace Rssdp.Infrastructure
                         var localEndpointAdapter = _networkManager.GetAllBindInterfaces().First(a => a.Index == result.PacketInformation.Interface);
 
                         ProcessMessage(
-                            UTF8Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes),
+                            Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes),
                             remoteEndpoint,
                             localEndpointAdapter.Address);
                     }
@@ -511,7 +507,7 @@ namespace Rssdp.Infrastructure
                 return;
             }
 
-            var handlers = this.RequestReceived;
+            var handlers = RequestReceived;
             if (handlers is not null)
             {
                 handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnlocalIPAddress));
@@ -520,7 +516,7 @@ namespace Rssdp.Infrastructure
 
         private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIPAddress)
         {
-            var handlers = this.ResponseReceived;
+            var handlers = ResponseReceived;
             if (handlers is not null)
             {
                 handlers(this, new ResponseReceivedEventArgs(data, endPoint)

+ 19 - 23
RSSDP/SsdpDeviceLocator.cs

@@ -17,7 +17,7 @@ namespace Rssdp.Infrastructure
         private ISsdpCommunicationsServer _CommunicationsServer;
 
         private Timer _BroadcastTimer;
-        private object _timerLock = new object();
+        private object _timerLock = new();
 
         private string _OSName;
 
@@ -221,12 +221,12 @@ namespace Rssdp.Infrastructure
         /// <seealso cref="DeviceAvailable"/>
         protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IPAddress)
         {
-            if (this.IsDisposed)
+            if (IsDisposed)
             {
                 return;
             }
 
-            var handlers = this.DeviceAvailable;
+            var handlers = DeviceAvailable;
             if (handlers is not null)
             {
                 handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)
@@ -244,12 +244,12 @@ namespace Rssdp.Infrastructure
         /// <seealso cref="DeviceUnavailable"/>
         protected virtual void OnDeviceUnavailable(DiscoveredSsdpDevice device, bool expired)
         {
-            if (this.IsDisposed)
+            if (IsDisposed)
             {
                 return;
             }
 
-            var handlers = this.DeviceUnavailable;
+            var handlers = DeviceUnavailable;
             if (handlers is not null)
             {
                 handlers(this, new DeviceUnavailableEventArgs(device, expired));
@@ -291,8 +291,8 @@ namespace Rssdp.Infrastructure
                 _CommunicationsServer = null;
                 if (commsServer is not null)
                 {
-                    commsServer.ResponseReceived -= this.CommsServer_ResponseReceived;
-                    commsServer.RequestReceived -= this.CommsServer_RequestReceived;
+                    commsServer.ResponseReceived -= CommsServer_ResponseReceived;
+                    commsServer.RequestReceived -= CommsServer_RequestReceived;
                 }
             }
         }
@@ -341,7 +341,7 @@ namespace Rssdp.Infrastructure
 
             var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
-            values["HOST"] = "239.255.255.250:1900";
+            values["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort);
             values["USER-AGENT"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
             values["MAN"] = "\"ssdp:discover\"";
 
@@ -382,17 +382,17 @@ namespace Rssdp.Infrastructure
 
         private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IPAddress)
         {
-            if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
+            if (string.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
             {
                 return;
             }
 
             var notificationType = GetFirstHeaderStringValue("NTS", message);
-            if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
+            if (string.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
             {
                 ProcessAliveNotification(message, IPAddress);
             }
-            else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
+            else if (string.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
             {
                 ProcessByeByeNotification(message);
             }
@@ -420,7 +420,7 @@ namespace Rssdp.Infrastructure
         private void ProcessByeByeNotification(HttpRequestMessage message)
         {
             var notficationType = GetFirstHeaderStringValue("NT", message);
-            if (!String.IsNullOrEmpty(notficationType))
+            if (!string.IsNullOrEmpty(notficationType))
             {
                 var usn = GetFirstHeaderStringValue("USN", message);
 
@@ -447,10 +447,9 @@ namespace Rssdp.Infrastructure
         private string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
         {
             string retVal = null;
-            IEnumerable<string> values;
             if (message.Headers.Contains(headerName))
             {
-                message.Headers.TryGetValues(headerName, out values);
+                message.Headers.TryGetValues(headerName, out var values);
                 if (values is not null)
                 {
                     retVal = values.FirstOrDefault();
@@ -463,10 +462,9 @@ namespace Rssdp.Infrastructure
         private string GetFirstHeaderStringValue(string headerName, HttpRequestMessage message)
         {
             string retVal = null;
-            IEnumerable<string> values;
             if (message.Headers.Contains(headerName))
             {
-                message.Headers.TryGetValues(headerName, out values);
+                message.Headers.TryGetValues(headerName, out var values);
                 if (values is not null)
                 {
                     retVal = values.FirstOrDefault();
@@ -479,10 +477,9 @@ namespace Rssdp.Infrastructure
         private Uri GetFirstHeaderUriValue(string headerName, HttpRequestMessage request)
         {
             string value = null;
-            IEnumerable<string> values;
             if (request.Headers.Contains(headerName))
             {
-                request.Headers.TryGetValues(headerName, out values);
+                request.Headers.TryGetValues(headerName, out var values);
                 if (values is not null)
                 {
                     value = values.FirstOrDefault();
@@ -496,10 +493,9 @@ namespace Rssdp.Infrastructure
         private Uri GetFirstHeaderUriValue(string headerName, HttpResponseMessage response)
         {
             string value = null;
-            IEnumerable<string> values;
             if (response.Headers.Contains(headerName))
             {
-                response.Headers.TryGetValues(headerName, out values);
+                response.Headers.TryGetValues(headerName, out var values);
                 if (values is not null)
                 {
                     value = values.FirstOrDefault();
@@ -529,7 +525,7 @@ namespace Rssdp.Infrastructure
 
                 foreach (var device in expiredDevices)
                 {
-                    if (this.IsDisposed)
+                    if (IsDisposed)
                     {
                         return;
                     }
@@ -543,7 +539,7 @@ namespace Rssdp.Infrastructure
             // problems.
             foreach (var expiredUsn in (from expiredDevice in expiredDevices select expiredDevice.Usn).Distinct())
             {
-                if (this.IsDisposed)
+                if (IsDisposed)
                 {
                     return;
                 }
@@ -560,7 +556,7 @@ namespace Rssdp.Infrastructure
                 existingDevices = FindExistingDeviceNotifications(_Devices, deviceUsn);
                 foreach (var existingDevice in existingDevices)
                 {
-                    if (this.IsDisposed)
+                    if (IsDisposed)
                     {
                         return true;
                     }

+ 46 - 43
RSSDP/SsdpDevicePublisher.cs

@@ -206,9 +206,9 @@ namespace Rssdp.Infrastructure
             IPAddress receivedOnlocalIPAddress,
             CancellationToken cancellationToken)
         {
-            if (String.IsNullOrEmpty(searchTarget))
+            if (string.IsNullOrEmpty(searchTarget))
             {
-                WriteTrace(String.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString()));
+                WriteTrace(string.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString()));
                 return;
             }
 
@@ -232,7 +232,7 @@ namespace Rssdp.Infrastructure
                 // return;
             }
 
-            if (!Int32.TryParse(mx, out var maxWaitInterval) || maxWaitInterval <= 0)
+            if (!int.TryParse(mx, out var maxWaitInterval) || maxWaitInterval <= 0)
             {
                 return;
             }
@@ -243,27 +243,27 @@ namespace Rssdp.Infrastructure
             }
 
             // Do not block synchronously as that may tie up a threadpool thread for several seconds.
-            Task.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) =>
+            Task.Delay(_Random.Next(16, maxWaitInterval * 1000)).ContinueWith((parentTask) =>
             {
                 // Copying devices to local array here to avoid threading issues/enumerator exceptions.
                 IEnumerable<SsdpDevice> devices = null;
                 lock (_Devices)
                 {
-                    if (String.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
+                    if (string.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
                     {
                         devices = GetAllDevicesAsFlatEnumerable().ToArray();
                     }
-                    else if (String.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (this.SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
+                    else if (string.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
                     {
                         devices = _Devices.ToArray();
                     }
                     else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase))
                     {
-                        devices = GetAllDevicesAsFlatEnumerable().Where(d => String.Compare(d.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0).ToArray();
+                        devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0).ToArray();
                     }
                     else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase))
                     {
-                        devices = GetAllDevicesAsFlatEnumerable().Where(d => String.Compare(d.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0).ToArray();
+                        devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0).ToArray();
                     }
                 }
 
@@ -299,7 +299,7 @@ namespace Rssdp.Infrastructure
             if (isRootDevice)
             {
                 SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken);
-                if (this.SupportPnpRootDevice)
+                if (SupportPnpRootDevice)
                 {
                     SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken);
                 }
@@ -312,7 +312,7 @@ namespace Rssdp.Infrastructure
 
         private string GetUsn(string udn, string fullDeviceType)
         {
-            return String.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType);
+            return string.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType);
         }
 
         private async void SendSearchResponse(
@@ -326,16 +326,17 @@ namespace Rssdp.Infrastructure
             const string header = "HTTP/1.1 200 OK";
 
             var rootDevice = device.ToRootDevice();
-            var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-            values["EXT"] = "";
-            values["DATE"] = DateTime.UtcNow.ToString("r");
-            values["HOST"] = "239.255.255.250:1900";
-            values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds;
-            values["ST"] = searchTarget;
-            values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
-            values["USN"] = uniqueServiceName;
-            values["LOCATION"] = rootDevice.Location.ToString();
+            var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+            {
+                ["EXT"] = "",
+                ["DATE"] = DateTime.UtcNow.ToString("r"),
+                ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
+                ["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds,
+                ["ST"] = searchTarget,
+                ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
+                ["USN"] = uniqueServiceName,
+                ["LOCATION"] = rootDevice.Location.ToString()
+            };
 
             var message = BuildMessage(header, values);
 
@@ -439,7 +440,7 @@ namespace Rssdp.Infrastructure
             if (isRoot)
             {
                 SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken);
-                if (this.SupportPnpRootDevice)
+                if (SupportPnpRootDevice)
                 {
                     SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken);
                 }
@@ -460,17 +461,18 @@ namespace Rssdp.Infrastructure
 
             const string header = "NOTIFY * HTTP/1.1";
 
-            var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-            // If needed later for non-server devices, these headers will need to be dynamic
-            values["HOST"] = "239.255.255.250:1900";
-            values["DATE"] = DateTime.UtcNow.ToString("r");
-            values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds;
-            values["LOCATION"] = rootDevice.Location.ToString();
-            values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
-            values["NTS"] = "ssdp:alive";
-            values["NT"] = notificationType;
-            values["USN"] = uniqueServiceName;
+            var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+            {
+                // If needed later for non-server devices, these headers will need to be dynamic
+                ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
+                ["DATE"] = DateTime.UtcNow.ToString("r"),
+                ["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds,
+                ["LOCATION"] = rootDevice.Location.ToString(),
+                ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
+                ["NTS"] = "ssdp:alive",
+                ["NT"] = notificationType,
+                ["USN"] = uniqueServiceName
+            };
 
             var message = BuildMessage(header, values);
 
@@ -485,7 +487,7 @@ namespace Rssdp.Infrastructure
             if (isRoot)
             {
                 tasks.Add(SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken));
-                if (this.SupportPnpRootDevice)
+                if (SupportPnpRootDevice)
                 {
                     tasks.Add(SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken));
                 }
@@ -506,20 +508,21 @@ namespace Rssdp.Infrastructure
         {
             const string header = "NOTIFY * HTTP/1.1";
 
-            var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-            // If needed later for non-server devices, these headers will need to be dynamic
-            values["HOST"] = "239.255.255.250:1900";
-            values["DATE"] = DateTime.UtcNow.ToString("r");
-            values["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion);
-            values["NTS"] = "ssdp:byebye";
-            values["NT"] = notificationType;
-            values["USN"] = uniqueServiceName;
+            var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+            {
+                // If needed later for non-server devices, these headers will need to be dynamic
+                ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort),
+                ["DATE"] = DateTime.UtcNow.ToString("r"),
+                ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion),
+                ["NTS"] = "ssdp:byebye",
+                ["NT"] = notificationType,
+                ["USN"] = uniqueServiceName
+            };
 
             var message = BuildMessage(header, values);
 
             var sendCount = IsDisposed ? 1 : 3;
-            WriteTrace(String.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device);
+            WriteTrace(string.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device);
             return _CommsServer.SendMulticastMessage(message, sendCount, _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken);
         }
 

+ 0 - 1
tests/Jellyfin.Networking.Tests/NetworkExtensionsTests.cs

@@ -16,7 +16,6 @@ namespace Jellyfin.Networking.Tests
         [InlineData("127.0.0.1:123")]
         [InlineData("localhost")]
         [InlineData("localhost:1345")]
-        [InlineData("www.google.co.uk")]
         [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
         [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")]
         [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")]

+ 7 - 3
tests/Jellyfin.Networking.Tests/NetworkManagerTests.cs

@@ -1,7 +1,9 @@
 using System.Net;
 using Jellyfin.Networking.Configuration;
 using Jellyfin.Networking.Manager;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
 using Xunit;
 
 namespace Jellyfin.Networking.Tests
@@ -28,7 +30,8 @@ namespace Jellyfin.Networking.Tests
                 LocalNetworkSubnets = network.Split(',')
             };
 
-            using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
 
             Assert.True(networkManager.IsInLocalNetwork(ip));
         }
@@ -56,9 +59,10 @@ namespace Jellyfin.Networking.Tests
                 LocalNetworkSubnets = network.Split(',')
             };
 
-            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var networkManager = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
 
-            Assert.False(nm.IsInLocalNetwork(ip));
+            Assert.False(networkManager.IsInLocalNetwork(ip));
         }
     }
 }

+ 24 - 15
tests/Jellyfin.Networking.Tests/NetworkParseTests.cs

@@ -7,6 +7,7 @@ using Jellyfin.Networking.Extensions;
 using Jellyfin.Networking.Manager;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.Net;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
 using Xunit;
@@ -54,7 +55,8 @@ namespace Jellyfin.Networking.Tests
             };
 
             NetworkManager.MockNetworkSettings = interfaces;
-            using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
             NetworkManager.MockNetworkSettings = string.Empty;
 
             Assert.Equal(value, "[" + string.Join(",", nm.GetInternalBindAddresses().Select(x => x.Address + "/" + x.Subnet.PrefixLength)) + "]");
@@ -200,7 +202,8 @@ namespace Jellyfin.Networking.Tests
             };
 
             NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
-            using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
             NetworkManager.MockNetworkSettings = string.Empty;
 
             // Check to see if DNS resolution is working. If not, skip test.
@@ -229,24 +232,24 @@ namespace Jellyfin.Networking.Tests
         [InlineData("192.168.1.1", "192.168.1.0/24", "eth16,eth11", false, "192.168.1.0/24=internal.jellyfin", "internal.jellyfin")]
 
         // User on external network, we're bound internal and external - so result is override.
-        [InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
+        [InlineData("8.8.8.8", "192.168.1.0/24", "eth16,eth11", false, "all=http://helloworld.com", "http://helloworld.com")]
 
         // User on internal network, we're bound internal only, but the address isn't in the LAN - so return the override.
-        [InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")]
+        [InlineData("10.10.10.10", "192.168.1.0/24", "eth16", false, "external=http://internalButNotDefinedAsLan.com", "http://internalButNotDefinedAsLan.com")]
 
         // User on internal network, no binding specified - so result is the 1st internal.
-        [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
+        [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "external=http://helloworld.com", "eth16")]
 
         // User on external network, internal binding only - so assumption is a proxy forward, return external override.
-        [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
+        [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "external=http://helloworld.com", "http://helloworld.com")]
 
         // User on external network, no binding - so result is the 1st external which is overriden.
-        [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
+        [InlineData("jellyfin.org", "192.168.1.0/24", "", false, "external=http://helloworld.com", "http://helloworld.com")]
 
-        // User assumed to be internal, no binding - so result is the 1st internal.
-        [InlineData("", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
+        // User assumed to be internal, no binding - so result is the 1st matching interface.
+        [InlineData("", "192.168.1.0/24", "", false, "all=http://helloworld.com", "eth16")]
 
-        // User is internal, no binding - so result is the 1st internal, which is then overridden.
+        // User is internal, no binding - so result is the 1st internal interface, which is then overridden.
         [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "eth16=http://helloworld.com", "http://helloworld.com")]
         public void TestBindInterfaceOverrides(string source, string lan, string bindAddresses, bool ipv6enabled, string publishedServers, string result)
         {
@@ -264,7 +267,8 @@ namespace Jellyfin.Networking.Tests
             };
 
             NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11";
-            using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
             NetworkManager.MockNetworkSettings = string.Empty;
 
             if (nm.TryParseInterface(result, out IReadOnlyList<IPData>? resultObj) && resultObj is not null)
@@ -293,7 +297,9 @@ namespace Jellyfin.Networking.Tests
                 RemoteIPFilter = addresses.Split(','),
                 IsRemoteIPFilterBlacklist = false
             };
-            using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+
+            var startupConf = new Mock<IConfiguration>();
+            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
 
             Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied);
         }
@@ -314,7 +320,8 @@ namespace Jellyfin.Networking.Tests
                 IsRemoteIPFilterBlacklist = true
             };
 
-            using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
 
             Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIP)), denied);
         }
@@ -334,7 +341,8 @@ namespace Jellyfin.Networking.Tests
             };
 
             NetworkManager.MockNetworkSettings = interfaces;
-            using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
 
             var interfaceToUse = nm.GetBindAddress(string.Empty, out _);
 
@@ -358,7 +366,8 @@ namespace Jellyfin.Networking.Tests
             };
 
             NetworkManager.MockNetworkSettings = interfaces;
-            using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
 
             var interfaceToUse = nm.GetBindAddress(source, out _);
 

+ 3 - 2
tests/Jellyfin.Server.Tests/ParseNetworkTests.cs

@@ -7,6 +7,7 @@ using Jellyfin.Server.Extensions;
 using MediaBrowser.Common.Configuration;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
 using Xunit;
@@ -119,8 +120,8 @@ namespace Jellyfin.Server.Tests
                 EnableIPv6 = true,
                 EnableIPv4 = true,
             };
-
-            return new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
+            var startupConf = new Mock<IConfiguration>();
+            return new NetworkManager(GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>());
         }
     }
 }