Browse Source

Use file-scoped namespaces in Jellyfin.Networking

Patrick Barron 1 year ago
parent
commit
eea676429b

+ 134 - 135
src/Jellyfin.Networking/ExternalPortForwarding.cs

@@ -16,181 +16,180 @@ using MediaBrowser.Controller.Plugins;
 using Microsoft.Extensions.Logging;
 using Mono.Nat;
 
-namespace Jellyfin.Networking
+namespace Jellyfin.Networking;
+
+/// <summary>
+/// Server entrypoint handling external port forwarding.
+/// </summary>
+public sealed class ExternalPortForwarding : IServerEntryPoint
 {
+    private readonly IServerApplicationHost _appHost;
+    private readonly ILogger<ExternalPortForwarding> _logger;
+    private readonly IServerConfigurationManager _config;
+
+    private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
+
+    private Timer _timer;
+    private string _configIdentifier;
+
+    private bool _disposed;
+
     /// <summary>
-    /// Server entrypoint handling external port forwarding.
+    /// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class.
     /// </summary>
-    public sealed class ExternalPortForwarding : IServerEntryPoint
+    /// <param name="logger">The logger.</param>
+    /// <param name="appHost">The application host.</param>
+    /// <param name="config">The configuration manager.</param>
+    public ExternalPortForwarding(
+        ILogger<ExternalPortForwarding> logger,
+        IServerApplicationHost appHost,
+        IServerConfigurationManager config)
     {
-        private readonly IServerApplicationHost _appHost;
-        private readonly ILogger<ExternalPortForwarding> _logger;
-        private readonly IServerConfigurationManager _config;
-
-        private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
-
-        private Timer _timer;
-        private string _configIdentifier;
-
-        private bool _disposed;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="appHost">The application host.</param>
-        /// <param name="config">The configuration manager.</param>
-        public ExternalPortForwarding(
-            ILogger<ExternalPortForwarding> logger,
-            IServerApplicationHost appHost,
-            IServerConfigurationManager config)
-        {
-            _logger = logger;
-            _appHost = appHost;
-            _config = config;
-        }
+        _logger = logger;
+        _appHost = appHost;
+        _config = config;
+    }
 
-        private string GetConfigIdentifier()
-        {
-            const char Separator = '|';
-            var config = _config.GetNetworkConfiguration();
-
-            return new StringBuilder(32)
-                .Append(config.EnableUPnP).Append(Separator)
-                .Append(config.PublicHttpPort).Append(Separator)
-                .Append(config.PublicHttpsPort).Append(Separator)
-                .Append(_appHost.HttpPort).Append(Separator)
-                .Append(_appHost.HttpsPort).Append(Separator)
-                .Append(_appHost.ListenWithHttps).Append(Separator)
-                .Append(config.EnableRemoteAccess).Append(Separator)
-                .ToString();
-        }
+    private string GetConfigIdentifier()
+    {
+        const char Separator = '|';
+        var config = _config.GetNetworkConfiguration();
+
+        return new StringBuilder(32)
+            .Append(config.EnableUPnP).Append(Separator)
+            .Append(config.PublicHttpPort).Append(Separator)
+            .Append(config.PublicHttpsPort).Append(Separator)
+            .Append(_appHost.HttpPort).Append(Separator)
+            .Append(_appHost.HttpsPort).Append(Separator)
+            .Append(_appHost.ListenWithHttps).Append(Separator)
+            .Append(config.EnableRemoteAccess).Append(Separator)
+            .ToString();
+    }
 
-        private void OnConfigurationUpdated(object sender, EventArgs e)
-        {
-            var oldConfigIdentifier = _configIdentifier;
-            _configIdentifier = GetConfigIdentifier();
-
-            if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
-            {
-                Stop();
-                Start();
-            }
-        }
+    private void OnConfigurationUpdated(object sender, EventArgs e)
+    {
+        var oldConfigIdentifier = _configIdentifier;
+        _configIdentifier = GetConfigIdentifier();
 
-        /// <inheritdoc />
-        public Task RunAsync()
+        if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
         {
+            Stop();
             Start();
+        }
+    }
 
-            _config.ConfigurationUpdated += OnConfigurationUpdated;
+    /// <inheritdoc />
+    public Task RunAsync()
+    {
+        Start();
 
-            return Task.CompletedTask;
-        }
+        _config.ConfigurationUpdated += OnConfigurationUpdated;
 
-        private void Start()
+        return Task.CompletedTask;
+    }
+
+    private void Start()
+    {
+        var config = _config.GetNetworkConfiguration();
+        if (!config.EnableUPnP || !config.EnableRemoteAccess)
         {
-            var config = _config.GetNetworkConfiguration();
-            if (!config.EnableUPnP || !config.EnableRemoteAccess)
-            {
-                return;
-            }
+            return;
+        }
 
-            _logger.LogInformation("Starting NAT discovery");
+        _logger.LogInformation("Starting NAT discovery");
 
-            NatUtility.DeviceFound += OnNatUtilityDeviceFound;
-            NatUtility.StartDiscovery();
+        NatUtility.DeviceFound += OnNatUtilityDeviceFound;
+        NatUtility.StartDiscovery();
 
-            _timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
-        }
+        _timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
+    }
 
-        private void Stop()
-        {
-            _logger.LogInformation("Stopping NAT discovery");
+    private void Stop()
+    {
+        _logger.LogInformation("Stopping NAT discovery");
 
-            NatUtility.StopDiscovery();
-            NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
+        NatUtility.StopDiscovery();
+        NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
 
-            _timer?.Dispose();
-        }
+        _timer?.Dispose();
+    }
 
-        private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
+    private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
+    {
+        try
         {
-            try
-            {
-                await CreateRules(e.Device).ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error creating port forwarding rules");
-            }
+            await CreateRules(e.Device).ConfigureAwait(false);
         }
-
-        private Task CreateRules(INatDevice device)
+        catch (Exception ex)
         {
-            ObjectDisposedException.ThrowIf(_disposed, this);
+            _logger.LogError(ex, "Error creating port forwarding rules");
+        }
+    }
 
-            // On some systems the device discovered event seems to fire repeatedly
-            // This check will help ensure we're not trying to port map the same device over and over
-            if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
-            {
-                return Task.CompletedTask;
-            }
+    private Task CreateRules(INatDevice device)
+    {
+        ObjectDisposedException.ThrowIf(_disposed, this);
 
-            return Task.WhenAll(CreatePortMaps(device));
+        // On some systems the device discovered event seems to fire repeatedly
+        // This check will help ensure we're not trying to port map the same device over and over
+        if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
+        {
+            return Task.CompletedTask;
         }
 
-        private IEnumerable<Task> CreatePortMaps(INatDevice device)
-        {
-            var config = _config.GetNetworkConfiguration();
-            yield return CreatePortMap(device, _appHost.HttpPort, config.PublicHttpPort);
+        return Task.WhenAll(CreatePortMaps(device));
+    }
 
-            if (_appHost.ListenWithHttps)
-            {
-                yield return CreatePortMap(device, _appHost.HttpsPort, config.PublicHttpsPort);
-            }
+    private IEnumerable<Task> CreatePortMaps(INatDevice device)
+    {
+        var config = _config.GetNetworkConfiguration();
+        yield return CreatePortMap(device, _appHost.HttpPort, config.PublicHttpPort);
+
+        if (_appHost.ListenWithHttps)
+        {
+            yield return CreatePortMap(device, _appHost.HttpsPort, config.PublicHttpsPort);
         }
+    }
 
-        private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
+    private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
+    {
+        _logger.LogDebug(
+            "Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
+            privatePort,
+            publicPort,
+            device.DeviceEndpoint);
+
+        try
+        {
+            var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
+            await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
+        }
+        catch (Exception ex)
         {
-            _logger.LogDebug(
-                "Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
+            _logger.LogError(
+                ex,
+                "Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
                 privatePort,
                 publicPort,
                 device.DeviceEndpoint);
-
-            try
-            {
-                var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
-                await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(
-                    ex,
-                    "Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
-                    privatePort,
-                    publicPort,
-                    device.DeviceEndpoint);
-            }
         }
+    }
 
-        /// <inheritdoc />
-        public void Dispose()
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (_disposed)
         {
-            if (_disposed)
-            {
-                return;
-            }
+            return;
+        }
 
-            _config.ConfigurationUpdated -= OnConfigurationUpdated;
+        _config.ConfigurationUpdated -= OnConfigurationUpdated;
 
-            Stop();
+        Stop();
 
-            _timer?.Dispose();
-            _timer = null;
+        _timer?.Dispose();
+        _timer = null;
 
-            _disposed = true;
-        }
+        _disposed = true;
     }
 }

+ 65 - 66
src/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs

@@ -30,91 +30,90 @@ using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace Jellyfin.Networking.HappyEyeballs
+namespace Jellyfin.Networking.HappyEyeballs;
+
+/// <summary>
+/// Defines the <see cref="HttpClientExtension"/> class.
+///
+/// Implementation taken from https://github.com/ppy/osu-framework/pull/4191 .
+/// </summary>
+public static class HttpClientExtension
 {
     /// <summary>
-    /// Defines the <see cref="HttpClientExtension"/> class.
-    ///
-    /// Implementation taken from https://github.com/ppy/osu-framework/pull/4191 .
+    /// Gets or sets a value indicating whether the client should use IPv6.
     /// </summary>
-    public static class HttpClientExtension
+    public static bool UseIPv6 { get; set; } = true;
+
+    /// <summary>
+    /// Implements the httpclient callback method.
+    /// </summary>
+    /// <param name="context">The <see cref="SocketsHttpConnectionContext"/> instance.</param>
+    /// <param name="cancellationToken">The <see cref="CancellationToken"/> instance.</param>
+    /// <returns>The http steam.</returns>
+    public static async ValueTask<Stream> OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
     {
-        /// <summary>
-        /// Gets or sets a value indicating whether the client should use IPv6.
-        /// </summary>
-        public static bool UseIPv6 { get; set; } = true;
-
-        /// <summary>
-        /// Implements the httpclient callback method.
-        /// </summary>
-        /// <param name="context">The <see cref="SocketsHttpConnectionContext"/> instance.</param>
-        /// <param name="cancellationToken">The <see cref="CancellationToken"/> instance.</param>
-        /// <returns>The http steam.</returns>
-        public static async ValueTask<Stream> OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
+        if (!UseIPv6)
         {
-            if (!UseIPv6)
-            {
-                return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false);
-            }
+            return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false);
+        }
 
-            using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-            var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token);
+        using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+        var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token);
 
