Browse Source

Apply review suggestions

Shadowghost 2 years ago
parent
commit
a5bfeb28aa

+ 79 - 62
Jellyfin.Networking/Manager/NetworkManager.cs

@@ -26,11 +26,6 @@ namespace Jellyfin.Networking.Manager
         /// </summary>
         private readonly object _initLock;
 
-        /// <summary>
-        /// List of all interface MAC addresses.
-        /// </summary>
-        private readonly List<PhysicalAddress> _macAddresses;
-
         private readonly ILogger<NetworkManager> _logger;
 
         private readonly IConfigurationManager _configurationManager;
@@ -40,30 +35,35 @@ namespace Jellyfin.Networking.Manager
         /// <summary>
         /// Holds the published server URLs and the IPs to use them on.
         /// </summary>
-        private readonly Dictionary<IPData, string> _publishedServerUrls;
+        private IReadOnlyDictionary<IPData, string> _publishedServerUrls;
 
-        private List<IPNetwork> _remoteAddressFilter;
+        private IReadOnlyList<IPNetwork> _remoteAddressFilter;
 
         /// <summary>
         /// Used to stop "event-racing conditions".
         /// </summary>
         private bool _eventfire;
 
+        /// <summary>
+        /// List of all interface MAC addresses.
+        /// </summary>
+        private IReadOnlyList<PhysicalAddress> _macAddresses;
+
         /// <summary>
         /// Dictionary containing interface addresses and their subnets.
         /// </summary>
-        private List<IPData> _interfaces;
+        private IReadOnlyList<IPData> _interfaces;
 
         /// <summary>
         /// Unfiltered user defined LAN subnets (<see cref="NetworkConfiguration.LocalNetworkSubnets"/>)
         /// or internal interface network subnets if undefined by user.
         /// </summary>
-        private List<IPNetwork> _lanSubnets;
+        private IReadOnlyList<IPNetwork> _lanSubnets;
 
         /// <summary>
         /// User defined list of subnets to excluded from the LAN.
         /// </summary>
-        private List<IPNetwork> _excludedSubnets;
+        private IReadOnlyList<IPNetwork> _excludedSubnets;
 
         /// <summary>
         /// True if this object is disposed.
@@ -127,7 +127,7 @@ namespace Jellyfin.Networking.Manager
         /// <summary>
         /// Gets the Published server override list.
         /// </summary>
-        public Dictionary<IPData, string> PublishedServerUrls => _publishedServerUrls;
+        public IReadOnlyDictionary<IPData, string> PublishedServerUrls => _publishedServerUrls;
 
         /// <inheritdoc/>
         public void Dispose()
@@ -206,8 +206,8 @@ namespace Jellyfin.Networking.Manager
             {
                 _logger.LogDebug("Refreshing interfaces.");
 
-                _interfaces.Clear();
-                _macAddresses.Clear();
+                var interfaces = new List<IPData>();
+                var macAddresses = new List<PhysicalAddress>();
 
                 try
                 {
@@ -224,7 +224,7 @@ namespace Jellyfin.Networking.Manager
                             // Populate MAC list
                             if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && PhysicalAddress.None.Equals(mac))
                             {
-                                _macAddresses.Add(mac);
+                                macAddresses.Add(mac);
                             }
 
                             // Populate interface list
@@ -236,7 +236,7 @@ namespace Jellyfin.Networking.Manager
                                     interfaceObject.Index = ipProperties.GetIPv4Properties().Index;
                                     interfaceObject.Name = adapter.Name;
 
-                                    _interfaces.Add(interfaceObject);
+                                    interfaces.Add(interfaceObject);
                                 }
                                 else if (IsIPv6Enabled && info.Address.AddressFamily == AddressFamily.InterNetworkV6)
                                 {
@@ -244,7 +244,7 @@ namespace Jellyfin.Networking.Manager
                                     interfaceObject.Index = ipProperties.GetIPv6Properties().Index;
                                     interfaceObject.Name = adapter.Name;
 
-                                    _interfaces.Add(interfaceObject);
+                                    interfaces.Add(interfaceObject);
                                 }
                             }
                         }
@@ -265,23 +265,26 @@ namespace Jellyfin.Networking.Manager
                 }
 
                 // If no interfaces are found, fallback to loopback interfaces.
