|  | @@ -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));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |