< Summary - Jellyfin

Information
Class: Jellyfin.Networking.Manager.NetworkManager
Assembly: Jellyfin.Networking
File(s): /srv/git/jellyfin/src/Jellyfin.Networking/Manager/NetworkManager.cs
Line coverage
81%
Covered lines: 411
Uncovered lines: 96
Coverable lines: 507
Total lines: 1272
Line coverage: 81%
Branch coverage
63%
Covered branches: 186
Total branches: 292
Branch coverage: 63.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/6/2026 - 12:15:23 AM Line coverage: 74.4% (365/490) Branch coverage: 64.4% (178/276) Total lines: 12095/20/2026 - 12:15:44 AM Line coverage: 74.4% (365/490) Branch coverage: 59% (163/276) Total lines: 12097/7/2026 - 12:16:09 AM Line coverage: 74.4% (365/490) Branch coverage: 58.6% (162/276) Total lines: 12097/8/2026 - 12:16:24 AM Line coverage: 74.4% (365/490) Branch coverage: 59% (163/276) Total lines: 12098/3/2026 - 12:16:46 AM Line coverage: 81% (411/507) Branch coverage: 63.6% (186/292) Total lines: 1272 5/6/2026 - 12:15:23 AM Line coverage: 74.4% (365/490) Branch coverage: 64.4% (178/276) Total lines: 12095/20/2026 - 12:15:44 AM Line coverage: 74.4% (365/490) Branch coverage: 59% (163/276) Total lines: 12097/7/2026 - 12:16:09 AM Line coverage: 74.4% (365/490) Branch coverage: 58.6% (162/276) Total lines: 12097/8/2026 - 12:16:24 AM Line coverage: 74.4% (365/490) Branch coverage: 59% (163/276) Total lines: 12098/3/2026 - 12:16:46 AM Line coverage: 81% (411/507) Branch coverage: 63.6% (186/292) Total lines: 1272

Coverage delta

Coverage delta 7 -7

Metrics

File(s)