-                if (_interfaces.Count == 0)
+                if (interfaces.Count == 0)
                 {
                     _logger.LogWarning("No interface information available. Using loopback interface(s).");
 
                     if (IsIPv4Enabled && !IsIPv6Enabled)
                     {
-                        _interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
+                        interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
                     }
 
                     if (!IsIPv4Enabled && IsIPv6Enabled)
                     {
-                        _interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
+                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
                     }
                 }
 
-                _logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", _interfaces.Count);
-                _logger.LogDebug("Interfaces addresses: {Addresses}", _interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
+                _logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
+                _logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
+
+                _macAddresses = macAddresses;
+                _interfaces = interfaces;
             }
         }
 
@@ -297,37 +300,38 @@ namespace Jellyfin.Networking.Manager
                 // Get configuration options
                 var subnets = config.LocalNetworkSubnets;
 
-                if (!NetworkExtensions.TryParseToSubnets(subnets, out _lanSubnets, false))
-                {
-                    _lanSubnets.Clear();
-                }
-
-                if (!NetworkExtensions.TryParseToSubnets(subnets, out _excludedSubnets, true))
-                {
-                    _excludedSubnets.Clear();
-                }
-
                 // If no LAN addresses are specified, all private subnets and Loopback are deemed to be the LAN
-                if (_lanSubnets.Count == 0)
+                if (!NetworkExtensions.TryParseToSubnets(subnets, out var lanSubnets, false) || lanSubnets.Count == 0)
                 {
                     _logger.LogDebug("Using LAN interface addresses as user provided no LAN details.");
 
+                    var fallbackLanSubnets = new List<IPNetwork>();
                     if (IsIPv6Enabled)
                     {
-                        _lanSubnets.Add(new IPNetwork(IPAddress.IPv6Loopback, 128)); // RFC 4291 (Loopback)
-                        _lanSubnets.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10)); // RFC 4291 (Site local)
-                        _lanSubnets.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7)); // RFC 4193 (Unique local)
+                        fallbackLanSubnets.Add(new IPNetwork(IPAddress.IPv6Loopback, 128)); // RFC 4291 (Loopback)
+                        fallbackLanSubnets.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10)); // RFC 4291 (Site local)
+                        fallbackLanSubnets.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7)); // RFC 4193 (Unique local)
                     }
 
                     if (IsIPv4Enabled)
                     {
-                        _lanSubnets.Add(new IPNetwork(IPAddress.Loopback, 8)); // RFC 5735 (Loopback)
-                        _lanSubnets.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); // RFC 1918 (private)
-                        _lanSubnets.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)); // RFC 1918 (private)
-                        _lanSubnets.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16)); // RFC 1918 (private)
+                        fallbackLanSubnets.Add(new IPNetwork(IPAddress.Loopback, 8)); // RFC 5735 (Loopback)
+                        fallbackLanSubnets.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); // RFC 1918 (private)
+                        fallbackLanSubnets.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)); // RFC 1918 (private)
+                        fallbackLanSubnets.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16)); // RFC 1918 (private)
                     }
+
+                    _lanSubnets = fallbackLanSubnets;
+                }
+                else
+                {
+                    _lanSubnets = lanSubnets;
                 }
 