-            // GetAwaiter().GetResult() is used instead of .Result as this results in improved exception handling.
-            // The tasks have already been completed.
-            // See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details.
-            if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully)
+        // GetAwaiter().GetResult() is used instead of .Result as this results in improved exception handling.
+        // The tasks have already been completed.
+        // See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details.
+        if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully)
+        {
+            await cancelIPv6.CancelAsync().ConfigureAwait(false);
+            return tryConnectAsyncIPv6.GetAwaiter().GetResult();
+        }
+
+        using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+        var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token);
+
+        if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6)
+        {
+            if (tryConnectAsyncIPv6.IsCompletedSuccessfully)
             {
-                await cancelIPv6.CancelAsync().ConfigureAwait(false);
+                await cancelIPv4.CancelAsync().ConfigureAwait(false);
                 return tryConnectAsyncIPv6.GetAwaiter().GetResult();
             }
 
-            using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
-            var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token);
-
-            if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6)
+            return tryConnectAsyncIPv4.GetAwaiter().GetResult();
+        }
+        else
+        {
+            if (tryConnectAsyncIPv4.IsCompletedSuccessfully)
             {
-                if (tryConnectAsyncIPv6.IsCompletedSuccessfully)
-                {
-                    await cancelIPv4.CancelAsync().ConfigureAwait(false);
-                    return tryConnectAsyncIPv6.GetAwaiter().GetResult();
-                }
-
+                await cancelIPv6.CancelAsync().ConfigureAwait(false);
                 return tryConnectAsyncIPv4.GetAwaiter().GetResult();
             }
-            else
-            {
-                if (tryConnectAsyncIPv4.IsCompletedSuccessfully)
-                {
-                    await cancelIPv6.CancelAsync().ConfigureAwait(false);
-                    return tryConnectAsyncIPv4.GetAwaiter().GetResult();
-                }
 
-                return tryConnectAsyncIPv6.GetAwaiter().GetResult();
-            }
+            return tryConnectAsyncIPv6.GetAwaiter().GetResult();
         }
+    }
 
-        private static async Task<Stream> AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
+    private static async Task<Stream> AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken)
+    {
+        // The following socket constructor will create a dual-mode socket on systems where IPV6 is available.
+        var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)
         {
-            // The following socket constructor will create a dual-mode socket on systems where IPV6 is available.
-            var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)
-            {
-                // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
-                NoDelay = true
-            };
+            // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
+            NoDelay = true
+        };
 
-            try
-            {
-                await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
-                // The stream should take the ownership of the underlying socket,
-                // closing it when it's disposed.
-                return new NetworkStream(socket, ownsSocket: true);
-            }
-            catch
-            {
-                socket.Dispose();
-                throw;
-            }
+        try
+        {
+            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
+            // The stream should take the ownership of the underlying socket,
+            // closing it when it's disposed.
+            return new NetworkStream(socket, ownsSocket: true);
+        }
+        catch
+        {
+            socket.Dispose();
+            throw;
         }
     }
 }

+ 845 - 846
src/Jellyfin.Networking/Manager/NetworkManager.cs

@@ -18,1109 +18,1108 @@ using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 
-namespace Jellyfin.Networking.Manager
+namespace Jellyfin.Networking.Manager;
+
+/// <summary>
+/// Class to take care of network interface management.
+/// </summary>
+public class NetworkManager : INetworkManager, IDisposable
 {
     /// <summary>
-    /// Class to take care of network interface management.
+    /// Threading lock for network properties.
     /// </summary>
-    public class NetworkManager : INetworkManager, IDisposable
-    {
-        /// <summary>
-        /// Threading lock for network properties.
-        /// </summary>
-        private readonly object _initLock;
-
-        private readonly ILogger<NetworkManager> _logger;
-
-        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 IReadOnlyList<PublishedServerUriOverride> _publishedServerUrls;
-
-        private IReadOnlyList<IPNetwork> _remoteAddressFilter;
-
-        /// <summary>
-        /// Used to stop "event-racing conditions".
-        /// </summary>
-        private bool _eventfire;
-
-        /// <summary>
-        /// List of all interface MAC addresses.
-        /// </summary>
-        private IReadOnlyList<PhysicalAddress> _macAddresses;
-
-        /// <summary>
-        /// Dictionary containing interface addresses and their subnets.
-        /// </summary>
-        private IReadOnlyList<IPData> _interfaces;
-
-        /// <summary>
-        /// Unfiltered user defined LAN subnets (<see cref="NetworkConfiguration.LocalNetworkSubnets"/>)
-        /// or internal interface network subnets if undefined by user.
-        /// </summary>
-        private IReadOnlyList<IPNetwork> _lanSubnets;
-
-        /// <summary>
-        /// User defined list of subnets to excluded from the LAN.
-        /// </summary>
-        private IReadOnlyList<IPNetwork> _excludedSubnets;
-
-        /// <summary>
-        /// True if this object is disposed.
-        /// </summary>
-        private bool _disposed;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="NetworkManager"/> class.
-        /// </summary>
-        /// <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>
+    private readonly object _initLock;
+
+    private readonly ILogger<NetworkManager> _logger;
+
+    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 IReadOnlyList<PublishedServerUriOverride> _publishedServerUrls;
+
+    private IReadOnlyList<IPNetwork> _remoteAddressFilter;
+
+    /// <summary>
+    /// Used to stop "event-racing conditions".
+    /// </summary>
+    private bool _eventfire;
+
+    /// <summary>
+    /// List of all interface MAC addresses.
+    /// </summary>
+    private IReadOnlyList<PhysicalAddress> _macAddresses;
+
+    /// <summary>
+    /// Dictionary containing interface addresses and their subnets.
+    /// </summary>
+    private IReadOnlyList<IPData> _interfaces;
+
+    /// <summary>
+    /// Unfiltered user defined LAN subnets (<see cref="NetworkConfiguration.LocalNetworkSubnets"/>)
+    /// or internal interface network subnets if undefined by user.
+    /// </summary>
+    private IReadOnlyList<IPNetwork> _lanSubnets;
+
+    /// <summary>
+    /// User defined list of subnets to excluded from the LAN.
+    /// </summary>
+    private IReadOnlyList<IPNetwork> _excludedSubnets;
+
+    /// <summary>
+    /// True if this object is disposed.
+    /// </summary>
+    private bool _disposed;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="NetworkManager"/> class.
+    /// </summary>
+    /// <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, IConfiguration startupConfig, ILogger<NetworkManager> logger)
-        {
-            ArgumentNullException.ThrowIfNull(logger);
-            ArgumentNullException.ThrowIfNull(configurationManager);
+    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 List<PublishedServerUriOverride>();
-            _networkEventLock = new object();
-            _remoteAddressFilter = new List<IPNetwork>();
+        _logger = logger;
+        _configurationManager = configurationManager;
+        _startupConfig = startupConfig;
+        _initLock = new();
+        _interfaces = new List<IPData>();
+        _macAddresses = new List<PhysicalAddress>();
+        _publishedServerUrls = new List<PublishedServerUriOverride>();
+        _networkEventLock = new object();
+        _remoteAddressFilter = new List<IPNetwork>();
 
-            UpdateSettings(_configurationManager.GetNetworkConfiguration());
+        UpdateSettings(_configurationManager.GetNetworkConfiguration());
 
-            NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
-            NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
+        NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
+        NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
 
-            _configurationManager.NamedConfigurationUpdated += ConfigurationUpdated;
-        }
+        _configurationManager.NamedConfigurationUpdated += ConfigurationUpdated;
+    }
 #pragma warning restore CS8618 // Non-nullable field is uninitialized.
 
-        /// <summary>
-        /// Event triggered on network changes.
-        /// </summary>
-        public event EventHandler? NetworkChanged;
-
-        /// <summary>
-        /// Gets or sets a value indicating whether testing is taking place.
-        /// </summary>
-        public static string MockNetworkSettings { get; set; } = string.Empty;
-
-        /// <summary>
-        /// Gets a value indicating whether IP4 is enabled.
-        /// </summary>
-        public bool IsIPv4Enabled => _configurationManager.GetNetworkConfiguration().EnableIPv4;
-
-        /// <summary>
-        /// Gets a value indicating whether IP6 is enabled.
-        /// </summary>
-        public bool IsIPv6Enabled => _configurationManager.GetNetworkConfiguration().EnableIPv6;
-
-        /// <summary>
-        /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
-        /// </summary>
-        public bool TrustAllIPv6Interfaces { get; private set; }
-
-        /// <summary>
-        /// Gets the Published server override list.
-        /// </summary>
-        public IReadOnlyList<PublishedServerUriOverride> PublishedServerUrls => _publishedServerUrls;
-
-        /// <inheritdoc/>
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
+    /// <summary>
+    /// Event triggered on network changes.
+    /// </summary>
+    public event EventHandler? NetworkChanged;
 
-        /// <summary>
-        /// Handler for network change events.
-        /// </summary>
-        /// <param name="sender">Sender.</param>
-        /// <param name="e">A <see cref="NetworkAvailabilityEventArgs"/> containing network availability information.</param>
-        private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs e)
-        {
-            _logger.LogDebug("Network availability changed.");
-            HandleNetworkChange();
-        }
+    /// <summary>
+    /// Gets or sets a value indicating whether testing is taking place.
+    /// </summary>
+    public static string MockNetworkSettings { get; set; } = string.Empty;
 
-        /// <summary>
-        /// Handler for network change events.
-        /// </summary>
-        /// <param name="sender">Sender.</param>
-        /// <param name="e">An <see cref="EventArgs"/>.</param>
-        private void OnNetworkAddressChanged(object? sender, EventArgs e)
-        {
-            _logger.LogDebug("Network address change detected.");
-            HandleNetworkChange();
-        }
+    /// <summary>
+    /// Gets a value indicating whether IP4 is enabled.
+    /// </summary>
+    public bool IsIPv4Enabled => _configurationManager.GetNetworkConfiguration().EnableIPv4;
+
+    /// <summary>
+    /// Gets a value indicating whether IP6 is enabled.
+    /// </summary>
+    public bool IsIPv6Enabled => _configurationManager.GetNetworkConfiguration().EnableIPv6;
 
-        /// <summary>
-        /// Triggers our event, and re-loads interface information.
-        /// </summary>
-        private void HandleNetworkChange()
+    /// <summary>
+    /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
+    /// </summary>
+    public bool TrustAllIPv6Interfaces { get; private set; }
+
+    /// <summary>
+    /// Gets the Published server override list.
+    /// </summary>
+    public IReadOnlyList<PublishedServerUriOverride> PublishedServerUrls => _publishedServerUrls;
+
+    /// <inheritdoc/>
+    public void Dispose()
+    {
+        Dispose(true);
+        GC.SuppressFinalize(this);
+    }
+
+    /// <summary>
+    /// Handler for network change events.
+    /// </summary>
+    /// <param name="sender">Sender.</param>
+    /// <param name="e">A <see cref="NetworkAvailabilityEventArgs"/> containing network availability information.</param>
+    private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs e)
+    {
+        _logger.LogDebug("Network availability changed.");
+        HandleNetworkChange();
+    }
+
+    /// <summary>
+    /// Handler for network change events.
+    /// </summary>
+    /// <param name="sender">Sender.</param>
+    /// <param name="e">An <see cref="EventArgs"/>.</param>
+    private void OnNetworkAddressChanged(object? sender, EventArgs e)
+    {
+        _logger.LogDebug("Network address change detected.");
+        HandleNetworkChange();
+    }
+
+    /// <summary>
+    /// Triggers our event, and re-loads interface information.
+    /// </summary>
+    private void HandleNetworkChange()
+    {
+        lock (_networkEventLock)
         {
-            lock (_networkEventLock)
+            if (!_eventfire)
             {
-                if (!_eventfire)
-                {
-                    // As network events tend to fire one after the other only fire once every second.
-                    _eventfire = true;
-                    OnNetworkChange();
-                }
+                // As network events tend to fire one after the other only fire once every second.
+                _eventfire = true;
+                OnNetworkChange();
             }
         }
+    }
 
