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