+                _excludedSubnets = NetworkExtensions.TryParseToSubnets(subnets, out var excludedSubnets, true)
+                    ? excludedSubnets
+                    : new List<IPNetwork>();
+
                 _logger.LogInformation("Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
                 _logger.LogInformation("Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
                 _logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
@@ -342,26 +346,27 @@ namespace Jellyfin.Networking.Manager
             lock (_initLock)
             {
                 // Respect explicit bind addresses
+                var interfaces = _interfaces.ToList();
                 var localNetworkAddresses = config.LocalNetworkAddresses;
                 if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
                 {
                     var bindAddresses = localNetworkAddresses.Select(p => NetworkExtensions.TryParseToSubnet(p, out var network)
                         ? network.Prefix
-                        : (_interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
+                        : (interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
                             .Select(x => x.Address)
                             .FirstOrDefault() ?? IPAddress.None))
                         .Where(x => x != IPAddress.None)
                         .ToHashSet();
-                    _interfaces = _interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
+                    interfaces = interfaces.Where(x => bindAddresses.Contains(x.Address)).ToList();
 
                     if (bindAddresses.Contains(IPAddress.Loopback))
                     {
-                        _interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
+                        interfaces.Add(new IPData(IPAddress.Loopback, new IPNetwork(IPAddress.Loopback, 8), "lo"));
                     }
 
                     if (bindAddresses.Contains(IPAddress.IPv6Loopback))
                     {
-                        _interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
+                        interfaces.Add(new IPData(IPAddress.IPv6Loopback, new IPNetwork(IPAddress.IPv6Loopback, 128), "lo"));
                     }
                 }
 
@@ -377,7 +382,7 @@ namespace Jellyfin.Networking.Manager
                     {
                         foreach (var virtualInterfacePrefix in virtualInterfacePrefixes)
                         {
-                            _interfaces.RemoveAll(x => x.Name.StartsWith(virtualInterfacePrefix, StringComparison.OrdinalIgnoreCase));
+                            interfaces.RemoveAll(x => x.Name.StartsWith(virtualInterfacePrefix, StringComparison.OrdinalIgnoreCase));
                         }
                     }
                 }
@@ -385,16 +390,17 @@ namespace Jellyfin.Networking.Manager
                 // Remove all IPv4 interfaces if IPv4 is disabled
                 if (!IsIPv4Enabled)
                 {
-                    _interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetwork);
+                    interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetwork);
                 }
 
                 // Remove all IPv6 interfaces if IPv6 is disabled
                 if (!IsIPv6Enabled)
                 {
-                    _interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
+                    interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
                 }
 
-                _logger.LogInformation("Using bind addresses: {0}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
+                _logger.LogInformation("Using bind addresses: {0}", interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
+                _interfaces = interfaces;
             }
         }
 
@@ -410,10 +416,11 @@ namespace Jellyfin.Networking.Manager
                 if (remoteIPFilter.Any() && !string.IsNullOrWhiteSpace(remoteIPFilter.First()))
                 {
                     // Parse all IPs with netmask to a subnet
+                    var remoteAddressFilter = new List<IPNetwork>();
                     var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray();
-                    if (!NetworkExtensions.TryParseToSubnets(remoteFilteredSubnets, out _remoteAddressFilter, false))
+                    if (NetworkExtensions.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
                     {
-                        _remoteAddressFilter.Clear();
+                        remoteAddressFilter = remoteAddressFilterResult.ToList();
                     }
 
                     // Parse everything else as an IP and construct subnet with a single IP
@@ -422,9 +429,11 @@ namespace Jellyfin.Networking.Manager
                     {
                         if (IPAddress.TryParse(ip, out var ipp))
                         {
-                            _remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? 32 : 128));
+                            remoteAddressFilter.Add(new IPNetwork(ipp, ipp.AddressFamily == AddressFamily.InterNetwork ? 32 : 128));
                         }
                     }
+
+                    _remoteAddressFilter = remoteAddressFilter;
                 }
             }
         }
@@ -440,8 +449,8 @@ namespace Jellyfin.Networking.Manager
         {
             lock (_initLock)
             {
-                _publishedServerUrls.Clear();
-                string[] overrides = config.PublishedServerUriBySubnet;
+                var publishedServerUrls = new Dictionary<IPData, string>();
+                var overrides = config.PublishedServerUriBySubnet;
 
                 foreach (var entry in overrides)
                 {
@@ -456,31 +465,31 @@ namespace Jellyfin.Networking.Manager
                     var identifier = parts[0];
                     if (string.Equals(identifier, "all", StringComparison.OrdinalIgnoreCase))
                     {
-                        _publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
+                        publishedServerUrls[new IPData(IPAddress.Broadcast, null)] = replacement;
                     }
                     else if (string.Equals(identifier, "external", StringComparison.OrdinalIgnoreCase))
                     {
-                        _publishedServerUrls[new IPData(IPAddress.Any, new IPNetwork(IPAddress.Any, 0))] = replacement;
-                        _publishedServerUrls[new IPData(IPAddress.IPv6Any, new IPNetwork(IPAddress.IPv6Any, 0))] = replacement;
+                        publishedServerUrls[new IPData(IPAddress.Any, new IPNetwork(IPAddress.Any, 0))] = replacement;
+                        publishedServerUrls[new IPData(IPAddress.IPv6Any, new IPNetwork(IPAddress.IPv6Any, 0))] = replacement;
                     }
                     else if (string.Equals(identifier, "internal", StringComparison.OrdinalIgnoreCase))
                     {
                         foreach (var lan in _lanSubnets)
                         {
                             var lanPrefix = lan.Prefix;
-                            _publishedServerUrls[new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength))] = replacement;
+                            publishedServerUrls[new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength))] = replacement;
                         }
                     }
                     else if (NetworkExtensions.TryParseToSubnet(identifier, out var result) && result is not null)
                     {
                         var data = new IPData(result.Prefix, result);
-                        _publishedServerUrls[data] = replacement;
+                        publishedServerUrls[data] = replacement;
                     }
                     else if (TryParseInterface(identifier, out var ifaces))
                     {
                         foreach (var iface in ifaces)
                         {
-                            _publishedServerUrls[iface] = replacement;
+                            publishedServerUrls[iface] = replacement;
                         }
                     }
                     else