-        /// <summary>
-        /// Waits for 2 seconds before re-initialising the settings, as typically these events fire multiple times in succession.
-        /// </summary>
-        private void OnNetworkChange()
+    /// <summary>
+    /// Waits for 2 seconds before re-initialising the settings, as typically these events fire multiple times in succession.
+    /// </summary>
+    private void OnNetworkChange()
+    {
+        try
         {
-            try
+            Thread.Sleep(2000);
+            var networkConfig = _configurationManager.GetNetworkConfiguration();
+            if (IsIPv6Enabled && !Socket.OSSupportsIPv6)
             {
-                Thread.Sleep(2000);
-                var networkConfig = _configurationManager.GetNetworkConfiguration();
-                if (IsIPv6Enabled && !Socket.OSSupportsIPv6)
-                {
-                    UpdateSettings(networkConfig);
-                }
-                else
-                {
-                    InitializeInterfaces();
-                    InitializeLan(networkConfig);
-                    EnforceBindSettings(networkConfig);
-                }
-
-                PrintNetworkInformation(networkConfig);
-                NetworkChanged?.Invoke(this, EventArgs.Empty);
+                UpdateSettings(networkConfig);
             }
-            finally
+            else
             {
-                _eventfire = false;
+                InitializeInterfaces();
+                InitializeLan(networkConfig);
+                EnforceBindSettings(networkConfig);
             }
+
+            PrintNetworkInformation(networkConfig);
+            NetworkChanged?.Invoke(this, EventArgs.Empty);
+        }
+        finally
+        {
+            _eventfire = false;
         }
+    }
 
-        /// <summary>
-        /// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
-        /// Generate a list of all active mac addresses that aren't loopback addresses.
-        /// </summary>
-        private void InitializeInterfaces()
+    /// <summary>
+    /// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
+    /// Generate a list of all active mac addresses that aren't loopback addresses.
+    /// </summary>
+    private void InitializeInterfaces()
+    {
+        lock (_initLock)
         {
-            lock (_initLock)
-            {
-                _logger.LogDebug("Refreshing interfaces.");
+            _logger.LogDebug("Refreshing interfaces.");
 
-                var interfaces = new List<IPData>();
-                var macAddresses = new List<PhysicalAddress>();
+            var interfaces = new List<IPData>();
+            var macAddresses = new List<PhysicalAddress>();
 
-                try
-                {
-                    var nics = NetworkInterface.GetAllNetworkInterfaces()
-                        .Where(i => i.OperationalStatus == OperationalStatus.Up);
+            try
+            {
+                var nics = NetworkInterface.GetAllNetworkInterfaces()
+                    .Where(i => i.OperationalStatus == OperationalStatus.Up);
 
-                    foreach (NetworkInterface adapter in nics)
+                foreach (NetworkInterface adapter in nics)
+                {
+                    try
                     {
-                        try
+                        var ipProperties = adapter.GetIPProperties();
+                        var mac = adapter.GetPhysicalAddress();
+
+                        // Populate MAC list
+                        if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && PhysicalAddress.None.Equals(mac))
                         {
-                            var ipProperties = adapter.GetIPProperties();
-                            var mac = adapter.GetPhysicalAddress();
+                            macAddresses.Add(mac);
+                        }
 
-                            // Populate MAC list
-                            if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && PhysicalAddress.None.Equals(mac))
+                        // Populate interface list
+                        foreach (var info in ipProperties.UnicastAddresses)
+                        {
+                            if (IsIPv4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
                             {
-                                macAddresses.Add(mac);
-                            }
+                                var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), adapter.Name)
+                                {
+                                    Index = ipProperties.GetIPv4Properties().Index,
+                                    Name = adapter.Name,
+                                    SupportsMulticast = adapter.SupportsMulticast
+                                };
 
-                            // Populate interface list
-                            foreach (var info in ipProperties.UnicastAddresses)
+                                interfaces.Add(interfaceObject);
+                            }
+                            else if (IsIPv6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
                             {
-                                if (IsIPv4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
+                                var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength), 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)
-                                    {
-                                        Index = ipProperties.GetIPv6Properties().Index,
-                                        Name = adapter.Name,
-                                        SupportsMulticast = adapter.SupportsMulticast
-                                    };
-
-                                    interfaces.Add(interfaceObject);
-                                }
+                                    Index = ipProperties.GetIPv6Properties().Index,
+                                    Name = adapter.Name,
+                                    SupportsMulticast = adapter.SupportsMulticast
+                                };
+
+                                interfaces.Add(interfaceObject);
                             }
                         }
-                        catch (Exception ex)
-                        {
-                            // Ignore error, and attempt to continue.
-                            _logger.LogError(ex, "Error encountered parsing interfaces.");
-                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        // Ignore error, and attempt to continue.
+                        _logger.LogError(ex, "Error encountered parsing interfaces.");
                     }
                 }
-                catch (Exception ex)
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error obtaining interfaces.");
+            }
+
+            // If no interfaces are found, fallback to loopback interfaces.
+            if (interfaces.Count == 0)
+            {
+                _logger.LogWarning("No interface information available. Using loopback interface(s).");
+
+                if (IsIPv4Enabled)
                 {
-                    _logger.LogError(ex, "Error obtaining interfaces.");
+                    interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
                 }
 
-                // If no interfaces are found, fallback to loopback interfaces.
-                if (interfaces.Count == 0)
+                if (IsIPv6Enabled)
                 {
-                    _logger.LogWarning("No interface information available. Using loopback interface(s).");
-
-                    if (IsIPv4Enabled)
-                    {
-                        interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
-                    }
-
-                    if (IsIPv6Enabled)
-                    {
-                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
-                    }
+                    interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
                 }
+            }
 
-                _logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
-                _logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
+            _logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
+            _logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
 
-                _macAddresses = macAddresses;
-                _interfaces = interfaces;
-            }
+            _macAddresses = macAddresses;
+            _interfaces = interfaces;
         }
+    }
 
-        /// <summary>
-        /// Initializes internal LAN cache.
-        /// </summary>
-        private void InitializeLan(NetworkConfiguration config)
+    /// <summary>
+    /// Initializes internal LAN cache.
+    /// </summary>
+    private void InitializeLan(NetworkConfiguration config)
+    {
+        lock (_initLock)
         {
-            lock (_initLock)
-            {
-                _logger.LogDebug("Refreshing LAN information.");
-
-                // Get configuration options
-                var subnets = config.LocalNetworkSubnets;
+            _logger.LogDebug("Refreshing LAN information.");
 
-                // If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
-                if (!NetworkUtils.TryParseToSubnets(subnets, out var lanSubnets, false) || lanSubnets.Count == 0)
-                {
-                    _logger.LogDebug("Using LAN interface addresses as user provided no LAN details.");
+            // Get configuration options
+            var subnets = config.LocalNetworkSubnets;
 
-                    var fallbackLanSubnets = new List<IPNetwork>();
-                    if (IsIPv6Enabled)
-                    {
-                        fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291Loopback); // RFC 4291 (Loopback)
-                        fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291SiteLocal); // RFC 4291 (Site local)
-                        fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4193UniqueLocal); // RFC 4193 (Unique local)
-                    }
-
-                    if (IsIPv4Enabled)
-                    {
-                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC5735Loopback); // RFC 5735 (Loopback)
-                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassA); // RFC 1918 (private Class A)
-                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassB); // RFC 1918 (private Class B)
-                        fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassC); // RFC 1918 (private Class C)
-                    }
+            // If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
+            if (!NetworkUtils.TryParseToSubnets(subnets, out var lanSubnets, false) || lanSubnets.Count == 0)
+            {
+                _logger.LogDebug("Using LAN interface addresses as user provided no LAN details.");
 
-                    _lanSubnets = fallbackLanSubnets;
+                var fallbackLanSubnets = new List<IPNetwork>();
+                if (IsIPv6Enabled)
+                {
+                    fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291Loopback); // RFC 4291 (Loopback)
+                    fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291SiteLocal); // RFC 4291 (Site local)
+                    fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4193UniqueLocal); // RFC 4193 (Unique local)
                 }
-                else
+
+                if (IsIPv4Enabled)
                 {
-                    _lanSubnets = lanSubnets;
+                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC5735Loopback); // RFC 5735 (Loopback)
+                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassA); // RFC 1918 (private Class A)
+                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassB); // RFC 1918 (private Class B)
+                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassC); // RFC 1918 (private Class C)
                 }
 
-                _excludedSubnets = NetworkUtils.TryParseToSubnets(subnets, out var excludedSubnets, true)
-                    ? excludedSubnets
-                    : new List<IPNetwork>();
+                _lanSubnets = fallbackLanSubnets;
+            }
+            else
+            {
+                _lanSubnets = lanSubnets;
             }
+
+            _excludedSubnets = NetworkUtils.TryParseToSubnets(subnets, out var excludedSubnets, true)
+                ? excludedSubnets
+                : new List<IPNetwork>();
         }
+    }
 
-        /// <summary>
-        /// Enforce bind addresses and exclusions on available interfaces.
-        /// </summary>
-        private void EnforceBindSettings(NetworkConfiguration config)
+    /// <summary>
+    /// Enforce bind addresses and exclusions on available interfaces.
+    /// </summary>
+    private void EnforceBindSettings(NetworkConfiguration config)
+    {
+        lock (_initLock)
         {
-            lock (_initLock)
+            // Respect explicit bind addresses
+            var interfaces = _interfaces.ToList();
+            var localNetworkAddresses = config.LocalNetworkAddresses;
+            if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
             {
-                // Respect explicit bind addresses
-                var interfaces = _interfaces.ToList();
-                var localNetworkAddresses = config.LocalNetworkAddresses;
-                if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
-                {
-                    var bindAddresses = localNetworkAddresses.Select(p => NetworkUtils.TryParseToSubnet(p, out var network)
+                var bindAddresses = localNetworkAddresses.Select(p => NetworkUtils.TryParseToSubnet(p, out var network)
                         ? network.Prefix
                         : (interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
                             .Select(x => x.Address)
                             .FirstOrDefault() ?? IPAddress.None))
-                        .Where(x => x != IPAddress.None)
-                        .ToHashSet();
-                    interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
-
-                    if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
-                    {
-                        interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
-                    }
-
-                    if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
-                    {
-                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
-                    }
-                }
+                    .Where(x => x != IPAddress.None)
+                    .ToHashSet();
+                interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
 
-                // Remove all interfaces matching any virtual machine interface prefix
-                if (config.IgnoreVirtualInterfaces)
+                if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback)))
                 {
-                    // Remove potentially existing * and split config string into prefixes
-                    var virtualInterfacePrefixes = config.VirtualInterfaceNames
-                        .Select(i => i.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase));
-
-                    // Check all interfaces for matches against the prefixes and remove them
-                    if (_interfaces.Count > 0)
-                    {
-                        foreach (var virtualInterfacePrefix in virtualInterfacePrefixes)
-                        {
-                            interfaces.RemoveAll(x => x.Name.StartsWith(virtualInterfacePrefix, StringComparison.OrdinalIgnoreCase));
-                        }
-                    }
+                    interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
                 }
 
