< Summary - Jellyfin

Information
Class: MediaBrowser.Common.Net.NetworkUtils
Assembly: MediaBrowser.Common
File(s): /srv/git/jellyfin/MediaBrowser.Common/Net/NetworkUtils.cs
Line coverage
60%
Covered lines: 84
Uncovered lines: 56
Coverable lines: 140
Total lines: 402
Line coverage: 60%
Branch coverage
60%
Covered branches: 63
Total branches: 104
Branch coverage: 60.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 56.6% (68/120) Branch coverage: 60.8% (56/92) Total lines: 3605/11/2026 - 12:15:59 AM Line coverage: 61.5% (85/138) Branch coverage: 63.7% (65/102) Total lines: 3955/18/2026 - 12:15:49 AM Line coverage: 60% (84/140) Branch coverage: 62.5% (65/104) Total lines: 4025/20/2026 - 12:15:44 AM Line coverage: 60% (84/140) Branch coverage: 60.5% (63/104) Total lines: 402 2/13/2026 - 12:11:21 AM Line coverage: 56.6% (68/120) Branch coverage: 60.8% (56/92) Total lines: 3605/11/2026 - 12:15:59 AM Line coverage: 61.5% (85/138) Branch coverage: 63.7% (65/102) Total lines: 3955/18/2026 - 12:15:49 AM Line coverage: 60% (84/140) Branch coverage: 62.5% (65/104) Total lines: 4025/20/2026 - 12:15:44 AM Line coverage: 60% (84/140) Branch coverage: 60.5% (63/104) Total lines: 402

Coverage delta

Coverage delta 5 -5

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsIPv6LinkLocal(...)0%4260%
CidrToMask(...)0%620%
CidrToMask(...)0%620%
MaskToCidr(...)0%210140%
FormatIPString(...)33.33%12644.44%
TryParseToSubnets(...)91.66%1212100%
LogInvalidSubnet(...)80%101088.23%
TryParseToSubnet(...)85%202090.9%
TryParseHost(...)80%313088.23%
GetBroadcastAddress(...)100%210%
SubnetContainsAddress(...)50%2275%

File(s)