@@ -488,6 +497,8 @@ namespace Jellyfin.Networking.Manager
                         _logger.LogError("Unable to parse bind override: {Entry}", entry);
                     }
                 }
+
+                _publishedServerUrls = publishedServerUrls;
             }
         }
 
@@ -520,6 +531,7 @@ namespace Jellyfin.Networking.Manager
             {
                 // Format is <IPAddress>,<Index>,<Name>: <next interface>. Set index to -ve to simulate a gateway.
                 var interfaceList = MockNetworkSettings.Split('|');
+                var interfaces = new List<IPData>();
                 foreach (var details in interfaceList)
                 {
                     var parts = details.Split(',');
@@ -531,7 +543,7 @@ namespace Jellyfin.Networking.Manager
                         {
                             var data = new IPData(address, subnet, parts[2]);
                             data.Index = index;
-                            _interfaces.Add(data);
+                            interfaces.Add(data);
                         }
                     }
                     else
@@ -539,6 +551,8 @@ namespace Jellyfin.Networking.Manager
                         _logger.LogWarning("Could not parse mock interface settings: {Part}", details);
                     }
                 }
+
+                _interfaces = interfaces;
             }
 
             EnforceBindSettings(config);
@@ -565,11 +579,12 @@ namespace Jellyfin.Networking.Manager
         }
 
         /// <inheritdoc/>
-        public bool TryParseInterface(string intf, out List<IPData> result)
+        public bool TryParseInterface(string intf, out IReadOnlyList<IPData> result)
         {
-            result = new List<IPData>();
+            var resultList = new List<IPData>();
             if (string.IsNullOrEmpty(intf) || _interfaces is null)
             {
+                result = resultList.AsReadOnly();
                 return false;
             }
 
@@ -585,13 +600,15 @@ namespace Jellyfin.Networking.Manager
                     if ((IsIPv4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
                         || (IsIPv6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6))
                     {
-                        result.Add(iface);
+                        resultList.Add(iface);
                     }
                 }
 
+                result = resultList.AsReadOnly();
                 return true;
             }
 
+            result = resultList.AsReadOnly();
             return false;
         }
 

+ 3 - 3
MediaBrowser.Common/Net/INetworkManager.cs

@@ -30,7 +30,7 @@ namespace MediaBrowser.Common.Net
         /// <summary>
         /// Calculates the list of interfaces to use for Kestrel.
         /// </summary>
-        /// <returns>A List{IPData} object containing all the interfaces to bind.
+        /// <returns>A IReadOnlyList{IPData} object containing all the interfaces to bind.
         /// If all the interfaces are specified, and none are excluded, it returns zero items
         /// to represent any address.</returns>
         /// <param name="individualInterfaces">When false, return <see cref="IPAddress.Any"/> or <see cref="IPAddress.IPv6Any"/> for all interfaces.</param>
@@ -39,7 +39,7 @@ namespace MediaBrowser.Common.Net
         /// <summary>
         /// Returns a list containing the loopback interfaces.
         /// </summary>
-        /// <returns>List{IPData}.</returns>
+        /// <returns>IReadOnlyList{IPData}.</returns>
         IReadOnlyList<IPData> GetLoopbacks();
 
         /// <summary>