-                // Remove all IPv4 interfaces if IPv4 is disabled
-                if (!IsIPv4Enabled)
+                if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Loopback)))
                 {
-                    interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetwork);
+                    interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
                 }
+            }
+
+            // Remove all interfaces matching any virtual machine interface prefix
+            if (config.IgnoreVirtualInterfaces)
+            {
+                // Remove potentially existing * and split config string into prefixes
+                var virtualInterfacePrefixes = config.VirtualInterfaceNames
+                    .Select(i => i.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase));
 
-                // Remove all IPv6 interfaces if IPv6 is disabled
-                if (!IsIPv6Enabled)
+                // Check all interfaces for matches against the prefixes and remove them
+                if (_interfaces.Count > 0)
                 {
-                    interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
+                    foreach (var virtualInterfacePrefix in virtualInterfacePrefixes)
+                    {
+                        interfaces.RemoveAll(x => x.Name.StartsWith(virtualInterfacePrefix, StringComparison.OrdinalIgnoreCase));
+                    }
                 }
+            }
 
-                _interfaces = interfaces;
+            // Remove all IPv4 interfaces if IPv4 is disabled
+            if (!IsIPv4Enabled)
+            {
+                interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetwork);
             }
+
+            // Remove all IPv6 interfaces if IPv6 is disabled
+            if (!IsIPv6Enabled)
+            {
+                interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
+            }
+
+            _interfaces = interfaces;
         }
+    }
 
-        /// <summary>
-        /// Initializes the remote address values.
-        /// </summary>
-        private void InitializeRemote(NetworkConfiguration config)
+    /// <summary>
+    /// Initializes the remote address values.
+    /// </summary>
+    private void InitializeRemote(NetworkConfiguration config)
+    {
+        lock (_initLock)
         {
-            lock (_initLock)
+            // Parse config values into filter collection
+            var remoteIPFilter = config.RemoteIPFilter;
+            if (remoteIPFilter.Length != 0 && !string.IsNullOrWhiteSpace(remoteIPFilter[0]))
             {
-                // Parse config values into filter collection
-                var remoteIPFilter = config.RemoteIPFilter;
-                if (remoteIPFilter.Length != 0 && !string.IsNullOrWhiteSpace(remoteIPFilter[0]))
+                // Parse all IPs with netmask to a subnet
+                var remoteAddressFilter = new List<IPNetwork>();
+                var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray();
+                if (NetworkUtils.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
                 {
-                    // Parse all IPs with netmask to a subnet
-                    var remoteAddressFilter = new List<IPNetwork>();
-                    var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray();
-                    if (NetworkUtils.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
-                    {
-                        remoteAddressFilter = remoteAddressFilterResult.ToList();
-                    }
+                    remoteAddressFilter = remoteAddressFilterResult.ToList();
+                }
 
-                    // Parse everything else as an IP and construct subnet with a single IP
-                    var remoteFilteredIPs = remoteIPFilter.Where(x => !x.Contains('/', StringComparison.OrdinalIgnoreCase));
-                    foreach (var ip in remoteFilteredIPs)
+                // Parse everything else as an IP and construct subnet with a single IP
+                var remoteFilteredIPs = remoteIPFilter.Where(x => !x.Contains('/', StringComparison.OrdinalIgnoreCase));
+                foreach (var ip in remoteFilteredIPs)
+                {
+                    if (IPAddress.TryParse(ip, out var ipp))
                     {
-                        if (IPAddress.TryParse(ip, out var ipp))
-                        {
-                            remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize));
-                        }
+                        remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize));
                     }
-
-                    _remoteAddressFilter = remoteAddressFilter;
                 }
+
+                _remoteAddressFilter = remoteAddressFilter;
             }
         }
+    }
 
-        /// <summary>
-        /// Parses the user defined overrides into the dictionary object.
-        /// Overrides are the equivalent of localised publishedServerUrl, enabling
-        /// different addresses to be advertised over different subnets.
-        /// format is subnet=ipaddress|host|uri
-        /// when subnet = 0.0.0.0, any external address matches.
-        /// </summary>
-        private void InitializeOverrides(NetworkConfiguration config)
+    /// <summary>
+    /// Parses the user defined overrides into the dictionary object.
+    /// Overrides are the equivalent of localised publishedServerUrl, enabling
+    /// different addresses to be advertised over different subnets.
+    /// format is subnet=ipaddress|host|uri
+    /// when subnet = 0.0.0.0, any external address matches.
+    /// </summary>
+    private void InitializeOverrides(NetworkConfiguration config)
+    {
+        lock (_initLock)
         {
-            lock (_initLock)
+            var publishedServerUrls = new List<PublishedServerUriOverride>();
+
+            // Prefer startup configuration.
+            var startupOverrideKey = _startupConfig[AddressOverrideKey];
+            if (!string.IsNullOrEmpty(startupOverrideKey))
+            {
+                publishedServerUrls.Add(
+                    new PublishedServerUriOverride(
+                        new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
+                        startupOverrideKey,
+                        true,
+                        true));
+                publishedServerUrls.Add(
+                    new PublishedServerUriOverride(
+                        new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
+                        startupOverrideKey,
+                        true,
+                        true));
+                _publishedServerUrls = publishedServerUrls;
+                return;
+            }
+
+            var overrides = config.PublishedServerUriBySubnet;
+            foreach (var entry in overrides)
             {
-                var publishedServerUrls = new List<PublishedServerUriOverride>();
+                var parts = entry.Split('=');
+                if (parts.Length != 2)
+                {
+                    _logger.LogError("Unable to parse bind override: {Entry}", entry);
+                    return;
+                }
 
-                // Prefer startup configuration.
-                var startupOverrideKey = _startupConfig[AddressOverrideKey];
-                if (!string.IsNullOrEmpty(startupOverrideKey))
+                var replacement = parts[1].Trim();
+                var identifier = parts[0];
+                if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
                 {
+                    // Drop any other overrides in case an "all" override exists
+                    publishedServerUrls.Clear();
                     publishedServerUrls.Add(
                         new PublishedServerUriOverride(
                             new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
-                            startupOverrideKey,
+                            replacement,
                             true,
                             true));
                     publishedServerUrls.Add(
                         new PublishedServerUriOverride(
                             new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
-                            startupOverrideKey,
+                            replacement,
                             true,
                             true));
-                    _publishedServerUrls = publishedServerUrls;
-                    return;
+                    break;
                 }
-
-                var overrides = config.PublishedServerUriBySubnet;
-                foreach (var entry in overrides)
+                else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
                 {
-                    var parts = entry.Split('=');
-                    if (parts.Length != 2)
-                    {
-                        _logger.LogError("Unable to parse bind override: {Entry}", entry);
-                        return;
-                    }
-
-                    var replacement = parts[1].Trim();
-                    var identifier = parts[0];
-                    if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
+                    publishedServerUrls.Add(
+                        new PublishedServerUriOverride(
+                            new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
+                            replacement,
+                            false,
+                            true));
+                    publishedServerUrls.Add(
+                        new PublishedServerUriOverride(
+                            new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
+                            replacement,
+                            false,
+                            true));
+                }
+                else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
+                {
+                    foreach (var lan in _lanSubnets)
                     {
-                        // Drop any other overrides in case an "all" override exists
-                        publishedServerUrls.Clear();
+                        var lanPrefix = lan.Prefix;
                         publishedServerUrls.Add(
                             new PublishedServerUriOverride(
-                                new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
+                                new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
                                 replacement,
                                 true,
-                                true));
-                        publishedServerUrls.Add(
-                            new PublishedServerUriOverride(
-                                new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
-                                replacement,
-                                true,
-                                true));
-                        break;
-                    }
-                    else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
-                    {
-                        publishedServerUrls.Add(
-                            new PublishedServerUriOverride(
-                                new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
-                                replacement,
-                                false,
-                                true));
-                        publishedServerUrls.Add(
-                            new PublishedServerUriOverride(
-                                new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
-                                replacement,
-                                false,
-                                true));
+                                false));
                     }
-                    else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
-                    {
-                        foreach (var lan in _lanSubnets)
-                        {
-                            var lanPrefix = lan.Prefix;
-                            publishedServerUrls.Add(
-                                new PublishedServerUriOverride(
-                                    new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
-                                    replacement,
-                                    true,
-                                    false));
-                        }
-                    }
-                    else if (NetworkUtils.TryParseToSubnet(identifier, out var result) && result is not null)
+                }
+                else if (NetworkUtils.TryParseToSubnet(identifier, out var result) && result is not null)
+                {
+                    var data = new IPData(result.Prefix, result);
+                    publishedServerUrls.Add(
+                        new PublishedServerUriOverride(
+                            data,
+                            replacement,
+                            true,
+                            true));
+                }
+                else if (TryParseInterface(identifier, out var ifaces))
+                {
+                    foreach (var iface in ifaces)
                     {
-                        var data = new IPData(result.Prefix, result);
                         publishedServerUrls.Add(
-                            new PublishedServerUriOverride(
-                                data,
-                                replacement,
-                                true,
-                                true));
-                    }
-                    else if (TryParseInterface(identifier, out var ifaces))
-                    {
-                        foreach (var iface in ifaces)
-                        {
-                            publishedServerUrls.Add(
                             new PublishedServerUriOverride(
                                 iface,
                                 replacement,
                                 true,
                                 true));
-                        }
-                    }
-                    else
-                    {
-                        _logger.LogError("Unable to parse bind override: {Entry}", entry);
                     }
                 }
-
-                _publishedServerUrls = publishedServerUrls;
+                else
+                {
+                    _logger.LogError("Unable to parse bind override: {Entry}", entry);
+                }
             }
+
+            _publishedServerUrls = publishedServerUrls;
         }
+    }
 
-        private void ConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs evt)
-        {
-            if (evt.Key.Equals(NetworkConfigurationStore.StoreKey, StringComparison.Ordinal))
-            {
-                UpdateSettings((NetworkConfiguration)evt.NewConfiguration);
-            }
+    private void ConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs evt)
+    {
+        if (evt.Key.Equals(NetworkConfigurationStore.StoreKey, StringComparison.Ordinal))
+        {
+            UpdateSettings((NetworkConfiguration)evt.NewConfiguration);
         }
+    }
 