/srv/git/jellyfin/MediaBrowser.Common/Net/NetworkUtils.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics.CodeAnalysis;
 4using System.Globalization;
 5using System.Net;
 6using System.Net.Sockets;
 7using System.Text.RegularExpressions;
 8using Jellyfin.Extensions;
 9using MediaBrowser.Model.Net;
 10using Microsoft.Extensions.Logging;
 11
 12namespace MediaBrowser.Common.Net;
 13
 14/// <summary>
 15/// Defines the <see cref="NetworkUtils" />.
 16/// </summary>
 17public static partial class NetworkUtils
 18{
 19    // Use regular expression as CheckHostName isn't RFC5892 compliant.
 20    // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-v
 21    [GeneratedRegex(@"(?im)^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)(:(\d){1,5}){0,1}$", Regex
 22    private static partial Regex FqdnGeneratedRegex();
 23
 24    /// <summary>
 25    /// Returns true if the IPAddress contains an IP6 Local link address.
 26    /// </summary>
 27    /// <param name="address">IPAddress object to check.</param>
 28    /// <returns>True if it is a local link address.</returns>
 29    /// <remarks>
 30    /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress
 31    /// it appears that the IPAddress.IsIPv6LinkLocal is out of date.
 32    /// </remarks>
 33    public static bool IsIPv6LinkLocal(IPAddress address)
 34    {
 035        ArgumentNullException.ThrowIfNull(address);
 36
 037        if (address.IsIPv4MappedToIPv6)
 38        {
 039            address = address.MapToIPv4();
 40        }
 41
 042        if (address.AddressFamily != AddressFamily.InterNetworkV6)
 43        {
 044            return false;
 45        }
 46
 47        // GetAddressBytes
 048        Span<byte> octet = stackalloc byte[16];
 049        address.TryWriteBytes(octet, out _);
 050        uint word = (uint)(octet[0] << 8) + octet[1];
 51
 052        return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link.
 53    }
 54
 55    /// <summary>
 56    /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
 57    /// </summary>
 58    /// <param name="cidr">Subnet mask in CIDR notation.</param>
 59    /// <param name="family">IPv4 or IPv6 family.</param>
 60    /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
 61    public static IPAddress CidrToMask(byte cidr, AddressFamily family)
 62    {
 063        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : Netw
 064        addr = ((addr & 0xff000000) >> 24)
 065                | ((addr & 0x00ff0000) >> 8)
 066                | ((addr & 0x0000ff00) << 8)
 067                | ((addr & 0x000000ff) << 24);
 068        return new IPAddress(addr);
 69    }
 70
 71    /// <summary>
 72    /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
 73    /// </summary>
 74    /// <param name="cidr">Subnet mask in CIDR notation.</param>
 75    /// <param name="family">IPv4 or IPv6 family.</param>
 76    /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
 77    public static IPAddress CidrToMask(int cidr, AddressFamily family)
 78    {
 079        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : Netw
 080        addr = ((addr & 0xff000000) >> 24)
 081                | ((addr & 0x00ff0000) >> 8)
 082                | ((addr & 0x0000ff00) << 8)
 083                | ((addr & 0x000000ff) << 24);
 084        return new IPAddress(addr);
 85    }
 86
 87    /// <summary>
 88    /// Convert a subnet mask to a CIDR. IPv4 only.
 89    /// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask.
 90    /// </summary>
 91    /// <param name="mask">Subnet mask.</param>
 92    /// <returns>Byte CIDR representing the mask.</returns>
 93    public static byte MaskToCidr(IPAddress mask)
 94    {
 095        ArgumentNullException.ThrowIfNull(mask);
 96
 097        byte cidrnet = 0;
 098        if (mask.Equals(IPAddress.Any))
 99        {
 0100            return cidrnet;
 101        }
 102
 103        // GetAddressBytes
 0104        Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.IPv4MaskB
 0105        if (!mask.TryWriteBytes(bytes, out var bytesWritten))
 106        {
 0107            Console.WriteLine("Unable to write address bytes, only {0} bytes written.", bytesWritten.ToString(CultureInf
 108        }
 109
 0110        var zeroed = false;
 0111        for (var i = 0; i < bytes.Length; i++)
 112        {
 0113            for (int v = bytes[i]; (v & 0xFF) != 0; v <<= 1)
 114            {
 0115                if (zeroed)
 116                {
 117                    // Invalid netmask.
 0118                    return (byte)~cidrnet;
 119                }
 120
 0121                if ((v & 0x80) == 0)
 122                {
 0123                    zeroed = true;
 124                }
 125                else
 126                {
 0127                    cidrnet++;
 128                }
 129            }
 130        }
 131
 0132        return cidrnet;
 133    }
 134
 135    /// <summary>
 136    /// Converts an IPAddress into a string.
 137    /// IPv6 addresses are returned in [ ], with their scope removed.
 138    /// </summary>
 139    /// <param name="address">Address to convert.</param>
 140    /// <returns>URI safe conversion of the address.</returns>
 141    public static string FormatIPString(IPAddress? address)
 142    {
 18143        if (address is null)
 144        {
 0145            return string.Empty;
 146        }
 147
 18148        var str = address.ToString();
 18149        if (address.AddressFamily == AddressFamily.InterNetworkV6)
 150        {
 0151            int i = str.IndexOf('%', StringComparison.Ordinal);
 0152            if (i != -1)
 153            {
 0154                str = str.Substring(0, i);
 155            }
 156
 0157            return $"[{str}]";
 158        }
 159
 18160        return str;
 161    }
 162
 163    /// <summary>
 164    /// Try parsing an array of strings into <see cref="IPNetwork"/> objects, respecting exclusions.
 165    /// Elements without a subnet mask will be represented as <see cref="IPNetwork"/> with a single IP.
 166    /// </summary>
 167    /// <param name="values">Input string array to be parsed.</param>
 168    /// <param name="result">Collection of <see cref="IPNetwork"/>.</param>
 169    /// <param name="negated">Boolean signaling if negated or not negated values should be parsed.</param>
 170    /// <param name="logger">Optional logger used to warn about entries that fail to parse.</param>
 171    /// <returns><c>True</c> if parsing was successful.</returns>
 172    public static bool TryParseToSubnets(string[] values, [NotNullWhen(true)] out IReadOnlyList<IPData>? result, bool ne
 173    {
 171174        if (values is null || values.Length == 0)
 175        {
 96176            result = null;
 96177            return false;
 178        }
 179
 75180        List<IPData>? tmpResult = null;
 344181        for (int a = 0; a < values.Length; a++)
 182        {
 183            // Skip entries whose '!' polarity doesn't match this pass
 97184            var trimmed = values[a].AsSpan().Trim();
 97185            if (trimmed.StartsWith('!') != negated)
 186            {
 187                continue;
 188            }
 189
 50190            if (TryParseToSubnet(values[a], out var innerResult, negated))
 191            {
 48192                (tmpResult ??= []).Add(innerResult);
 193            }
 194            else
 195            {
 2196                LogInvalidSubnet(logger, values[a]);
 197            }
 198        }
 199
 75200        result = tmpResult;
 75201        return result is not null;
 202    }
 203
 204    private static void LogInvalidSubnet(ILogger? logger, string value)
 205    {
 2206        if (logger is null)
 207        {
 0208            return;
 209        }
 210
 2211        var trimmed = value.AsSpan().Trim();
 2212        if (trimmed.StartsWith('!'))
 213        {
 0214            trimmed = trimmed[1..];
 215        }
 216
 2217        var slash = trimmed.IndexOf('/');
 2218        if (slash != -1
 2219            && trimmed.Contains(':')
 2220            && trimmed.IndexOf("::", StringComparison.Ordinal) == -1)
 221        {
 1222            logger.LogWarning(
 1223                "Invalid IPv6 subnet '{Subnet}': IPv6 prefix-only notation is not supported. Use the full notation inclu
 1224                value,
 1225                trimmed[..slash].ToString(),
 1226                trimmed[(slash + 1)..].ToString());
 1227            return;
 228        }
 229
 1230        logger.LogWarning("Invalid subnet '{Subnet}' will be ignored.", value);
 1231    }
 232
 233    /// <summary>
 234    /// Try parsing a string into an <see cref="IPData"/>, respecting exclusions.
 235    /// Inputs without a subnet mask will be represented as <see cref="IPData"/> with a single IP.
 236    /// </summary>
 237    /// <param name="value">Input string to be parsed.</param>
 238    /// <param name="result">An <see cref="IPData"/>.</param>
 239    /// <param name="negated">Boolean signaling if negated or not negated values should be parsed.</param>
 240    /// <returns><c>True</c> if parsing was successful.</returns>
 241    public static bool TryParseToSubnet(ReadOnlySpan<char> value, [NotNullWhen(true)] out IPData? result, bool negated =
 242    {
 243        // If multiple IP addresses are in a comma-separated string, the individual addresses may contain leading and/or
 211244        value = value.Trim();
 245
 211246        bool isAddressNegated = false;
 211247        if (value.StartsWith('!'))
 248        {
 17249            isAddressNegated = true;
 17250            value = value[1..]; // Remove leading '!' character
 251        }
 252
 211253        if (isAddressNegated != negated)
 254        {
 0255            result = default;
 0256            return false;
 257        }
 258
 211259        var index = value.IndexOf('/');
 211260        if (index != -1)
 261        {
 158262            if (IPAddress.TryParse(value[..index], out var address) && IPNetwork.TryParse(value, out var subnet))
 263            {
 156264                result = new IPData(address, subnet);
 156265                return true;
 266            }
 267        }
 53268        else if (IPAddress.TryParse(value, out var address))
 269        {
 26270            if (address.AddressFamily == AddressFamily.InterNetwork)
 271            {
 14272                result = address.Equals(IPAddress.Any) ? new IPData(IPAddress.Any, NetworkConstants.IPv4Any) : new IPDat
 14273                return true;
 274            }
 12275            else if (address.AddressFamily == AddressFamily.InterNetworkV6)
 276            {
 12277                result = address.Equals(IPAddress.IPv6Any) ? new IPData(IPAddress.IPv6Any, NetworkConstants.IPv6Any) : n
 12278                return true;
 279            }
 280        }
 281
 29282        result = default;
 29283        return false;
 284    }
 285
 286    /// <summary>
 287    /// Attempts to parse a host span.
 288    /// </summary>
 289    /// <param name="host">Host name to parse.</param>
 290    /// <param name="addresses">Object representing the span, if it has successfully been parsed.</param>
 291    /// <param name="isIPv4Enabled"><c>true</c> if IPv4 is enabled.</param>
 292    /// <param name="isIPv6Enabled"><c>true</c> if IPv6 is enabled.</param>
 293    /// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
 294    public static bool TryParseHost(ReadOnlySpan<char> host, [NotNullWhen(true)] out IPAddress[]? addresses, bool isIPv4
 295    {
 262296        host = host.Trim();
 262297        if (host.IsEmpty)
 298        {
 5299            addresses = null;
 5300            return false;
 301        }
 302
 303        // See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
 257304        if (host[0] == '[')
 305        {
 4306            int i = host.IndexOf(']');
 4307            if (i != -1)
 308            {
 4309                return TryParseHost(host[1..(i - 1)], out addresses);
 310            }
 311
 0312            addresses = Array.Empty<IPAddress>();
 0313            return false;
 314        }
 315
 253316        var hosts = new List<string>();
 2532317        foreach (var splitSpan in host.Split(':'))
 318        {
 1013319            hosts.Add(splitSpan.ToString());
 320        }
 321
 253322        if (hosts.Count <= 2)
 323        {
 144324            var firstPart = hosts[0];
 325
 326            // Is hostname or hostname:port
 144327            if (FqdnGeneratedRegex().IsMatch(firstPart))
 328            {
 329                try
 330                {
 331                    // .NET automatically filters only supported returned addresses based on OS support.
 15332                    addresses = Dns.GetHostAddresses(firstPart);
 12333                    return true;
 334                }
 3335                catch (SocketException)
 336                {
 337                    // Ignore socket errors, as the result value will just be an empty array.
 3338                }
 339            }
 340
 341            // Is an IPv4 or IPv4:port
 132342            if (IPAddress.TryParse(firstPart.AsSpan().LeftPart('/'), out var address))
 343            {
 124344                if (((address.AddressFamily == AddressFamily.InterNetwork) && (!isIPv4Enabled && isIPv6Enabled))
 124345                    || ((address.AddressFamily == AddressFamily.InterNetworkV6) && (isIPv4Enabled && !isIPv6Enabled)))
 346                {
 0347                    addresses = Array.Empty<IPAddress>();
 0348                    return false;
 349                }
 350
 124351                addresses = new[] { address };
 352
 353                // Host name is an IPv4 address, so fake resolve.
 124354                return true;
 355            }
 356        }
 109357        else if (hosts.Count > 0 && hosts.Count <= 9) // 8 octets + port
 358        {
 109359            if (IPAddress.TryParse(host.LeftPart('/'), out var address))
 360            {
 107361                addresses = new[] { address };
 107362                return true;
 363            }
 364        }
 365
 10366        addresses = Array.Empty<IPAddress>();
 10367        return false;
 12368    }
 369
 370    /// <summary>
 371    /// Gets the broadcast address for a <see cref="IPNetwork"/>.
 372    /// </summary>
 373    /// <param name="network">The <see cref="IPNetwork"/>.</param>
 374    /// <returns>The broadcast address.</returns>
 375    public static IPAddress GetBroadcastAddress(IPNetwork network)
 376    {
 0377        var addressBytes = network.BaseAddress.GetAddressBytes();
 0378        uint ipAddress = BitConverter.ToUInt32(addressBytes, 0);
 0379        uint ipMaskV4 = BitConverter.ToUInt32(CidrToMask(network.PrefixLength, AddressFamily.InterNetwork).GetAddressByt
 0380        uint broadCastIPAddress = ipAddress | ~ipMaskV4;
 381
 0382        return new IPAddress(BitConverter.GetBytes(broadCastIPAddress));
 383    }
 384
 385    /// <summary>
 386    /// Check if a subnet contains an address. This method also handles IPv4 mapped to IPv6 addresses.
 387    /// </summary>
 388    /// <param name="network">The <see cref="IPNetwork"/>.</param>
 389    /// <param name="address">The <see cref="IPAddress"/>.</param>
 390    /// <returns>Whether the supplied IP is in the supplied network.</returns>
 391    public static bool SubnetContainsAddress(IPNetwork network, IPAddress address)
 392    {
 35393        ArgumentNullException.ThrowIfNull(address);
 394
 35395        if (address.IsIPv4MappedToIPv6)
 396        {
 0397            address = address.MapToIPv4();
 398        }
 399
 35400        return network.Contains(address);
 401    }
 402}