@@ -120,7 +120,7 @@ namespace MediaBrowser.Common.Net
         /// <param name="intf">Interface name.</param>
         /// <param name="result">Resulting object's IP addresses, if successful.</param>
         /// <returns>Success of the operation.</returns>
-        bool TryParseInterface(string intf, out List<IPData> result);
+        bool TryParseInterface(string intf, out IReadOnlyList<IPData> result);
 
         /// <summary>
         /// Returns all internal (LAN) bind interface addresses.

+ 53 - 50
MediaBrowser.Common/Net/NetworkExtensions.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using System.Net;
 using System.Net.Sockets;
 using System.Text.RegularExpressions;
+using Jellyfin.Extensions;
 using Microsoft.AspNetCore.HttpOverrides;
 
 namespace MediaBrowser.Common.Net
@@ -193,71 +194,64 @@ namespace MediaBrowser.Common.Net
         /// <param name="result">An <see cref="IPNetwork"/>.</param>
         /// <param name="negated">Boolean signaling if negated or not negated values should be parsed.</param>
         /// <returns><c>True</c> if parsing was successful.</returns>
-        public static bool TryParseToSubnet(string value, out IPNetwork result, bool negated = false)
+        public static bool TryParseToSubnet(ReadOnlySpan<char> value, out IPNetwork result, bool negated = false)
         {
             result = new IPNetwork(IPAddress.None, 32);
-
-            if (string.IsNullOrEmpty(value))
-            {
-                return false;
-            }
-
-            var splitString = value.Trim().Split("/");
-            var ipBlock = splitString[0];
-
-            var address = IPAddress.None;
-            if (negated && ipBlock.StartsWith('!') && IPAddress.TryParse(ipBlock[1..], out var tmpAddress))
-            {
-                address = tmpAddress;
-            }
-            else if (!negated && IPAddress.TryParse(ipBlock, out tmpAddress))
+            var splitString = value.Trim().Split('/');
+            if (splitString.MoveNext())
             {
-                address = tmpAddress;
-            }
+                var ipBlock = splitString.Current;
+                var address = IPAddress.None;
+                if (negated && ipBlock.StartsWith("!") && IPAddress.TryParse(ipBlock[1..], out var tmpAddress))
+                {
+                    address = tmpAddress;
+                }
+                else if (!negated && IPAddress.TryParse(ipBlock, out tmpAddress))
+                {
+                    address = tmpAddress;
+                }
 
-            if (address != IPAddress.None && address is not null)
-            {
-                if (splitString.Length > 1)
+                if (address != IPAddress.None)
                 {
-                    var subnetBlock = splitString[1];
-                    if (int.TryParse(subnetBlock, out var netmask))
+                    if (splitString.MoveNext())
                     {
-                        result = new IPNetwork(address, netmask);
+                        var subnetBlock = splitString.Current;
+                        if (int.TryParse(subnetBlock, out var netmask))
+                        {
+                            result = new IPNetwork(address, netmask);
+                        }
+                        else if (IPAddress.TryParse(subnetBlock, out var netmaskAddress))
+                        {
+                            result = new IPNetwork(address, NetworkExtensions.MaskToCidr(netmaskAddress));
+                        }
                     }
-                    else if (IPAddress.TryParse(subnetBlock, out var netmaskAddress))
+                    else if (address.AddressFamily == AddressFamily.InterNetwork)
                     {
-                        result = new IPNetwork(address, NetworkExtensions.MaskToCidr(netmaskAddress));
+                        result = new IPNetwork(address, 32);
+                    }
+                    else if (address.AddressFamily == AddressFamily.InterNetworkV6)
+                    {
+                        result = new IPNetwork(address, 128);
                     }
-                }
-                else if (address.AddressFamily == AddressFamily.InterNetwork)
-                {
-                    result = new IPNetwork(address, 32);
-                }
-                else if (address.AddressFamily == AddressFamily.InterNetworkV6)
-                {
-                    result = new IPNetwork(address, 128);
-                }
-            }
 
-            if (!result.Prefix.Equals(IPAddress.None))
-            {
-                return true;
+                    return true;
+                }
             }
 
             return false;
         }
 
         /// <summary>
-        /// Attempts to parse a host string.
+        /// Attempts to parse a host span.
         /// </summary>
         /// <param name="host">Host name to parse.</param>