-        /// <summary>
-        /// Reloads all settings and re-Initializes the instance.
-        /// </summary>
-        /// <param name="configuration">The <see cref="NetworkConfiguration"/> to use.</param>
-        public void UpdateSettings(object configuration)
-        {
-            ArgumentNullException.ThrowIfNull(configuration);
+    /// <summary>
+    /// Reloads all settings and re-Initializes the instance.
+    /// </summary>
+    /// <param name="configuration">The <see cref="NetworkConfiguration"/> to use.</param>
+    public void UpdateSettings(object configuration)
+    {
+        ArgumentNullException.ThrowIfNull(configuration);
 
-            var config = (NetworkConfiguration)configuration;
-            HappyEyeballs.HttpClientExtension.UseIPv6 = config.EnableIPv6;
+        var config = (NetworkConfiguration)configuration;
+        HappyEyeballs.HttpClientExtension.UseIPv6 = config.EnableIPv6;
 
-            InitializeLan(config);
-            InitializeRemote(config);
+        InitializeLan(config);
+        InitializeRemote(config);
 
-            if (string.IsNullOrEmpty(MockNetworkSettings))
-            {
-                InitializeInterfaces();
-            }
-            else // Used in testing only.
+        if (string.IsNullOrEmpty(MockNetworkSettings))
+        {
+            InitializeInterfaces();
+        }
+        else // Used in testing only.
+        {
+            // Format is <IPAddress>,<Index>,<Name>: <next interface>. Set index to -ve to simulate a gateway.
+            var interfaceList = MockNetworkSettings.Split('|');
+            var interfaces = new List<IPData>();
+            foreach (var details in interfaceList)
             {
-                // Format is <IPAddress>,<Index>,<Name>: <next interface>. Set index to -ve to simulate a gateway.
-                var interfaceList = MockNetworkSettings.Split('|');
-                var interfaces = new List<IPData>();
-                foreach (var details in interfaceList)
+                var parts = details.Split(',');
+                if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet))
                 {
-                    var parts = details.Split(',');
-                    if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet))
+                    var address = subnet.Prefix;
+                    var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
+                    if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
                     {
-                        var address = subnet.Prefix;
-                        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])
                         {
-                            var data = new IPData(address, subnet, parts[2])
-                            {
-                                Index = index
-                            };
-                            interfaces.Add(data);
-                        }
-                    }
-                    else
-                    {
-                        _logger.LogWarning("Could not parse mock interface settings: {Part}", details);
+                            Index = index
+                        };
+                        interfaces.Add(data);
                     }
                 }
-
-                _interfaces = interfaces;
+                else
+                {
+                    _logger.LogWarning("Could not parse mock interface settings: {Part}", details);
+                }
             }
 
-            EnforceBindSettings(config);
-            InitializeOverrides(config);
-
-            PrintNetworkInformation(config, false);
+            _interfaces = interfaces;
         }
 
-        /// <summary>
-        /// Protected implementation of Dispose pattern.
-        /// </summary>
-        /// <param name="disposing"><c>True</c> to dispose the managed state.</param>
-        protected virtual void Dispose(bool disposing)
-        {
-            if (!_disposed)
-            {
-                if (disposing)
-                {
-                    _configurationManager.NamedConfigurationUpdated -= ConfigurationUpdated;
-                    NetworkChange.NetworkAddressChanged -= OnNetworkAddressChanged;
-                    NetworkChange.NetworkAvailabilityChanged -= OnNetworkAvailabilityChanged;
-                }
+        EnforceBindSettings(config);
+        InitializeOverrides(config);
 
-                _disposed = true;
-            }
-        }
+        PrintNetworkInformation(config, false);
+    }
 
-        /// <inheritdoc/>
-        public bool TryParseInterface(string intf, [NotNullWhen(true)] out IReadOnlyList<IPData>? result)
+    /// <summary>
+    /// Protected implementation of Dispose pattern.
+    /// </summary>
+    /// <param name="disposing"><c>True</c> to dispose the managed state.</param>
+    protected virtual void Dispose(bool disposing)
+    {
+        if (!_disposed)
         {
-            if (string.IsNullOrEmpty(intf)
-                || _interfaces is null
-                || _interfaces.Count == 0)
+            if (disposing)
             {
-                result = null;
-                return false;
+                _configurationManager.NamedConfigurationUpdated -= ConfigurationUpdated;
+                NetworkChange.NetworkAddressChanged -= OnNetworkAddressChanged;
+                NetworkChange.NetworkAvailabilityChanged -= OnNetworkAvailabilityChanged;
             }
 
-            // Match all interfaces starting with names starting with token
-            result = _interfaces
-                .Where(i => i.Name.Equals(intf, StringComparison.OrdinalIgnoreCase)
-                    && ((IsIPv4Enabled && i.Address.AddressFamily == AddressFamily.InterNetwork)
-                        || (IsIPv6Enabled && i.Address.AddressFamily == AddressFamily.InterNetworkV6)))
-                .OrderBy(x => x.Index)
-                .ToArray();
-            return result.Count > 0;
+            _disposed = true;
+        }
+    }
+
+    /// <inheritdoc/>
+    public bool TryParseInterface(string intf, [NotNullWhen(true)] out IReadOnlyList<IPData>? result)
+    {
+        if (string.IsNullOrEmpty(intf)
+            || _interfaces is null
+            || _interfaces.Count == 0)
+        {
+            result = null;
+            return false;
         }
 
-        /// <inheritdoc/>
-        public bool HasRemoteAccess(IPAddress remoteIP)
+        // Match all interfaces starting with names starting with token
+        result = _interfaces
+            .Where(i => i.Name.Equals(intf, StringComparison.OrdinalIgnoreCase)
+                        && ((IsIPv4Enabled && i.Address.AddressFamily == AddressFamily.InterNetwork)
+                            || (IsIPv6Enabled && i.Address.AddressFamily == AddressFamily.InterNetworkV6)))
+            .OrderBy(x => x.Index)
+            .ToArray();
+        return result.Count > 0;
+    }
+
+    /// <inheritdoc/>
+    public bool HasRemoteAccess(IPAddress remoteIP)
+    {
+        var config = _configurationManager.GetNetworkConfiguration();
+        if (config.EnableRemoteAccess)
         {
-            var config = _configurationManager.GetNetworkConfiguration();
-            if (config.EnableRemoteAccess)
+            // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
+            // If left blank, all remote addresses will be allowed.
+            if (_remoteAddressFilter.Any() && !_lanSubnets.Any(x => x.Contains(remoteIP)))
             {
-                // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
-                // If left blank, all remote addresses will be allowed.
-                if (_remoteAddressFilter.Any() && !_lanSubnets.Any(x => x.Contains(remoteIP)))
+                // remoteAddressFilter is a whitelist or blacklist.
+                var matches = _remoteAddressFilter.Count(remoteNetwork => remoteNetwork.Contains(remoteIP));
+                if ((!config.IsRemoteIPFilterBlacklist && matches > 0)
+                    || (config.IsRemoteIPFilterBlacklist && matches == 0))
                 {
-                    // remoteAddressFilter is a whitelist or blacklist.
-                    var matches = _remoteAddressFilter.Count(remoteNetwork => remoteNetwork.Contains(remoteIP));
-                    if ((!config.IsRemoteIPFilterBlacklist && matches > 0)
-                        || (config.IsRemoteIPFilterBlacklist && matches == 0))
-                    {
-                        return true;
-                    }
-
-                    return false;
+                    return true;
                 }
-            }
-            else if (!_lanSubnets.Any(x => x.Contains(remoteIP)))
-            {
-                // Remote not enabled. So everyone should be LAN.
+
                 return false;
             }
-
-            return true;
         }
-
-        /// <inheritdoc/>
-        public IReadOnlyList<PhysicalAddress> GetMacAddresses()
+        else if (!_lanSubnets.Any(x => x.Contains(remoteIP)))
         {
-            // Populated in construction - so always has values.
-            return _macAddresses;
+            // Remote not enabled. So everyone should be LAN.
+            return false;
         }
 
-        /// <inheritdoc/>
-        public IReadOnlyList<IPData> GetLoopbacks()
-        {
-            if (!IsIPv4Enabled && !IsIPv6Enabled)
-            {
-                return Array.Empty<IPData>();
-            }
+        return true;
+    }
 
-            var loopbackNetworks = new List<IPData>();
-            if (IsIPv4Enabled)
-            {
-                loopbackNetworks.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
-            }
+    /// <inheritdoc/>
+    public IReadOnlyList<PhysicalAddress> GetMacAddresses()
+    {
+        // Populated in construction - so always has values.
+        return _macAddresses;
+    }
 
-            if (IsIPv6Enabled)
-            {
-                loopbackNetworks.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
-            }
+    /// <inheritdoc/>
+    public IReadOnlyList<IPData> GetLoopbacks()
+    {
+        if (!IsIPv4Enabled && !IsIPv6Enabled)
+        {
+            return Array.Empty<IPData>();
+        }
 
-            return loopbackNetworks;
+        var loopbackNetworks = new List<IPData>();
+        if (IsIPv4Enabled)
+        {
+            loopbackNetworks.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
         }
 
-        /// <inheritdoc/>
-        public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
+        if (IsIPv6Enabled)
         {
-            if (_interfaces.Count > 0 || individualInterfaces)
-            {
-                return _interfaces;
-            }
+            loopbackNetworks.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
+        }
 
-            // No bind address and no exclusions, so listen on all interfaces.
-            var result = new List<IPData>();
-            if (IsIPv4Enabled && IsIPv6Enabled)
-            {
-                // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
-                result.Add(new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any));
-            }
-            else if (IsIPv4Enabled)
-            {
-                result.Add(new IPData(IPAddress.Any, NetworkConstants.IPv4Any));
-            }
-            else if (IsIPv6Enabled)
-            {
-                // Cannot use IPv6Any as Kestrel will bind to IPv4 addresses too.
-                foreach (var iface in _interfaces)
-                {
-                    if (iface.AddressFamily == AddressFamily.InterNetworkV6)
-                    {
-                        result.Add(iface);
-                    }
-                }
-            }
+        return loopbackNetworks;
+    }
 
-            return result;
+    /// <inheritdoc/>
+    public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
+    {
+        if (_interfaces.Count > 0 || individualInterfaces)
+        {
+            return _interfaces;
         }
 
-        /// <inheritdoc/>
-        public string GetBindAddress(string source, out int? port)
+        // No bind address and no exclusions, so listen on all interfaces.
+        var result = new List<IPData>();
+        if (IsIPv4Enabled && IsIPv6Enabled)
+        {
+            // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
+            result.Add(new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any));
+        }
+        else if (IsIPv4Enabled)
+        {
+            result.Add(new IPData(IPAddress.Any, NetworkConstants.IPv4Any));
+        }
+        else if (IsIPv6Enabled)
         {
-            if (!NetworkUtils.TryParseHost(source, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
+            // Cannot use IPv6Any as Kestrel will bind to IPv4 addresses too.
+            foreach (var iface in _interfaces)
             {
-                addresses = Array.Empty<IPAddress>();
+                if (iface.AddressFamily == AddressFamily.InterNetworkV6)
+                {
+                    result.Add(iface);
+                }
             }
-
-            var result = GetBindAddress(addresses.FirstOrDefault(), out port);
-            return result;
         }
 
-        /// <inheritdoc/>
-        public string GetBindAddress(HttpRequest source, out int? port)
-        {
-            var result = GetBindAddress(source.Host.Host, out port);
-            port ??= source.Host.Port;
-
-            return result;
-        }
+        return result;
+    }
 
-        /// <inheritdoc/>
-        public string GetBindAddress(IPAddress? source, out int? port, bool skipOverrides = false)
+    /// <inheritdoc/>
+    public string GetBindAddress(string source, out int? port)
+    {
+        if (!NetworkUtils.TryParseHost(source, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
         {
-            port = null;
+            addresses = Array.Empty<IPAddress>();
+        }
 
-            string result;
+        var result = GetBindAddress(addresses.FirstOrDefault(), out port);
+        return result;
+    }
 
-            if (source is not null)
-            {
-                if (IsIPv4Enabled && !IsIPv6Enabled && source.AddressFamily == AddressFamily.InterNetworkV6)
-                {
-                    _logger.LogWarning("IPv6 is disabled in Jellyfin, but enabled in the OS. This may affect how the interface is selected.");
-                }
+    /// <inheritdoc/>
+    public string GetBindAddress(HttpRequest source, out int? port)
+    {
+        var result = GetBindAddress(source.Host.Host, out port);
+        port ??= source.Host.Port;
 
-                if (!IsIPv4Enabled && IsIPv6Enabled && source.AddressFamily == AddressFamily.InterNetwork)
-                {
-                    _logger.LogWarning("IPv4 is disabled in Jellyfin, but enabled in the OS. This may affect how the interface is selected.");
-                }
+        return result;
+    }
 
-                bool isExternal = !_lanSubnets.Any(network => network.Contains(source));
-                _logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExternal);
+    /// <inheritdoc/>
+    public string GetBindAddress(IPAddress? source, out int? port, bool skipOverrides = false)
+    {
+        port = null;
 
-                if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result))
-                {
-                    return result;
-                }
+        string result;
 
-                // No preference given, so move on to bind addresses.
-                if (MatchesBindInterface(source, isExternal, out result))
-                {
-                    return result;
-                }
+        if (source is not null)
+        {
+            if (IsIPv4Enabled && !IsIPv6Enabled && 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 (isExternal && MatchesExternalInterface(source, out result))
-                {
-                    return result;
-                }
+            if (!IsIPv4Enabled && IsIPv6Enabled && source.AddressFamily == AddressFamily.InterNetwork)
+            {
+                _logger.LogWarning("IPv4 is disabled in Jellyfin, but enabled in the OS. This may affect how the interface is selected.");
             }
 
-            // Get the first LAN interface address that's not excluded and not a loopback address.
-            // Get all available interfaces, prefer local interfaces
-            var availableInterfaces = _interfaces.Where(x => !IPAddress.IsLoopback(x.Address))
-                .OrderByDescending(x => IsInLocalNetwork(x.Address))
-                .ThenBy(x => x.Index)
-                .ToList();
+            bool isExternal = !_lanSubnets.Any(network => network.Contains(source));
+            _logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExternal);
 
-            if (availableInterfaces.Count == 0)
+            if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result))
             {
-                // There isn't any others, so we'll use the loopback.
-                result = IsIPv4Enabled && !IsIPv6Enabled ? "127.0.0.1" : "::1";
-                _logger.LogWarning("{Source}: Only loopback {Result} returned, using that as bind address.", source, result);
                 return result;
             }
 
-            // If no source address is given, use the preferred (first) interface
-            if (source is null)
+            // No preference given, so move on to bind addresses.
+            if (MatchesBindInterface(source, isExternal, out result))
             {
-                result = NetworkUtils.FormatIPString(availableInterfaces.First().Address);
-                _logger.LogDebug("{Source}: Using first internal interface as bind address: {Result}", source, result);
                 return result;
             }
 
-            // Does the request originate in one of the interface subnets?
-            // (For systems with multiple internal network cards, and multiple subnets)
-            foreach (var intf in availableInterfaces)
+            if (isExternal && MatchesExternalInterface(source, out result))
             {
-                if (intf.Subnet.Contains(source))
-                {
-                    result = NetworkUtils.FormatIPString(intf.Address);
-                    _logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", source, result);
-                    return result;
-                }
+                return result;
             }
+        }
 
-            // Fallback to first available interface
-            result = NetworkUtils.FormatIPString(availableInterfaces[0].Address);
-            _logger.LogDebug("{Source}: No matching interfaces found, using preferred interface as bind address: {Result}", source, result);
+        // Get the first LAN interface address that's not excluded and not a loopback address.
+        // Get all available interfaces, prefer local interfaces
+        var availableInterfaces = _interfaces.Where(x => !IPAddress.IsLoopback(x.Address))
+            .OrderByDescending(x => IsInLocalNetwork(x.Address))
+            .ThenBy(x => x.Index)
+            .ToList();
+
+        if (availableInterfaces.Count == 0)
+        {
+            // There isn't any others, so we'll use the loopback.
+            result = IsIPv4Enabled && !IsIPv6Enabled ? "127.0.0.1" : "::1";
+            _logger.LogWarning("{Source}: Only loopback {Result} returned, using that as bind address.", source, result);
             return result;
         }
 
-        /// <inheritdoc/>
-        public IReadOnlyList<IPData> GetInternalBindAddresses()
+        // If no source address is given, use the preferred (first) interface
+        if (source is null)
         {
-            // Select all local bind addresses
-            return _interfaces.Where(x => IsInLocalNetwork(x.Address))
-                .OrderBy(x => x.Index)
-                .ToList();
+            result = NetworkUtils.FormatIPString(availableInterfaces.First().Address);
+            _logger.LogDebug("{Source}: Using first internal interface as bind address: {Result}", source, result);
+            return result;
         }
 
-        /// <inheritdoc/>
-        public bool IsInLocalNetwork(string address)
+        // Does the request originate in one of the interface subnets?
+        // (For systems with multiple internal network cards, and multiple subnets)
+        foreach (var intf in availableInterfaces)
         {
-            if (NetworkUtils.TryParseToSubnet(address, out var subnet))
-            {
-                return IPAddress.IsLoopback(subnet.Prefix) || (_lanSubnets.Any(x => x.Contains(subnet.Prefix)) && !_excludedSubnets.Any(x => x.Contains(subnet.Prefix)));
-            }
-
-            if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
+            if (intf.Subnet.Contains(source))
             {
-                foreach (var ept in addresses)
-                {
-                    if (IPAddress.IsLoopback(ept) || (_lanSubnets.Any(x => x.Contains(ept)) && !_excludedSubnets.Any(x => x.Contains(ept))))
-                    {
-                        return true;
-                    }
-                }
+                result = NetworkUtils.FormatIPString(intf.Address);
+                _logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", source, result);
+                return result;
             }
-
-            return false;
         }
 
-        /// <inheritdoc/>
-        public bool IsInLocalNetwork(IPAddress address)
-        {
-            ArgumentNullException.ThrowIfNull(address);
+        // Fallback to first available interface
+        result = NetworkUtils.FormatIPString(availableInterfaces[0].Address);
+        _logger.LogDebug("{Source}: No matching interfaces found, using preferred interface as bind address: {Result}", source, result);
+        return result;
+    }
 
-            // See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
-            if ((TrustAllIPv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
-                || address.Equals(IPAddress.Loopback)
-                || address.Equals(IPAddress.IPv6Loopback))
-            {
-                return true;
-            }
+    /// <inheritdoc/>
+    public IReadOnlyList<IPData> GetInternalBindAddresses()
+    {
+        // Select all local bind addresses
+        return _interfaces.Where(x => IsInLocalNetwork(x.Address))
+            .OrderBy(x => x.Index)
+            .ToList();
+    }
 
-            // As private addresses can be redefined by Configuration.LocalNetworkAddresses
-            return CheckIfLanAndNotExcluded(address);
+    /// <inheritdoc/>
+    public bool IsInLocalNetwork(string address)
+    {
+        if (NetworkUtils.TryParseToSubnet(address, out var subnet))
+        {
+            return IPAddress.IsLoopback(subnet.Prefix) || (_lanSubnets.Any(x => x.Contains(subnet.Prefix)) && !_excludedSubnets.Any(x => x.Contains(subnet.Prefix)));
         }
 
-        private bool CheckIfLanAndNotExcluded(IPAddress address)
+        if (NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
         {
-            foreach (var lanSubnet in _lanSubnets)
+            foreach (var ept in addresses)
             {
-                if (lanSubnet.Contains(address))
+                if (IPAddress.IsLoopback(ept) || (_lanSubnets.Any(x => x.Contains(ept)) && !_excludedSubnets.Any(x => x.Contains(ept))))
                 {
-                    foreach (var excludedSubnet in _excludedSubnets)
-                    {
-                        if (excludedSubnet.Contains(address))
-                        {
-                            return false;
-                        }
-                    }
-
                     return true;
                 }
             }
-
-            return false;
         }
 
-        /// <summary>
-        /// Attempts to match the source against the published server URL overrides.
-        /// </summary>
-        /// <param name="source">IP source address to use.</param>
-        /// <param name="isInExternalSubnet">True if the source is in an external subnet.</param>
-        /// <param name="bindPreference">The published server URL that matches the source address.</param>
-        /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
-        private bool MatchesPublishedServerUrl(IPAddress source, bool isInExternalSubnet, out string bindPreference)
+        return false;
+    }
+
+    /// <inheritdoc/>
+    public bool IsInLocalNetwork(IPAddress address)
+    {
+        ArgumentNullException.ThrowIfNull(address);
+
+        // See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
+        if ((TrustAllIPv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
+            || address.Equals(IPAddress.Loopback)
+            || address.Equals(IPAddress.IPv6Loopback))
         {
-            bindPreference = string.Empty;
-            int? port = null;
+            return true;
+        }
 
-            // 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();
-            }
+        // As private addresses can be redefined by Configuration.LocalNetworkAddresses
+        return CheckIfLanAndNotExcluded(address);
+    }
 
-            foreach (var data in validPublishedServerUrls)
+    private bool CheckIfLanAndNotExcluded(IPAddress address)
+    {
+        foreach (var lanSubnet in _lanSubnets)
+        {
+            if (lanSubnet.Contains(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)
+                foreach (var excludedSubnet in _excludedSubnets)
                 {
-                    // If matching interface is found, use override
-                    bindPreference = data.OverrideUri;
-                    break;
+                    if (excludedSubnet.Contains(address))
+                    {
+                        return false;
+                    }
                 }
-            }
 
-            if (string.IsNullOrEmpty(bindPreference))
-            {
-                _logger.LogDebug("{Source}: No matching bind address override found", source);
-                return false;
+                return true;
             }
+        }
 
-            // Handle override specifying port
-            var parts = bindPreference.Split(':');
-            if (parts.Length > 1)
-            {
-                if (int.TryParse(parts[1], out int p))
-                {
-                    bindPreference = parts[0];
-                    port = p;
-                    _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
-                    return true;
-                }
-            }
+        return false;
+    }
 
-            _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
-            return true;
+    /// <summary>
+    /// Attempts to match the source against the published server URL overrides.
+    /// </summary>
+    /// <param name="source">IP source address to use.</param>
+    /// <param name="isInExternalSubnet">True if the source is in an external subnet.</param>
+    /// <param name="bindPreference">The published server URL that matches the source address.</param>
+    /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
+    private bool MatchesPublishedServerUrl(IPAddress source, bool isInExternalSubnet, out string bindPreference)
+    {
+        bindPreference = string.Empty;
+        int? port = null;
+
+        // 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();
         }
 
-        /// <summary>
-        /// Attempts to match the source against the user defined bind interfaces.
-        /// </summary>
-        /// <param name="source">IP source address to use.</param>
-        /// <param name="isInExternalSubnet">True if the source is in the external subnet.</param>
-        /// <param name="result">The result, if a match is found.</param>
-        /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
-        private bool MatchesBindInterface(IPAddress source, bool isInExternalSubnet, out string result)
+        foreach (var data in validPublishedServerUrls)
         {
-            result = string.Empty;
+            // Get interface matching override subnet
+            var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => data.Data.Subnet.Contains(x.Address));
 
-            int count = _interfaces.Count;
-            if (count == 1 && (_interfaces[0].Equals(IPAddress.Any) || _interfaces[0].Equals(IPAddress.IPv6Any)))
+            if (intf?.Address is not null)
             {
-                // Ignore IPAny addresses.
-                count = 0;
+                // If matching interface is found, use override
+                bindPreference = data.OverrideUri;
+                break;
             }
+        }
+
+        if (string.IsNullOrEmpty(bindPreference))
+        {
+            _logger.LogDebug("{Source}: No matching bind address override found", source);
+            return false;
+        }
 
-            if (count == 0)
+        // Handle override specifying port
+        var parts = bindPreference.Split(':');
+        if (parts.Length > 1)
+        {
+            if (int.TryParse(parts[1], out int p))
             {
-                return false;
+                bindPreference = parts[0];
+                port = p;
+                _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port);
+                return true;
             }
+        }
 