/srv/git/jellyfin/src/Jellyfin.Networking/Manager/NetworkManager.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics.CodeAnalysis;
 4using System.Globalization;
 5using System.Linq;
 6using System.Net;
 7using System.Net.NetworkInformation;
 8using System.Net.Sockets;
 9using System.Threading;
 10using J2N.Collections.Generic.Extensions;
 11using MediaBrowser.Common.Configuration;
 12using MediaBrowser.Common.Net;
 13using MediaBrowser.Model.Net;
 14using Microsoft.AspNetCore.Http;
 15using Microsoft.Extensions.Configuration;
 16using Microsoft.Extensions.Logging;
 17using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 18using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 19
 20namespace Jellyfin.Networking.Manager;
 21
 22/// <summary>
 23/// Class to take care of network interface management.
 24/// </summary>
 25public class NetworkManager : INetworkManager, IDisposable
 26{
 27    /// <summary>
 28    /// Threading lock for network properties.
 29    /// </summary>
 30    private readonly Lock _initLock;
 31
 32    private readonly ILogger<NetworkManager> _logger;
 33
 34    private readonly IConfigurationManager _configurationManager;
 35
 36    private readonly IConfiguration _startupConfig;
 37
 38    private readonly Lock _networkEventLock;
 39
 40    /// <summary>
 41    /// Holds the published server URLs and the IPs to use them on.
 42    /// </summary>
 43    private IReadOnlyList<PublishedServerUriOverride> _publishedServerUrls;
 44
 45    private IReadOnlyList<IPNetwork> _remoteAddressFilter;
 46
 47    /// <summary>
 48    /// Used to stop "event-racing conditions".
 49    /// </summary>
 50    private bool _eventfire;
 51
 52    /// <summary>
 53    /// Dictionary containing interface addresses and their subnets.
 54    /// </summary>
 55    private List<IPData> _interfaces;
 56
 57    /// <summary>
 58    /// Unfiltered user defined LAN subnets (<see cref="NetworkConfiguration.LocalNetworkSubnets"/>)
 59    /// or internal interface network subnets if undefined by user.
 60    /// </summary>
 61    private IReadOnlyList<IPNetwork> _lanSubnets;
 62
 63    /// <summary>
 64    /// User defined list of subnets to excluded from the LAN.
 65    /// </summary>
 66    private IReadOnlyList<IPNetwork> _excludedSubnets;
 67
 68    /// <summary>
 69    /// True if this object is disposed.
 70    /// </summary>
 71    private bool _disposed;
 72
 73    /// <summary>
 74    /// Initializes a new instance of the <see cref="NetworkManager"/> class.
 75    /// </summary>
 76    /// <param name="configurationManager">The <see cref="IConfigurationManager"/> instance.</param>
 77    /// <param name="startupConfig">The <see cref="IConfiguration"/> instance holding startup parameters.</param>
 78    /// <param name="logger">Logger to use for messages.</param>
 79    public NetworkManager(IConfigurationManager configurationManager, IConfiguration startupConfig, ILogger<NetworkManag
 80    {
 10481        ArgumentNullException.ThrowIfNull(logger);
 10482        ArgumentNullException.ThrowIfNull(configurationManager);
 83
 10484        _logger = logger;
 10485        _configurationManager = configurationManager;
 10486        _startupConfig = startupConfig;
 10487        _initLock = new();
 10488        _interfaces = new List<IPData>();
 10489        _publishedServerUrls = new List<PublishedServerUriOverride>();
 10490        _networkEventLock = new();
 10491        _remoteAddressFilter = new List<IPNetwork>();
 92
 10493        _ = bool.TryParse(startupConfig[DetectNetworkChangeKey], out var detectNetworkChange);
 94
 10495        UpdateSettings(_configurationManager.GetNetworkConfiguration());
 96
 10497        if (detectNetworkChange)
 98        {
 2299            NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
 22100            NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
 101        }
 102
 104103        _configurationManager.NamedConfigurationUpdated += ConfigurationUpdated;
 104104    }
 105
 106    /// <summary>
 107    /// Event triggered on network changes.
 108    /// </summary>
 109    public event EventHandler? NetworkChanged;
 110
 111    /// <summary>
 112    /// Gets or sets a value indicating whether testing is taking place.
 113    /// </summary>
 3114    public static string MockNetworkSettings { get; set; } = string.Empty;
 115
 116    /// <summary>
 117    /// Gets a value indicating whether IPv4 is enabled.
 118    /// </summary>
 305119    public bool IsIPv4Enabled => _configurationManager.GetNetworkConfiguration().EnableIPv4;
 120
 121    /// <summary>
 122    /// Gets a value indicating whether IP6 is enabled.
 123    /// </summary>
 266124    public bool IsIPv6Enabled => _configurationManager.GetNetworkConfiguration().EnableIPv6;
 125
 126    /// <summary>
 127    /// Gets a value indicating whether is all IPv6 interfaces are trusted as internal.
 128    /// </summary>
 129    public bool TrustAllIPv6Interfaces { get; private set; }
 130
 131    /// <summary>
 132    /// Gets the Published server override list.
 133    /// </summary>
 0134    public IReadOnlyList<PublishedServerUriOverride> PublishedServerUrls => _publishedServerUrls;
 135
 136    /// <inheritdoc/>
 137    public void Dispose()
 138    {
 82139        Dispose(true);
 82140        GC.SuppressFinalize(this);
 82141    }
 142
 143    /// <summary>
 144    /// Handler for network change events.
 145    /// </summary>
 146    /// <param name="sender">Sender.</param>
 147    /// <param name="e">A <see cref="NetworkAvailabilityEventArgs"/> containing network availability information.</param
 148    private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs e)
 149    {
 0150        _logger.LogDebug("Network availability changed.");
 0151        HandleNetworkChange();
 0152    }
 153
 154    /// <summary>
 155    /// Handler for network change events.
 156    /// </summary>
 157    /// <param name="sender">Sender.</param>
 158    /// <param name="e">An <see cref="EventArgs"/>.</param>
 159    private void OnNetworkAddressChanged(object? sender, EventArgs e)
 160    {
 0161        _logger.LogDebug("Network address change detected.");
 0162        HandleNetworkChange();
 0163    }
 164
 165    /// <summary>
 166    /// Triggers our event, and re-loads interface information.
 167    /// </summary>
 168    private void HandleNetworkChange()
 0169    {
 170        lock (_networkEventLock)
 171        {
 0172            if (!_eventfire)
 173            {
 174                // As network events tend to fire one after the other only fire once every second.
 0175                _eventfire = true;
 0176                OnNetworkChange();
 177            }
 0178        }
 0179    }
 180
 181    /// <summary>
 182    /// Waits for 2 seconds before re-initialising the settings, as typically these events fire multiple times in succes
 183    /// </summary>
 184    private void OnNetworkChange()
 185    {
 186        try
 187        {
 0188            Thread.Sleep(2000);
 0189            var networkConfig = _configurationManager.GetNetworkConfiguration();
 0190            if (IsIPv6Enabled && !Socket.OSSupportsIPv6)
 191            {
 0192                UpdateSettings(networkConfig);
 193            }
 194            else
 195            {
 0196                InitializeInterfaces();
 0197                InitializeLan(networkConfig);
 0198                EnforceBindSettings(networkConfig);
 199            }
 200
 0201            PrintNetworkInformation(networkConfig);
 0202            NetworkChanged?.Invoke(this, EventArgs.Empty);
 0203        }
 204        finally
 205        {
 0206            _eventfire = false;
 0207        }
 0208    }
 209
 210    /// <summary>
 211    /// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
 212    /// </summary>
 213    private void InitializeInterfaces()
 38214    {
 215        lock (_initLock)
 216        {
 38217            _interfaces = GetInterfacesCore(_logger, IsIPv4Enabled, IsIPv6Enabled).ToList();
 38218        }
 38219    }
 220
 221    /// <summary>
 222    /// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
 223    /// </summary>
 224    /// <param name="logger">The logger.</param>
 225    /// <param name="isIPv4Enabled">If true evaluates IPV4 type ip addresses.</param>
 226    /// <param name="isIPv6Enabled">If true evaluates IPV6 type ip addresses.</param>
 227    /// <returns>A list of all locally known up addresses and submasks that are to be considered usable.</returns>
 228    public static IReadOnlyList<IPData> GetInterfacesCore(ILogger logger, bool isIPv4Enabled, bool isIPv6Enabled)
 229    {
 38230        logger.LogDebug("Refreshing interfaces.");
 231
 38232        var interfaces = new List<IPData>();
 233
 234        try
 235        {
 38236            var nics = NetworkInterface.GetAllNetworkInterfaces()
 38237                .Where(i => i.OperationalStatus == OperationalStatus.Up);
 238
 228239            foreach (NetworkInterface adapter in nics)
 240            {
 241                try
 242                {
 76243                    var ipProperties = adapter.GetIPProperties();
 244
 245                    // Populate interface list
 456246                    foreach (var info in ipProperties.UnicastAddresses)
 247                    {
 152248                        if (isIPv4Enabled && info.Address.AddressFamily == AddressFamily.InterNetwork)
 249                        {
 76250                            var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength
 76251                            {
 76252                                Index = ipProperties.GetIPv4Properties().Index,
 76253                                Name = adapter.Name,
 76254                                SupportsMulticast = adapter.SupportsMulticast
 76255                            };
 256
 76257                            interfaces.Add(interfaceObject);
 258                        }
 76259                        else if (isIPv6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
 260                        {
 26261                            var interfaceObject = new IPData(info.Address, new IPNetwork(info.Address, info.PrefixLength
 26262                            {
 26263                                Index = ipProperties.GetIPv6Properties().Index,
 26264                                Name = adapter.Name,
 26265                                SupportsMulticast = adapter.SupportsMulticast
 26266                            };
 267
 26268                            interfaces.Add(interfaceObject);
 269                        }
 270                    }
 76271                }
 0272                catch (Exception ex)
 273                {
 274                    // Ignore error, and attempt to continue.
 0275                    logger.LogError(ex, "Error encountered parsing interfaces.");
 0276                }
 277            }
 38278        }
 0279        catch (Exception ex)
 280        {
 0281            logger.LogError(ex, "Error obtaining interfaces.");
 0282        }
 283
 284        // If no interfaces are found, fallback to loopback interfaces.
 38285        if (interfaces.Count == 0)
 286        {
 0287            logger.LogWarning("No interface information available. Using loopback interface(s).");
 288
 0289            if (isIPv4Enabled)
 290            {
 0291                interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
 292            }
 293
 0294            if (isIPv6Enabled)
 295            {
 0296                interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
 297            }
 298        }
 299
 38300        logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
 38301        logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == Addres
 38302        return interfaces;
 303    }
 304
 305    /// <summary>
 306    /// Initializes internal LAN cache.
 307    /// </summary>
 308    [MemberNotNull(nameof(_lanSubnets), nameof(_excludedSubnets))]
 309    private void InitializeLan(NetworkConfiguration config)
 104310    {
 311        lock (_initLock)
 312        {
 104313            _logger.LogDebug("Refreshing LAN information.");
 314
 315            // Get configuration options
 104316            var subnets = config.LocalNetworkSubnets;
 317
 318            // If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
 104319            if (!NetworkUtils.TryParseToSubnets(subnets, out var lanSubnets, false, _logger) || lanSubnets.Count == 0)
 320            {
 46321                _logger.LogDebug("Using LAN interface addresses as user provided no LAN details.");
 322
 46323                var fallbackLanSubnets = new List<IPNetwork>();
 46324                if (IsIPv6Enabled)
 325                {
 7326                    fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291Loopback); // RFC 4291 (Loopback)
 7327                    fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4291SiteLocal); // RFC 4291 (Site local)
 7328                    fallbackLanSubnets.Add(NetworkConstants.IPv6RFC4193UniqueLocal); // RFC 4193 (Unique local)
 329                }
 330
 46331                if (IsIPv4Enabled)
 332                {
 46333                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC5735Loopback); // RFC 5735 (Loopback)
 46334                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassA); // RFC 1918 (private Class A)
 46335                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassB); // RFC 1918 (private Class B)
 46336                    fallbackLanSubnets.Add(NetworkConstants.IPv4RFC1918PrivateClassC); // RFC 1918 (private Class C)
 337                }
 338
 46339                _lanSubnets = fallbackLanSubnets;
 340            }
 341            else
 342            {
 58343                _lanSubnets = lanSubnets.Select(x => x.Subnet).ToArray();
 344            }
 345
 104346            _excludedSubnets = NetworkUtils.TryParseToSubnets(subnets, out var excludedSubnets, true, _logger)
 104347                ? excludedSubnets.Select(x => x.Subnet).ToArray()
 104348                : Array.Empty<IPNetwork>();
 104349        }
 104350    }
 351
 352    /// <summary>
 353    /// Enforce bind addresses and exclusions on available interfaces.
 354    /// </summary>
 355    private void EnforceBindSettings(NetworkConfiguration config)
 104356    {
 357        lock (_initLock)
 358        {
 104359            _interfaces = FilterBindSettings(config, _interfaces, IsIPv4Enabled, IsIPv6Enabled).ToList();
 104360        }
 104361    }
 362
 363    /// <summary>
 364    /// Filters a list of bind addresses and exclusions on available interfaces.
 365    /// </summary>
 366    /// <param name="config">The network config to be filtered by.</param>
 367    /// <param name="interfaces">A list of possible interfaces to be filtered.</param>
 368    /// <param name="isIPv4Enabled">If true evaluates IPV4 type ip addresses.</param>
 369    /// <param name="isIPv6Enabled">If true evaluates IPV6 type ip addresses.</param>
 370    /// <returns>A list of all locally known up addresses and submasks that are to be considered usable.</returns>
 371    public static IReadOnlyList<IPData> FilterBindSettings(NetworkConfiguration config, IList<IPData> interfaces, bool i
 372    {
 373        // Respect explicit bind addresses
 104374        var localNetworkAddresses = config.LocalNetworkAddresses;
 104375        if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
 376        {
 35377            var bindAddresses = localNetworkAddresses.Select(p => NetworkUtils.TryParseToSubnet(p, out var network)
 35378                    ? network.Address
 35379                    : (interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
 35380                        .Select(x => x.Address)
 35381                        .FirstOrDefault() ?? IPAddress.None))
 35382                .Where(x => x != IPAddress.None)
 35383                .ToHashSet();
 35384            interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
 385
 35386            if (bindAddresses.Contains(IPAddress.Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.Loopback))
 387            {
 0388                interfaces.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
 389            }
 390
 35391            if (bindAddresses.Contains(IPAddress.IPv6Loopback) && !interfaces.Any(i => i.Address.Equals(IPAddress.IPv6Lo
 392            {
 0393                interfaces.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
 394            }
 395        }
 396
 397        // Remove all interfaces matching any virtual machine interface prefix
 104398        if (config.IgnoreVirtualInterfaces)
 399        {
 400            // Remove potentially existing * and split config string into prefixes
 104401            var virtualInterfacePrefixes = config.VirtualInterfaceNames
 104402                .Select(i => i.Replace("*", string.Empty, StringComparison.OrdinalIgnoreCase));
 403
 404            // Check all interfaces for matches against the prefixes and remove them
 104405            if (interfaces.Count > 0)
 406            {
 416407                foreach (var virtualInterfacePrefix in virtualInterfacePrefixes)
 408                {
 104409                    interfaces.RemoveAll(x => x.Name.StartsWith(virtualInterfacePrefix, StringComparison.OrdinalIgnoreCa
 410                }
 411            }
 412        }
 413
 414        // Remove all IPv4 interfaces if IPv4 is disabled
 104415        if (!isIPv4Enabled)
 416        {
 0417            interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetwork);
 418        }
 419
 420        // Remove all IPv6 interfaces if IPv6 is disabled
 104421        if (!isIPv6Enabled)
 422        {
 80423            interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
 424        }
 425
 426        // Users may have complex networking configuration that multiple interfaces sharing the same IP address
 427        // Only return one IP for binding, and let the OS handle the rest
 104428        return interfaces.DistinctBy(iface => iface.Address).ToList();
 429    }
 430
 431    /// <summary>
 432    /// Initializes the remote address values.
 433    /// </summary>
 434    private void InitializeRemote(NetworkConfiguration config)
 104435    {
 436        lock (_initLock)
 437        {
 438            // Parse config values into filter collection
 104439            var remoteIPFilter = config.RemoteIPFilter;
 104440            if (remoteIPFilter.Length != 0 && !string.IsNullOrWhiteSpace(remoteIPFilter[0]))
 441            {
 442                // Parse all IPs with netmask to a subnet
 6443                var remoteAddressFilter = new List<IPNetwork>();
 6444                var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase
 6445                if (NetworkUtils.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
 446                {
 0447                    remoteAddressFilter = remoteAddressFilterResult.Select(x => x.Subnet).ToList();
 448                }
 449
 450                // Parse everything else as an IP and construct subnet with a single IP
 6451                var remoteFilteredIPs = remoteIPFilter.Where(x => !x.Contains('/', StringComparison.OrdinalIgnoreCase));
 28452                foreach (var ip in remoteFilteredIPs)
 453                {
 8454                    if (IPAddress.TryParse(ip, out var ipp))
 455                    {
 8456                        remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? Net
 457                    }
 458                }
 459
 6460                _remoteAddressFilter = remoteAddressFilter;
 461            }
 104462        }
 104463    }
 464
 465    /// <summary>
 466    /// Parses the user defined overrides into the dictionary object.
 467    /// Overrides are the equivalent of localised publishedServerUrl, enabling
 468    /// different addresses to be advertised over different subnets.
 469    /// format is subnet=ipaddress|host|uri
 470    /// when subnet = 0.0.0.0, any external address matches.
 471    /// </summary>
 472    private void InitializeOverrides(NetworkConfiguration config)
 104473    {
 474        lock (_initLock)
 475        {
 104476            var publishedServerUrls = new List<PublishedServerUriOverride>();
 477
 478            // Prefer startup configuration.
 104479            var startupOverrideKey = _startupConfig[AddressOverrideKey];
 104480            if (!string.IsNullOrEmpty(startupOverrideKey))
 481            {
 1482                publishedServerUrls.Add(
 1483                    new PublishedServerUriOverride(
 1484                        new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
 1485                        startupOverrideKey,
 1486                        true,
 1487                        true));
 1488                publishedServerUrls.Add(
 1489                    new PublishedServerUriOverride(
 1490                        new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
 1491                        startupOverrideKey,
 1492                        true,
 1493                        true));
 1494                WarnIfPublishedUrlBasePathDiffers(publishedServerUrls, config.BaseUrl);
 1495                _publishedServerUrls = publishedServerUrls;
 1496                return;
 497            }
 498
 103499            var overrides = config.PublishedServerUriBySubnet;
 247500            foreach (var entry in overrides)
 501            {
 29502                var parts = entry.Split('=');
 29503                if (parts.Length != 2)
 504                {
 0505                    _logger.LogError("Unable to parse bind override: {Entry}", entry);
 0506                    return;
 507                }
 508
 29509                var replacement = parts[1].Trim();
 29510                var identifier = parts[0];
 29511                if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
 512                {
 513                    // Drop any other overrides in case an "all" override exists
 17514                    publishedServerUrls.Clear();
 17515                    publishedServerUrls.Add(
 17516                        new PublishedServerUriOverride(
 17517                            new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
 17518                            replacement,
 17519                            true,
 17520                            true));
 17521                    publishedServerUrls.Add(
 17522                        new PublishedServerUriOverride(
 17523                            new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
 17524                            replacement,
 17525                            true,
 17526                            true));
 17527                    break;
 528                }
 12529                else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
 530                {
 5531                    publishedServerUrls.Add(
 5532                        new PublishedServerUriOverride(
 5533                            new IPData(IPAddress.Any, NetworkConstants.IPv4Any),
 5534                            replacement,
 5535                            false,
 5536                            true));
 5537                    publishedServerUrls.Add(
 5538                        new PublishedServerUriOverride(
 5539                            new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any),
 5540                            replacement,
 5541                            false,
 5542                            true));
 543                }
 7544                else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
 545                {
 16546                    foreach (var lan in _lanSubnets)
 547                    {
 4548                        var lanPrefix = lan.BaseAddress;
 4549                        publishedServerUrls.Add(
 4550                            new PublishedServerUriOverride(
 4551                                new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
 4552                                replacement,
 4553                                true,
 4554                                false));
 555                    }
 556                }
 3557                else if (NetworkUtils.TryParseToSubnet(identifier, out var result))
 558                {
 2559                    publishedServerUrls.Add(
 2560                        new PublishedServerUriOverride(
 2561                            result,
 2562                            replacement,
 2563                            true,
 2564                            true));
 565                }
 1566                else if (TryParseInterface(identifier, out var ifaces))
 567                {
 4568                    foreach (var iface in ifaces)
 569                    {
 1570                        publishedServerUrls.Add(
 1571                            new PublishedServerUriOverride(
 1572                                iface,
 1573                                replacement,
 1574                                true,
 1575                                true));
 576                    }
 577                }
 578                else
 579                {
 0580                    _logger.LogError("Unable to parse bind override: {Entry}", entry);
 581                }
 582            }
 583
 103584            WarnIfPublishedUrlBasePathDiffers(publishedServerUrls, config.BaseUrl);
 103585            _publishedServerUrls = publishedServerUrls;
 103586        }
 104587    }
 588
 589    /// <summary>
 590    /// Warns when a full-URL published server override uses a public path that differs from the configured base
 591    /// URL. Jellyfin appends the base URL to generated Live TV client URLs in this case, which can conflict with
 592    /// reverse proxies that translate public request paths. Bare host/IP overrides are exempt because the base URL
 593    /// is appended when the API URL is built from them.
 594    /// </summary>
 595    /// <param name="publishedServerUrls">The parsed published server URL overrides.</param>
 596    /// <param name="baseUrl">The configured base URL, if any.</param>
 597    private void WarnIfPublishedUrlBasePathDiffers(List<PublishedServerUriOverride> publishedServerUrls, string baseUrl)
 598    {
 104599        if (string.IsNullOrEmpty(baseUrl))
 600        {
 93601            return;
 602        }
 603
 44604        foreach (var overrideUri in publishedServerUrls.Select(x => x.OverrideUri).Distinct(StringComparer.OrdinalIgnore
 605        {
 11606            if (!overrideUri.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
 11607                && !overrideUri.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
 608            {
 609                continue;
 610            }
 611
 9612            if (!Uri.TryCreate(overrideUri, UriKind.Absolute, out var uri))
 613            {
 614                continue;
 615            }
 616
 9617            var path = Uri.UnescapeDataString(uri.AbsolutePath).TrimEnd('/');
 9618            if (path.EndsWith(baseUrl, StringComparison.OrdinalIgnoreCase))
 619            {
 620                continue;
 621            }
 622
 5623            var publishedServerHost = uri.GetComponents(UriComponents.HostAndPort, UriFormat.Unescaped);
 5624            _logger.LogWarning(
 5625                "The published server URL for host '{PublishedServerHost}' does not end with the configured base URL '{B
 5626                publishedServerHost,
 5627                baseUrl);
 628        }
 11629    }
 630
 631    private void ConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs evt)
 632    {
 23633        if (evt.Key.Equals(NetworkConfigurationStore.StoreKey, StringComparison.Ordinal))
 634        {
 0635            UpdateSettings((NetworkConfiguration)evt.NewConfiguration);
 636        }
 23637    }
 638
 639    /// <summary>
 640    /// Reloads all settings and re-Initializes the instance.
 641    /// </summary>
 642    /// <param name="configuration">The <see cref="NetworkConfiguration"/> to use.</param>
 643    [MemberNotNull(nameof(_lanSubnets), nameof(_excludedSubnets))]
 644    public void UpdateSettings(object configuration)
 645    {
 104646        ArgumentNullException.ThrowIfNull(configuration);
 647
 104648        var config = (NetworkConfiguration)configuration;
 104649        HappyEyeballs.HttpClientExtension.UseIPv6 = config.EnableIPv6;
 650
 104651        InitializeLan(config);
 104652        InitializeRemote(config);
 653
 104654        if (string.IsNullOrEmpty(MockNetworkSettings))
 655        {
 38656            InitializeInterfaces();
 657        }
 658        else // Used in testing only.
 659        {
 660            // Format is <IPAddress>,<Index>,<Name>: <next interface>. Set index to -ve to simulate a gateway.
 66661            var interfaceList = MockNetworkSettings.Split('|');
 66662            var interfaces = new List<IPData>();
 372663            foreach (var details in interfaceList)
 664            {
 120665                var parts = details.Split(',');
 120666                if (NetworkUtils.TryParseToSubnet(parts[0], out var data))
 667                {
 120668                    data.Index = int.Parse(parts[1], CultureInfo.InvariantCulture);
 120669                    if (data.AddressFamily == AddressFamily.InterNetwork || data.AddressFamily == AddressFamily.InterNet
 670                    {
 120671                        data.Name = parts[2];
 120672                        interfaces.Add(data);
 673                    }
 674                }
 675                else
 676                {
 0677                    _logger.LogWarning("Could not parse mock interface settings: {Part}", details);
 678                }
 679            }
 680
 66681            _interfaces = interfaces;
 682        }
 683
 104684        EnforceBindSettings(config);
 104685        InitializeOverrides(config);
 686
 104687        PrintNetworkInformation(config, false);
 104688    }
 689
 690    /// <summary>
 691    /// Protected implementation of Dispose pattern.
 692    /// </summary>
 693    /// <param name="disposing"><c>True</c> to dispose the managed state.</param>
 694    protected virtual void Dispose(bool disposing)
 695    {
 82696        if (!_disposed)
 697        {
 82698            if (disposing)
 699            {
 82700                _configurationManager.NamedConfigurationUpdated -= ConfigurationUpdated;
 82701                NetworkChange.NetworkAddressChanged -= OnNetworkAddressChanged;
 82702                NetworkChange.NetworkAvailabilityChanged -= OnNetworkAvailabilityChanged;
 703            }
 704
 82705            _disposed = true;
 706        }
 82707    }
 708
 709    /// <inheritdoc/>
 710    public bool TryParseInterface(string intf, [NotNullWhen(true)] out IReadOnlyList<IPData>? result)
 711    {
 15712        if (string.IsNullOrEmpty(intf)
 15713            || _interfaces is null
 15714            || _interfaces.Count == 0)
 715        {
 0716            result = null;
 0717            return false;
 718        }
 719
 720        // Match all interfaces starting with names starting with token
 15721        result = _interfaces
 15722            .Where(i => i.Name.Equals(intf, StringComparison.OrdinalIgnoreCase)
 15723                        && ((IsIPv4Enabled && i.Address.AddressFamily == AddressFamily.InterNetwork)
 15724                            || (IsIPv6Enabled && i.Address.AddressFamily == AddressFamily.InterNetworkV6)))
 15725            .OrderBy(x => x.Index)
 15726            .ToArray();
 15727        return result.Count > 0;
 728    }
 729
 730    /// <inheritdoc/>
 731    public RemoteAccessPolicyResult ShouldAllowServerAccess(IPAddress remoteIP)
 732    {
 9733        var config = _configurationManager.GetNetworkConfiguration();
 9734        if (IsInLocalNetwork(remoteIP))
 735        {
 1736            return RemoteAccessPolicyResult.Allow;
 737        }
 738
 8739        if (!config.EnableRemoteAccess)
 740        {
 741            // Remote not enabled. So everyone should be LAN.
 2742            return RemoteAccessPolicyResult.RejectDueToRemoteAccessDisabled;
 743        }
 744
 6745        if (!_remoteAddressFilter.Any())
 746        {
 747            // No filter on remote addresses, allow any of them.
 2748            return RemoteAccessPolicyResult.Allow;
 749        }
 750
 751        // Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remot
 752        // If left blank, all remote addresses will be allowed.
 753
 754        // remoteAddressFilter is a whitelist or blacklist.
 4755        var anyMatches = _remoteAddressFilter.Any(remoteNetwork => NetworkUtils.SubnetContainsAddress(remoteNetwork, rem
 4756        if (config.IsRemoteIPFilterBlacklist)
 757        {
 2758            return anyMatches
 2759                ? RemoteAccessPolicyResult.RejectDueToIPBlocklist
 2760                : RemoteAccessPolicyResult.Allow;
 761        }
 762
 763        // Allow-list
 2764        return anyMatches
 2765            ? RemoteAccessPolicyResult.Allow
 2766            : RemoteAccessPolicyResult.RejectDueToNotAllowlistedRemoteIP;
 767    }
 768
 769    /// <inheritdoc/>
 770    public IReadOnlyList<IPData> GetLoopbacks()
 771    {
 0772        if (!IsIPv4Enabled && !IsIPv6Enabled)
 773        {
 0774            return Array.Empty<IPData>();
 775        }
 776
 0777        var loopbackNetworks = new List<IPData>();
 0778        if (IsIPv4Enabled)
 779        {
 0780            loopbackNetworks.Add(new IPData(IPAddress.Loopback, NetworkConstants.IPv4RFC5735Loopback, "lo"));
 781        }
 782
 0783        if (IsIPv6Enabled)
 784        {
 0785            loopbackNetworks.Add(new IPData(IPAddress.IPv6Loopback, NetworkConstants.IPv6RFC4291Loopback, "lo"));
 786        }
 787
 0788        return loopbackNetworks;
 789    }
 790
 791    /// <inheritdoc/>
 792    public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
 793    {
 22794        return NetworkManager.GetAllBindInterfaces(_logger, individualInterfaces, _configurationManager, _interfaces, Is
 795    }
 796
 797    /// <summary>
 798    /// Reads the jellyfin configuration of the configuration manager and produces a list of interfaces that should be b
 799    /// </summary>
 800    /// <param name="logger">Logger to use for messages.</param>
 801    /// <param name="individualInterfaces">Defines that only known interfaces should be used.</param>
 802    /// <param name="configurationManager">The ConfigurationManager.</param>
 803    /// <param name="knownInterfaces">The known interfaces that gets returned if possible or instructed.</param>
 804    /// <param name="readIpv4">Include IPV4 type interfaces.</param>
 805    /// <param name="readIpv6">Include IPV6 type interfaces.</param>
 806    /// <returns>A list of ip address of which jellyfin should bind to.</returns>
 807    public static IReadOnlyList<IPData> GetAllBindInterfaces(
 808        ILogger<NetworkManager> logger,
 809        bool individualInterfaces,
 810        IConfigurationManager configurationManager,
 811        IReadOnlyList<IPData> knownInterfaces,
 812        bool readIpv4,
 813        bool readIpv6)
 814    {
 22815        var config = configurationManager.GetNetworkConfiguration();
 22816        var localNetworkAddresses = config.LocalNetworkAddresses;
 22817        if ((localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]) && knownInterfaces
 818        {
 0819            return knownInterfaces;
 820        }
 821
 822        // TODO: remove when upgrade to dotnet 11 is done
 22823        if (readIpv6 && !Socket.OSSupportsIPv6)
 824        {
 0825            logger.LogWarning("IPv6 Unsupported by OS, not listening on IPv6");
 0826            readIpv6 = false;
 827        }
 828
 829        // No bind address and no exclusions, so listen on all interfaces.
 22830        var result = new List<IPData>();
 22831        if (readIpv4 && readIpv6)
 832        {
 833            // Kestrel source code shows it uses Sockets.DualMode - so this also covers IPAddress.Any by default
 0834            result.Add(new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any));
 835        }
 22836        else if (readIpv4)
 837        {
 22838            result.Add(new IPData(IPAddress.Any, NetworkConstants.IPv4Any));
 839        }
 0840        else if (readIpv6)
 841        {
 842            // Cannot use IPv6Any as Kestrel will bind to IPv4 addresses too.
 0843            foreach (var iface in knownInterfaces)
 844            {
 0845                if (iface.AddressFamily == AddressFamily.InterNetworkV6)
 846                {
 0847                    result.Add(iface);
 848                }
 849            }
 850        }
 851
 22852        return result;
 853    }
 854
 855    /// <inheritdoc/>
 856    public string GetBindAddress(string source, out int? port)
 857    {
 26858        if (!NetworkUtils.TryParseHost(source, out var addresses, IsIPv4Enabled, IsIPv6Enabled))
 859        {
 4860            addresses = Array.Empty<IPAddress>();
 861        }
 862
 26863        var result = GetBindAddress(addresses.FirstOrDefault(), out port);
 26864        return result;
 865    }
 866
 867    /// <inheritdoc/>
 868    public string GetBindAddress(HttpRequest source, out int? port)
 869    {
 2870        var result = GetBindAddress(source.Host.Host, out port);
 2871        port ??= source.Host.Port;
 872
 2873        return result;
 874    }
 875
 876    /// <inheritdoc/>
 877    public string GetBindAddress(IPAddress? source, out int? port, bool skipOverrides = false)
 878    {
 35879        port = null;
 880
 881        string result;
 882
 35883        if (source is not null)
 884        {
 30885            if (IsIPv4Enabled && !IsIPv6Enabled && source.AddressFamily == AddressFamily.InterNetworkV6)
 886            {
 0887                _logger.LogWarning("IPv6 is disabled in Jellyfin, but enabled in the OS. This may affect how the interfa
 888            }
 889
 30890            if (!IsIPv4Enabled && IsIPv6Enabled && source.AddressFamily == AddressFamily.InterNetwork)
 891            {
 0892                _logger.LogWarning("IPv4 is disabled in Jellyfin, but enabled in the OS. This may affect how the interfa
 893            }
 894
 30895            bool isExternal = !IsInLocalNetwork(source);
 30896            _logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExtern
 897
 30898            if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result, out port))
 899            {
 15900                return result;
 901            }
 902
 903            // No preference given, so move on to bind addresses.
 15904            if (MatchesBindInterface(source, isExternal, out result))
 905            {
 12906                return result;
 907            }
 908
 3909            if (isExternal && MatchesExternalInterface(source, out result))
 910            {
 0911                return result;
 912            }
 913        }
 914
 915        // Get the first LAN interface address that's not excluded and not a loopback address.
 916        // Get all available interfaces, prefer local interfaces
 8917        var availableInterfaces = _interfaces.Where(x => !IPAddress.IsLoopback(x.Address))
 8918            .OrderByDescending(x => IsInLocalNetwork(x.Address))
 8919            .ThenBy(x => x.Index)
 8920            .ToList();
 921
 8922        if (availableInterfaces.Count == 0)
 923        {
 924            // There isn't any others, so we'll use the loopback.
 925            // Prefer loopback address matching the source's address family
 0926            if (source is not null && source.AddressFamily == AddressFamily.InterNetwork && IsIPv4Enabled)
 927            {
 0928                result = "127.0.0.1";
 929            }
 0930            else if (source is not null && source.AddressFamily == AddressFamily.InterNetworkV6 && IsIPv6Enabled)
 931            {
 0932                result = "::1";
 933            }
 934            else
 935            {
 0936                result = IsIPv4Enabled ? "127.0.0.1" : "::1";
 937            }
 938
 0939            _logger.LogWarning("{Source}: Only loopback {Result} returned, using that as bind address.", source, result)
 0940            return result;
 941        }
 942
 943        // If no source address is given, use the preferred (first) interface
 8944        if (source is null)
 945        {
 5946            result = NetworkUtils.FormatIPString(availableInterfaces.First().Address);
 5947            _logger.LogDebug("{Source}: Using first internal interface as bind address: {Result}", source, result);
 5948            return result;
 949        }
 950
 951        // Does the request originate in one of the interface subnets?
 952        // (For systems with multiple internal network cards, and multiple subnets)
 12953        foreach (var intf in availableInterfaces)
 954        {
 3955            if (NetworkUtils.SubnetContainsAddress(intf.Subnet, source))
 956            {
 0957                result = NetworkUtils.FormatIPString(intf.Address);
 0958                _logger.LogDebug("{Source}: Found interface with matching subnet, using it as bind address: {Result}", s
 0959                return result;
 960            }
 961        }
 962
 963        // Fallback to an interface matching the source's address family, or first available
 3964        var preferredInterface = availableInterfaces
 3965            .FirstOrDefault(x => x.Address.AddressFamily == source.AddressFamily);
 966
 3967        if (preferredInterface is not null)
 968        {
 3969            result = NetworkUtils.FormatIPString(preferredInterface.Address);
 3970            _logger.LogDebug("{Source}: No matching subnet found, using interface with matching address family: {Result}
 3971            return result;
 972        }
 973
 0974        result = NetworkUtils.FormatIPString(availableInterfaces[0].Address);
 0975        _logger.LogDebug("{Source}: No matching interfaces found, using first available interface as bind address: {Resu
 0976        return result;
 0977    }
 978
 979    /// <inheritdoc/>
 980    public IReadOnlyList<IPData> GetInternalBindAddresses()
 981    {
 982        // Select all local bind addresses
 6983        return _interfaces.Where(x => IsInLocalNetwork(x.Address))
 6984            .OrderBy(x => x.Index)
 6985            .ToList();
 986    }
 987
 988    /// <inheritdoc/>
 989    public bool IsInLocalNetwork(string address)
 990    {
 0991        if (NetworkUtils.TryParseToSubnet(address, out var subnet))
 992        {
 0993            return IsInLocalNetwork(subnet.Address);
 994        }
 995
 0996        return NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled)
 0997               && addresses.Any(IsInLocalNetwork);
 998    }
 999
 1000    /// <summary>
 1001    ///  Get if the IPAddress is Link-local.
 1002    /// </summary>
 1003    /// <param name="address">The IP Address.</param>
 1004    /// <returns>Bool indicates if the address is link-local.</returns>
 1005    public bool IsLinkLocalAddress(IPAddress address)
 1006    {
 41007        ArgumentNullException.ThrowIfNull(address);
 41008        return NetworkConstants.IPv4RFC3927LinkLocal.Contains(address) || address.IsIPv6LinkLocal;
 1009    }
 1010
 1011    /// <inheritdoc/>
 1012    public bool IsInLocalNetwork(IPAddress address)
 1013    {
 2241014        ArgumentNullException.ThrowIfNull(address);
 1015
 1016        // Map IPv6 mapped IPv4 back to IPv4 (happens if Kestrel runs in dual-socket mode)
 2241017        if (address.IsIPv4MappedToIPv6)
 1018        {
 01019            address = address.MapToIPv4();
 1020        }
 1021
 2241022        if ((TrustAllIPv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
 2241023            || IPAddress.IsLoopback(address))
 1024        {
 1311025            return true;
 1026        }
 1027
 1028        // As private addresses can be redefined by Configuration.LocalNetworkAddresses
 931029        return CheckIfLanAndNotExcluded(address);
 1030    }
 1031
 1032    /// <summary>
 1033    /// Check if the address is in the LAN and not excluded.
 1034    /// </summary>
 1035    /// <param name="address">The IP address to check. The caller should make sure this is not an IPv4MappedToIPv6 addre
 1036    /// <returns>Boolean indicates whether the address is in LAN.</returns>
 1037    private bool CheckIfLanAndNotExcluded(IPAddress address)
 1038    {
 4761039        foreach (var lanSubnet in _lanSubnets)
 1040        {
 1681041            if (lanSubnet.Contains(address))
 1042            {
 981043                foreach (var excludedSubnet in _excludedSubnets)
 1044                {
 41045                    if (excludedSubnet.Contains(address))
 1046                    {
 21047                        return false;
 1048                    }
 1049                }
 1050
 441051                return true;
 1052            }
 1053        }
 1054
 471055        return false;
 461056    }
 1057
 1058    /// <summary>
 1059    /// Attempts to match the source against the published server URL overrides.
 1060    /// </summary>
 1061    /// <param name="source">IP source address to use.</param>
 1062    /// <param name="isInExternalSubnet">True if the source is in an external subnet.</param>
 1063    /// <param name="bindPreference">The published server URL that matches the source address.</param>
 1064    /// <param name="port">The explicit port parsed from the override, if any.</param>
 1065    /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
 1066    private bool MatchesPublishedServerUrl(IPAddress source, bool isInExternalSubnet, out string bindPreference, out int
 1067    {
 301068        bindPreference = string.Empty;
 301069        port = null;
 1070
 1071        // Only consider subnets including the source IP, preferring specific overrides
 1072        List<PublishedServerUriOverride> validPublishedServerUrls;
 301073        if (!isInExternalSubnet)
 1074        {
 1075            // Only use matching internal subnets
 1076            // Prefer more specific (bigger subnet prefix) overrides
 141077            validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsInternalOverride && NetworkUtils.SubnetContai
 141078                .OrderByDescending(x => x.Data.Subnet.PrefixLength)
 141079                .ToList();
 1080        }
 1081        else
 1082        {
 1083            // Only use matching external subnets
 1084            // Prefer more specific (bigger subnet prefix) overrides
 161085            validPublishedServerUrls = _publishedServerUrls.Where(x => x.IsExternalOverride && NetworkUtils.SubnetContai
 161086                .OrderByDescending(x => x.Data.Subnet.PrefixLength)
 161087                .ToList();
 1088        }
 1089
 751090        foreach (var data in validPublishedServerUrls)
 1091        {
 1092            // Get interface matching override subnet
 151093            var intf = _interfaces.OrderBy(x => x.Index).FirstOrDefault(x => NetworkUtils.SubnetContainsAddress(data.Dat
 1094
 151095            if (intf?.Address is not null
 151096                || (data.Data.AddressFamily == AddressFamily.InterNetwork && data.Data.Address.Equals(IPAddress.Any))
 151097                || (data.Data.AddressFamily == AddressFamily.InterNetworkV6 && data.Data.Address.Equals(IPAddress.IPv6An
 1098            {
 1099                // If matching interface is found, use override
 151100                bindPreference = data.OverrideUri;
 151101                break;
 1102            }
 1103        }
 1104
 301105        if (string.IsNullOrEmpty(bindPreference))
 1106        {
 151107            _logger.LogDebug("{Source}: No matching bind address override found", source);
 151108            return false;
 1109        }
 1110
 1111        // Handle override specifying an explicit port.
 151112        (bindPreference, port) = ParseHostAndPort(bindPreference);
 1113
 151114        if (port.HasValue)
 1115        {
 71116            _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference,
 1117        }
 1118        else
 1119        {
 81120            _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference);
 1121        }
 1122
 151123        return true;
 1124    }
 1125
 1126    /// <summary>
 1127    /// Splits a published server URL override into its host and explicit port, if any.
 1128    /// Full URLs (containing "://") are returned whole, with any port left embedded.
 1129    /// </summary>
 1130    /// <param name="value">The override value, e.g. "host:port", "[::1]:port", or a full URL.</param>
 1131    /// <returns>The parsed host (or the original value if not split) and the explicit port, if any.</returns>
 1132    private static (string Host, int? Port) ParseHostAndPort(string value)
 1133    {
 151134        if (value.Contains("://", StringComparison.Ordinal))
 1135        {
 61136            return (value, null);
 1137        }
 1138
 91139        if (Uri.TryCreate("any://" + value, UriKind.Absolute, out var parsed) && parsed.Port != -1)
 1140        {
 71141            return (parsed.DnsSafeHost, parsed.Port);
 1142        }
 1143
 21144        return (value, null);
 1145    }
 1146
 1147    /// <summary>
 1148    /// Attempts to match the source against the user defined bind interfaces.
 1149    /// </summary>
 1150    /// <param name="source">IP source address to use.</param>
 1151    /// <param name="isInExternalSubnet">True if the source is in the external subnet.</param>
 1152    /// <param name="result">The result, if a match is found.</param>
 1153    /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
 1154    private bool MatchesBindInterface(IPAddress source, bool isInExternalSubnet, out string result)
 1155    {
 151156        result = string.Empty;
 1157
 151158        int count = _interfaces.Count;
 151159        if (count == 1 && (_interfaces[0].Address.Equals(IPAddress.Any) || _interfaces[0].Address.Equals(IPAddress.IPv6A
 1160        {
 1161            // Ignore IPAny addresses.
 01162            count = 0;
 1163        }
 1164
 151165        if (count == 0)
 1166        {
 01167            return false;
 1168        }
 1169
 151170        IPAddress? bindAddress = null;
 151171        if (isInExternalSubnet)
 1172        {
 61173            var externalInterfaces = _interfaces.Where(x => !IsInLocalNetwork(x.Address))
 61174                .Where(x => !IsLinkLocalAddress(x.Address))
 61175                .OrderBy(x => x.Index)
 61176                .ToList();
 61177            if (externalInterfaces.Count > 0)
 1178            {
 1179                // Check to see if any of the external bind interfaces are in the same subnet as the source.
 1180                // If none exists, this will select the first external interface if there is one.
 41181                bindAddress = externalInterfaces
 41182                    .OrderByDescending(x => NetworkUtils.SubnetContainsAddress(x.Subnet, source))
 41183                    .ThenByDescending(x => x.Subnet.PrefixLength)
 41184                    .ThenBy(x => x.Index)
 41185                    .Select(x => x.Address)
 41186                    .First();
 1187
 41188                result = NetworkUtils.FormatIPString(bindAddress);
 41189                _logger.LogDebug("{Source}: External request received, matching external bind address found: {Result}", 
 41190                return true;
 1191            }
 1192
 21193            _logger.LogDebug("{Source}: External request received, no matching external bind address found, trying inter
 1194        }
 1195        else
 1196        {
 1197            // Check to see if any of the internal bind interfaces are in the same subnet as the source.
 1198            // If none exists, this will select the first internal interface if there is one.
 91199            bindAddress = _interfaces.Where(x => IsInLocalNetwork(x.Address))
 91200                .OrderByDescending(x => NetworkUtils.SubnetContainsAddress(x.Subnet, source))
 91201                .ThenByDescending(x => x.Subnet.PrefixLength)
 91202                .ThenBy(x => x.Index)
 91203                .Select(x => x.Address)
 91204                .FirstOrDefault();
 1205
 91206            if (bindAddress is not null)
 1207            {
 81208                result = NetworkUtils.FormatIPString(bindAddress);
 81209                _logger.LogDebug("{Source}: Internal request received, matching internal bind address found: {Result}", 
 81210                return true;
 1211            }
 1212        }
 1213
 31214        return false;
 1215    }
 1216
 1217    /// <summary>
 1218    /// Attempts to match the source against external interfaces.
 1219    /// </summary>
 1220    /// <param name="source">IP source address to use.</param>
 1221    /// <param name="result">The result, if a match is found.</param>
 1222    /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns>
 1223    private bool MatchesExternalInterface(IPAddress source, out string result)
 1224    {
 1225        // Get the first external interface address that isn't a loopback.
 21226        var extResult = _interfaces
 21227            .Where(p => !IsInLocalNetwork(p.Address))
 21228            .Where(p => p.Address.AddressFamily.Equals(source.AddressFamily))
 21229            .Where(p => !IsLinkLocalAddress(p.Address))
 21230            .OrderBy(x => x.Index).ToArray();
 1231
 1232        // No external interface found
 21233        if (extResult.Length == 0)
 1234        {
 21235            result = string.Empty;
 21236            _logger.LogDebug("{Source}: External request received, but no external interface found. Need to route throug
 21237            return false;
 1238        }
 1239
 1240        // Does the request originate in one of the interface subnets?
 1241        // (For systems with multiple network cards and/or multiple subnets)
 01242        foreach (var intf in extResult)
 1243        {
 01244            if (NetworkUtils.SubnetContainsAddress(intf.Subnet, source))
 1245            {
 01246                result = NetworkUtils.FormatIPString(intf.Address);
 01247                _logger.LogDebug("{Source}: Found external interface with matching subnet, using it as bind address: {Re
 01248                return true;
 1249            }
 1250        }
 1251
 1252        // Fallback to first external interface.
 01253        result = NetworkUtils.FormatIPString(extResult[0].Address);
 01254        _logger.LogDebug("{Source}: Using first external interface as bind address: {Result}", source, result);
 01255        return true;
 1256    }
 1257
 1258    private void PrintNetworkInformation(NetworkConfiguration config, bool debug = true)
 1259    {
 1041260        var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
 1041261        if (_logger.IsEnabled(logLevel))
 1262        {
 221263            _logger.Log(logLevel, "Defined LAN subnets: {Subnets}", _lanSubnets.Select(s => s.BaseAddress + "/" + s.Pref
 221264            _logger.Log(logLevel, "Defined LAN exclusions: {Subnets}", _excludedSubnets.Select(s => s.BaseAddress + "/" 
 221265            _logger.Log(logLevel, "Used LAN subnets: {Subnets}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).S
 221266            _logger.Log(logLevel, "Filtered interface addresses: {Addresses}", _interfaces.OrderByDescending(x => x.Addr
 221267            _logger.Log(logLevel, "Bind Addresses {Addresses}", GetAllBindInterfaces(false).OrderByDescending(x => x.Add
 221268            _logger.Log(logLevel, "Remote IP filter is {Type}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowl
 221269            _logger.Log(logLevel, "Filtered subnets: {Subnets}", _remoteAddressFilter.Select(s => s.BaseAddress + "/" + 
 1270        }
 1041271    }
 1272}

Methods/Properties

.ctor(MediaBrowser.Common.Configuration.IConfigurationManager,Microsoft.Extensions.Configuration.IConfiguration,Microsoft.Extensions.Logging.ILogger`1<Jellyfin.Networking.Manager.NetworkManager>)
.cctor()
get_IsIPv4Enabled()
get_IsIPv6Enabled()
get_PublishedServerUrls()
Dispose()
OnNetworkAvailabilityChanged(System.Object,System.Net.NetworkInformation.NetworkAvailabilityEventArgs)
OnNetworkAddressChanged(System.Object,System.EventArgs)
HandleNetworkChange()
OnNetworkChange()
InitializeInterfaces()
GetInterfacesCore(Microsoft.Extensions.Logging.ILogger,System.Boolean,System.Boolean)
InitializeLan(MediaBrowser.Common.Net.NetworkConfiguration)
EnforceBindSettings(MediaBrowser.Common.Net.NetworkConfiguration)
FilterBindSettings(MediaBrowser.Common.Net.NetworkConfiguration,System.Collections.Generic.IList`1<MediaBrowser.Model.Net.IPData>,System.Boolean,System.Boolean)
InitializeRemote(MediaBrowser.Common.Net.NetworkConfiguration)
InitializeOverrides(MediaBrowser.Common.Net.NetworkConfiguration)
WarnIfPublishedUrlBasePathDiffers(System.Collections.Generic.List`1<MediaBrowser.Model.Net.PublishedServerUriOverride>,System.String)
ConfigurationUpdated(System.Object,MediaBrowser.Common.Configuration.ConfigurationUpdateEventArgs)
UpdateSettings(System.Object)
Dispose(System.Boolean)
TryParseInterface(System.String,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Net.IPData>&)
ShouldAllowServerAccess(System.Net.IPAddress)
GetLoopbacks()
GetAllBindInterfaces(System.Boolean)
GetAllBindInterfaces(Microsoft.Extensions.Logging.ILogger`1<Jellyfin.Networking.Manager.NetworkManager>,System.Boolean,MediaBrowser.Common.Configuration.IConfigurationManager,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Net.IPData>,System.Boolean,System.Boolean)
GetBindAddress(System.String,System.Nullable`1<System.Int32>&)
GetBindAddress(Microsoft.AspNetCore.Http.HttpRequest,System.Nullable`1<System.Int32>&)
GetBindAddress(System.Net.IPAddress,System.Nullable`1<System.Int32>&,System.Boolean)
GetInternalBindAddresses()
IsInLocalNetwork(System.String)
IsLinkLocalAddress(System.Net.IPAddress)
IsInLocalNetwork(System.Net.IPAddress)
CheckIfLanAndNotExcluded(System.Net.IPAddress)
MatchesPublishedServerUrl(System.Net.IPAddress,System.Boolean,System.String&,System.Nullable`1<System.Int32>&)
ParseHostAndPort(System.String)
MatchesBindInterface(System.Net.IPAddress,System.Boolean,System.String&)
MatchesExternalInterface(System.Net.IPAddress,System.String&)
PrintNetworkInformation(MediaBrowser.Common.Net.NetworkConfiguration,System.Boolean)