-        /// <param name="addresses">Object representing the string, if it has successfully been parsed.</param>
+        /// <param name="addresses">Object representing the span, if it has successfully been parsed.</param>
         /// <param name="isIPv4Enabled"><c>true</c> if IPv4 is enabled.</param>
         /// <param name="isIPv6Enabled"><c>true</c> if IPv6 is enabled.</param>
         /// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
-        public static bool TryParseHost(string host, [NotNullWhen(true)] out IPAddress[] addresses, bool isIPv4Enabled = true, bool isIPv6Enabled = false)
+        public static bool TryParseHost(ReadOnlySpan<char> host, [NotNullWhen(true)] out IPAddress[] addresses, bool isIPv4Enabled = true, bool isIPv6Enabled = false)
         {
-            if (string.IsNullOrWhiteSpace(host))
+            if (host.IsEmpty)
             {
                 addresses = Array.Empty<IPAddress>();
                 return false;
@@ -268,19 +262,24 @@ namespace MediaBrowser.Common.Net
             // See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
             if (host[0] == '[')
             {
-                int i = host.IndexOf(']', StringComparison.Ordinal);
+                int i = host.IndexOf("]", StringComparison.Ordinal);
                 if (i != -1)
                 {
-                    return TryParseHost(host.Remove(i)[1..], out addresses);
+                    return TryParseHost(host[1..(i - 1)], out addresses);
                 }
 
                 addresses = Array.Empty<IPAddress>();
                 return false;
             }
 
-            var hosts = host.Split(':');
+            var hosts = new List<string>();
+            var splitSpan = host.Split(':');
+            while (splitSpan.MoveNext())
+            {
+                hosts.Add(splitSpan.Current.ToString());
+            }
 
-            if (hosts.Length <= 2)
+            if (hosts.Count <= 2)
             {
                 // Is hostname or hostname:port
                 if (_fqdnRegex.IsMatch(hosts[0]))
@@ -315,10 +314,14 @@ namespace MediaBrowser.Common.Net
                     return true;
                 }
             }
-            else if (hosts.Length <= 9 && IPAddress.TryParse(host.Split('/')[0], out var address)) // 8 octets + port
+            else if (hosts.Count <= 9) // 8 octets + port
             {
-                addresses = new[] { address };
-                return true;
+                splitSpan = host.Split('/');
+                if (splitSpan.MoveNext() && IPAddress.TryParse(splitSpan.Current, out var address))
+                {
+                    addresses = new[] { address };
+                    return true;
+                }
             }
 
             addresses = Array.Empty<IPAddress>();

+ 1 - 1
RSSDP/SsdpCommunicationsServer.cs

@@ -428,7 +428,7 @@ namespace Rssdp.Infrastructure
                     if (result.ReceivedBytes > 0)
                     {
                         var remoteEndpoint = (IPEndPoint)result.RemoteEndPoint;
-                        var localEndpointAdapter = _networkManager.GetAllBindInterfaces().Where(a => a.Index == result.PacketInformation.Interface).First();
+                        var localEndpointAdapter = _networkManager.GetAllBindInterfaces().First(a => a.Index == result.PacketInformation.Interface);
 
                         ProcessMessage(
                             UTF8Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes),

+ 2 - 2
tests/Jellyfin.Networking.Tests/NetworkParseTests.cs

@@ -203,7 +203,7 @@ namespace Jellyfin.Networking.Tests
             using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
             NetworkManager.MockNetworkSettings = string.Empty;
 
-            _ = nm.TryParseInterface(result, out List<IPData>? resultObj);
+            _ = nm.TryParseInterface(result, out IReadOnlyList<IPData>? resultObj);
 
             // Check to see if dns resolution is working. If not, skip test.
             _ = NetworkExtensions.TryParseHost(source, out var host);
@@ -266,7 +266,7 @@ namespace Jellyfin.Networking.Tests
             using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
             NetworkManager.MockNetworkSettings = string.Empty;
 
-            if (nm.TryParseInterface(result, out List<IPData>? resultObj) && resultObj is not null)
+            if (nm.TryParseInterface(result, out IReadOnlyList<IPData>? resultObj) && resultObj is not null)
             {
                 // Parse out IPAddresses so we can do a string comparison (ignore subnet masks).
                 result = resultObj.First().Address.ToString();