-            IPAddress? bindAddress = null;
-            if (isInExternalSubnet)
-            {
-                var externalInterfaces = _interfaces.Where(x => !IsInLocalNetwork(x.Address))
-                    .OrderBy(x => x.Index)
-                    .ToList();
-                if (externalInterfaces.Count > 0)
-                {
-                    // Check to see if any of the external bind interfaces are in the same subnet as the source.
-                    // If none exists, this will select the first external interface if there is one.
-                    bindAddress = externalInterfaces
-                        .OrderByDescending(x => x.Subnet.Contains(source))
-                        .ThenBy(x => x.Index)
-                        .Select(x => x.Address)
-                        .First();
-
-                    result = NetworkUtils.FormatIPString(bindAddress);
-                    _logger.LogDebug("{Source}: External request received, matching external bind address found: {Result}", source, result);
-                    return true;
-                }
+        _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
+        return true;
+    }
 
-                _logger.LogWarning("{Source}: External request received, no matching external bind address found, trying internal addresses.", source);
-            }
-            else
+    /// <summary>
+    /// Attempts to match the source against the user defined bind interfaces.
+    /// </summary>
+    /// <param name="source">IP source address to use.</param>
+    /// <param name="isInExternalSubnet">True if the source is in the external subnet.</param>
+    /// <param name="result">The result, if a match is found.</param>
+    /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
+    private bool MatchesBindInterface(IPAddress source, bool isInExternalSubnet, out string result)
+    {
+        result = string.Empty;
+
+        int count = _interfaces.Count;
+        if (count == 1 && (_interfaces[0].Equals(IPAddress.Any) || _interfaces[0].Equals(IPAddress.IPv6Any)))
+        {
+            // Ignore IPAny addresses.
+            count = 0;
+        }
+
+        if (count == 0)
+        {
+            return false;
+        }
+
+        IPAddress? bindAddress = null;
+        if (isInExternalSubnet)
+        {
+            var externalInterfaces = _interfaces.Where(x => !IsInLocalNetwork(x.Address))
+                .OrderBy(x => x.Index)
+                .ToList();
+            if (externalInterfaces.Count > 0)
             {
-                // Check to see if any of the internal bind interfaces are in the same subnet as the source.
-                // If none exists, this will select the first internal interface if there is one.
-                bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address))
+                // Check to see if any of the external bind interfaces are in the same subnet as the source.
+                // If none exists, this will select the first external interface if there is one.
+                bindAddress = externalInterfaces
                     .OrderByDescending(x => x.Subnet.Contains(source))
                     .ThenBy(x => x.Index)
                     .Select(x => x.Address)
-                    .FirstOrDefault();
+                    .First();
 
-                if (bindAddress is not null)
-                {
-                    result = NetworkUtils.FormatIPString(bindAddress);
-                    _logger.LogDebug("{Source}: Internal request received, matching internal bind address found: {Result}", source, result);
-                    return true;
-                }
+                result = NetworkUtils.FormatIPString(bindAddress);
+                _logger.LogDebug("{Source}: External request received, matching external bind address found: {Result}", source, result);
+                return true;
             }
 
-            return false;
+            _logger.LogWarning("{Source}: External request received, no matching external bind address found, trying internal addresses.", source);
         }
-
-        /// <summary>
-        /// Attempts to match the source against external interfaces.
-        /// </summary>
-        /// <param name="source">IP source address to use.</param>
-        /// <param name="result">The result, if a match is found.</param>
-        /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
-        private bool MatchesExternalInterface(IPAddress source, out string result)
+        else
         {
-            // Get the first external interface address that isn't a loopback.
-            var extResult = _interfaces.Where(p => !IsInLocalNetwork(p.Address)).OrderBy(x => x.Index).ToArray();
+            // Check to see if any of the internal bind interfaces are in the same subnet as the source.
+            // If none exists, this will select the first internal interface if there is one.
+            bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address))
+                .OrderByDescending(x => x.Subnet.Contains(source))
+                .ThenBy(x => x.Index)
+                .Select(x => x.Address)
+                .FirstOrDefault();
 
-            // No external interface found
-            if (extResult.Length == 0)
+            if (bindAddress is not null)
             {
-                result = string.Empty;
-                _logger.LogWarning("{Source}: External request received, but no external interface found. Need to route through internal network.", source);
-                return false;
+                result = NetworkUtils.FormatIPString(bindAddress);
+                _logger.LogDebug("{Source}: Internal request received, matching internal bind address found: {Result}", source, result);
+                return true;
             }
+        }
 
-            // Does the request originate in one of the interface subnets?
-            // (For systems with multiple network cards and/or multiple subnets)
-            foreach (var intf in extResult)
-            {
-                if (intf.Subnet.Contains(source))
-                {
-                    result = NetworkUtils.FormatIPString(intf.Address);
-                    _logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Result}", source, result);
-                    return true;
-                }
-            }
+        return false;
+    }
 
-            // Fallback to first external interface.
-            result = NetworkUtils.FormatIPString(extResult[0].Address);
-            _logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
-            return true;
+    /// <summary>
+    /// Attempts to match the source against external interfaces.
+    /// </summary>
+    /// <param name="source">IP source address to use.</param>
+    /// <param name="result">The result, if a match is found.</param>
+    /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
+    private bool MatchesExternalInterface(IPAddress source, out string result)
+    {
+        // Get the first external interface address that isn't a loopback.
+        var extResult = _interfaces.Where(p => !IsInLocalNetwork(p.Address)).OrderBy(x => x.Index).ToArray();
+
+        // No external interface found
+        if (extResult.Length == 0)
+        {
+            result = string.Empty;
+            _logger.LogWarning("{Source}: External request received, but no external interface found. Need to route through internal network.", source);
+            return false;
         }
 
-        private void PrintNetworkInformation(NetworkConfiguration config, bool debug = true)
+        // Does the request originate in one of the interface subnets?
+        // (For systems with multiple network cards and/or multiple subnets)
+        foreach (var intf in extResult)
         {
-            var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
-            if (_logger.IsEnabled(logLevel))
+            if (intf.Subnet.Contains(source))
             {
-                _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));
+                result = NetworkUtils.FormatIPString(intf.Address);
+                _logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Result}", source, result);
+                return true;
             }
         }
+
+        // Fallback to first external interface.
+        result = NetworkUtils.FormatIPString(extResult[0].Address);
+        _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));
+        }
     }
 }

+ 24 - 25
src/Jellyfin.Networking/Udp/SocketFactory.cs

@@ -3,37 +3,36 @@ using System.Net;
 using System.Net.Sockets;
 using MediaBrowser.Model.Net;
 
-namespace Jellyfin.Networking.Udp
+namespace Jellyfin.Networking.Udp;
+
+/// <summary>
+/// Factory class to create different kinds of sockets.
+/// </summary>
+public class SocketFactory : ISocketFactory
 {
-    /// <summary>
-    /// Factory class to create different kinds of sockets.
-    /// </summary>
-    public class SocketFactory : ISocketFactory
+    /// <inheritdoc />
+    public Socket CreateUdpBroadcastSocket(int localPort)
     {
-        /// <inheritdoc />
-        public Socket CreateUdpBroadcastSocket(int localPort)
+        if (localPort < 0)
         {
-            if (localPort < 0)
-            {
-                throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
-            }
+            throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
+        }
 
-            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
-            try
-            {
-                socket.EnableBroadcast = true;
-                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
-                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
-                socket.Bind(new IPEndPoint(IPAddress.Any, localPort));
+        var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+        try
+        {
+            socket.EnableBroadcast = true;
+            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
+            socket.Bind(new IPEndPoint(IPAddress.Any, localPort));
 
-                return socket;
-            }
-            catch
-            {
-                socket.Dispose();
+            return socket;
+        }
+        catch
+        {
+            socket.Dispose();
 
-                throw;
-            }
+            throw;
         }
     }
 }

+ 96 - 97
src/Jellyfin.Networking/Udp/UdpServer.cs

@@ -11,127 +11,126 @@ using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 
-namespace Jellyfin.Networking.Udp
+namespace Jellyfin.Networking.Udp;
+
+/// <summary>
+/// Provides a Udp Server.
+/// </summary>
+public sealed class UdpServer : IDisposable
 {
     /// <summary>
-    /// Provides a Udp Server.
+    /// The _logger.
     /// </summary>
-    public sealed class UdpServer : IDisposable
+    private readonly ILogger _logger;
+    private readonly IServerApplicationHost _appHost;
+    private readonly IConfiguration _config;
+
+    private readonly byte[] _receiveBuffer = new byte[8192];
+
+    private readonly Socket _udpSocket;
+    private readonly IPEndPoint _endpoint;
+    private bool _disposed;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="UdpServer" /> class.
+    /// </summary>
+    /// <param name="logger">The logger.</param>
+    /// <param name="appHost">The application host.</param>
+    /// <param name="configuration">The configuration manager.</param>
+    /// <param name="bindAddress"> The bind address.</param>
+    /// <param name="port">The port.</param>
+    public UdpServer(
+        ILogger logger,
+        IServerApplicationHost appHost,
+        IConfiguration configuration,
+        IPAddress bindAddress,
+        int port)
     {
-        /// <summary>
-        /// The _logger.
-        /// </summary>
-        private readonly ILogger _logger;
-        private readonly IServerApplicationHost _appHost;
-        private readonly IConfiguration _config;
-
-        private readonly byte[] _receiveBuffer = new byte[8192];
-
-        private readonly Socket _udpSocket;
-        private readonly IPEndPoint _endpoint;
-        private bool _disposed;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="UdpServer" /> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="appHost">The application host.</param>
-        /// <param name="configuration">The configuration manager.</param>
-        /// <param name="bindAddress"> The bind address.</param>
-        /// <param name="port">The port.</param>
-        public UdpServer(
-            ILogger logger,
-            IServerApplicationHost appHost,
-            IConfiguration configuration,
-            IPAddress bindAddress,
-            int port)
+        _logger = logger;
+        _appHost = appHost;
+        _config = configuration;
+
+        _endpoint = new IPEndPoint(bindAddress, port);
+
+        _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
         {
-            _logger = logger;
-            _appHost = appHost;
-            _config = configuration;
+            MulticastLoopback = false,
+        };
+        _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+    }
 
-            _endpoint = new IPEndPoint(bindAddress, port);
+    private async Task RespondToV2Message(EndPoint endpoint, CancellationToken cancellationToken)
+    {
+        string? localUrl = _config[AddressOverrideKey];
+        if (string.IsNullOrEmpty(localUrl))
+        {
+            localUrl = _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address);
+        }
 
-            _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
-            {
-                MulticastLoopback = false,
-            };
-            _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+        if (string.IsNullOrEmpty(localUrl))
+        {
+            _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined.");
+            return;
         }
 
-        private async Task RespondToV2Message(EndPoint endpoint, CancellationToken cancellationToken)
+        var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);
+
+        try
         {
-            string? localUrl = _config[AddressOverrideKey];
-            if (string.IsNullOrEmpty(localUrl))
-            {
-                localUrl = _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address);
-            }
+            _logger.LogDebug("Sending AutoDiscovery response");
+            await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint, cancellationToken).ConfigureAwait(false);
+        }
+        catch (SocketException ex)
+        {
+            _logger.LogError(ex, "Error sending response message");
+        }
+    }
 
-            if (string.IsNullOrEmpty(localUrl))
-            {
-                _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined.");
-                return;
-            }
+    /// <summary>
+    /// Starts the specified port.
+    /// </summary>
+    /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
+    public void Start(CancellationToken cancellationToken)
+    {
+        _udpSocket.Bind(_endpoint);
 
-            var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);
+        _ = Task.Run(async () => await BeginReceiveAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
+    }
 
+    private async Task BeginReceiveAsync(CancellationToken cancellationToken)
+    {
+        while (!cancellationToken.IsCancellationRequested)
+        {
             try
             {
-                _logger.LogDebug("Sending AutoDiscovery response");
-                await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), 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))
+                {
+                    await RespondToV2Message(result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
+                }
             }
             catch (SocketException ex)
             {
-                _logger.LogError(ex, "Error sending response message");
+                _logger.LogError(ex, "Failed to receive data from socket");
             }
-        }
-
-        /// <summary>
-        /// Starts the specified port.
-        /// </summary>
-        /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
-        public void Start(CancellationToken cancellationToken)
-        {
-            _udpSocket.Bind(_endpoint);
-
-            _ = Task.Run(async () => await BeginReceiveAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
-        }
-
-        private async Task BeginReceiveAsync(CancellationToken cancellationToken)
-        {
-            while (!cancellationToken.IsCancellationRequested)
+            catch (OperationCanceledException)
             {
-                try
-                {
-                    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))
-                    {
-                        await RespondToV2Message(result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
-                    }
-                }
-                catch (SocketException ex)
-                {
-                    _logger.LogError(ex, "Failed to receive data from socket");
-                }
-                catch (OperationCanceledException)
-                {
-                    _logger.LogDebug("Broadcast socket operation cancelled");
-                }
+                _logger.LogDebug("Broadcast socket operation cancelled");
             }
         }
+    }
 
-        /// <inheritdoc />
-        public void Dispose()
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (_disposed)
         {
-            if (_disposed)
-            {
-                return;
-            }
-
-            _udpSocket.Dispose();
-            _disposed = true;
+            return;
         }
+
+        _udpSocket.Dispose();
+        _disposed = true;
     }
 }

+ 101 - 102
src/Jellyfin.Networking/UdpServerEntryPoint.cs

@@ -13,132 +13,131 @@ using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 
-namespace Jellyfin.Networking
+namespace Jellyfin.Networking;
+
+/// <summary>
+/// Class responsible for registering all UDP broadcast endpoints and their handlers.
+/// </summary>
+public sealed class UdpServerEntryPoint : IServerEntryPoint
 {
     /// <summary>
-    /// Class responsible for registering all UDP broadcast endpoints and their handlers.
+    /// The port of the UDP server.
     /// </summary>
-    public sealed class UdpServerEntryPoint : IServerEntryPoint
-    {
-        /// <summary>
-        /// The port of the UDP server.
-        /// </summary>
-        public const int PortNumber = 7359;
+    public const int PortNumber = 7359;
 
-        /// <summary>
-        /// The logger.
-        /// </summary>
-        private readonly ILogger<UdpServerEntryPoint> _logger;
-        private readonly IServerApplicationHost _appHost;
-        private readonly IConfiguration _config;
-        private readonly IConfigurationManager _configurationManager;
-        private readonly INetworkManager _networkManager;
+    /// <summary>
+    /// The logger.
+    /// </summary>
+    private readonly ILogger<UdpServerEntryPoint> _logger;
+    private readonly IServerApplicationHost _appHost;
+    private readonly IConfiguration _config;
+    private readonly IConfigurationManager _configurationManager;
+    private readonly INetworkManager _networkManager;
 
-        /// <summary>
-        /// The UDP server.
-        /// </summary>
-        private readonly List<UdpServer> _udpServers;
-        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
-        private bool _disposed;
+    /// <summary>
+    /// The UDP server.
+    /// </summary>
+    private readonly List<UdpServer> _udpServers;
+    private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+    private bool _disposed;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
+    /// </summary>
+    /// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param>
+    /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
+    /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
+    /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+    /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+    public UdpServerEntryPoint(
+        ILogger<UdpServerEntryPoint> logger,
+        IServerApplicationHost appHost,
+        IConfiguration configuration,
+        IConfigurationManager configurationManager,
+        INetworkManager networkManager)
+    {
+        _logger = logger;
+        _appHost = appHost;
+        _config = configuration;
+        _configurationManager = configurationManager;
+        _networkManager = networkManager;
+        _udpServers = new List<UdpServer>();
+    }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
-        /// </summary>
-        /// <param name="logger">Instance of the <see cref="ILogger{UdpServerEntryPoint}"/> interface.</param>
-        /// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
-        /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
-        /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
-        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
-        public UdpServerEntryPoint(
-            ILogger<UdpServerEntryPoint> logger,
-            IServerApplicationHost appHost,
-            IConfiguration configuration,
-            IConfigurationManager configurationManager,
-            INetworkManager networkManager)
+    /// <inheritdoc />
+    public Task RunAsync()
+    {
+        ObjectDisposedException.ThrowIf(_disposed, this);
+
+        if (!_configurationManager.GetNetworkConfiguration().AutoDiscovery)
         {
-            _logger = logger;
-            _appHost = appHost;
-            _config = configuration;
-            _configurationManager = configurationManager;
-            _networkManager = networkManager;
-            _udpServers = new List<UdpServer>();
+            return Task.CompletedTask;
         }
 
-        /// <inheritdoc />
-        public Task RunAsync()
+        try
         {
-            ObjectDisposedException.ThrowIf(_disposed, this);
-
-            if (!_configurationManager.GetNetworkConfiguration().AutoDiscovery)
+            // 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())
             {
-                return Task.CompletedTask;
-            }
+                // Add global broadcast listener
+                var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
+                server.Start(_cancellationTokenSource.Token);
+                _udpServers.Add(server);
 
-            try
-            {
-                // 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 bind address specific broadcast listeners
+                // IPv6 is currently unsupported
+                var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
+                foreach (var intf in validInterfaces)
                 {
-                    // Add global broadcast listener
-                    var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
+                    var broadcastAddress = NetworkUtils.GetBroadcastAddress(intf.Subnet);
+                    _logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", broadcastAddress, PortNumber);
+
+                    server = new UdpServer(_logger, _appHost, _config, broadcastAddress, 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 broadcastAddress = NetworkUtils.GetBroadcastAddress(intf.Subnet);
-                        _logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", broadcastAddress, PortNumber);
-
-                        server = new UdpServer(_logger, _appHost, _config, broadcastAddress, PortNumber);
-                        server.Start(_cancellationTokenSource.Token);
-                        _udpServers.Add(server);
-                    }
                 }
-                else
+            }
+            else
+            {
+                // 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)
                 {
-                    // 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 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);
-                    }
+                    var server = new UdpServer(_logger, _appHost, _config, intfAddress, PortNumber);
+                    server.Start(_cancellationTokenSource.Token);
+                    _udpServers.Add(server);
                 }
             }
-            catch (SocketException ex)
-            {
-                _logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
-            }
-
-            return Task.CompletedTask;
         }
-
-        /// <inheritdoc />
-        public void Dispose()
+        catch (SocketException ex)
         {
-            if (_disposed)
-            {
-                return;
-            }
+            _logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
+        }
 
-            _cancellationTokenSource.Cancel();
-            _cancellationTokenSource.Dispose();
-            foreach (var server in _udpServers)
-            {
-                server.Dispose();
-            }
+        return Task.CompletedTask;
+    }
+
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (_disposed)
+        {
+            return;
+        }
 
-            _udpServers.Clear();
-            _disposed = true;
+        _cancellationTokenSource.Cancel();
+        _cancellationTokenSource.Dispose();
+        foreach (var server in _udpServers)
+        {
+            server.Dispose();
         }
+
+        _udpServers.Clear();
+        _disposed = true;
     }
 }