< Summary - Jellyfin

Information
Class: MediaBrowser.Common.Net.NetworkUtils
Assembly: MediaBrowser.Common
File(s): /srv/git/jellyfin/MediaBrowser.Common/Net/NetworkUtils.cs
Line coverage
56%
Covered lines: 67
Uncovered lines: 52
Coverable lines: 119
Total lines: 359
Line coverage: 56.3%
Branch coverage
59%
Covered branches: 52
Total branches: 88
Branch coverage: 59%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

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(...)100%88100%
TryParseToSubnet(...)88.88%1818100%
TryParseHost(...)83.33%313088.23%
GetBroadcastAddress(...)100%210%
SubnetContainsAddress(...)50%2280%

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 IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
 10
 11namespace MediaBrowser.Common.Net;
 12
 13/// <summary>
 14/// Defines the <see cref="NetworkUtils" />.
 15/// </summary>
 16public static partial class NetworkUtils
 17{
 18    // Use regular expression as CheckHostName isn't RFC5892 compliant.
 19    // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-v
 20    [GeneratedRegex(@"(?im)^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)(:(\d){1,5}){0,1}$", Regex
 21    private static partial Regex FqdnGeneratedRegex();
 22
 23    /// <summary>
 24    /// Returns true if the IPAddress contains an IP6 Local link address.
 25    /// </summary>
 26    /// <param name="address">IPAddress object to check.</param>
 27    /// <returns>True if it is a local link address.</returns>
 28    /// <remarks>
 29    /// See https://stackoverflow.com/questions/6459928/explain-the-instance-properties-of-system-net-ipaddress
 30    /// it appears that the IPAddress.IsIPv6LinkLocal is out of date.
 31    /// </remarks>
 32    public static bool IsIPv6LinkLocal(IPAddress address)
 33    {
 034        ArgumentNullException.ThrowIfNull(address);
 35
 036        if (address.IsIPv4MappedToIPv6)
 37        {
 038            address = address.MapToIPv4();
 39        }
 40
 041        if (address.AddressFamily != AddressFamily.InterNetworkV6)
 42        {
 043            return false;
 44        }
 45
 46        // GetAddressBytes
 047        Span<byte> octet = stackalloc byte[16];
 048        address.TryWriteBytes(octet, out _);
 049        uint word = (uint)(octet[0] << 8) + octet[1];
 50
 051        return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link.
 52    }
 53
 54    /// <summary>
 55    /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
 56    /// </summary>
 57    /// <param name="cidr">Subnet mask in CIDR notation.</param>
 58    /// <param name="family">IPv4 or IPv6 family.</param>
 59    /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
 60    public static IPAddress CidrToMask(byte cidr, AddressFamily family)
 61    {
 062        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : Netw
 063        addr = ((addr & 0xff000000) >> 24)
 064                | ((addr & 0x00ff0000) >> 8)
 065                | ((addr & 0x0000ff00) << 8)
 066                | ((addr & 0x000000ff) << 24);
 067        return new IPAddress(addr);
 68    }
 69
 70    /// <summary>
 71    /// Convert a subnet mask in CIDR notation to a dotted decimal string value. IPv4 only.
 72    /// </summary>
 73    /// <param name="cidr">Subnet mask in CIDR notation.</param>
 74    /// <param name="family">IPv4 or IPv6 family.</param>
 75    /// <returns>String value of the subnet mask in dotted decimal notation.</returns>
 76    public static IPAddress CidrToMask(int cidr, AddressFamily family)
 77    {
 078        uint addr = 0xFFFFFFFF << ((family == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : Netw
 079        addr = ((addr & 0xff000000) >> 24)
 080                | ((addr & 0x00ff0000) >> 8)
 081                | ((addr & 0x0000ff00) << 8)
 082                | ((addr & 0x000000ff) << 24);
 083        return new IPAddress(addr);
 84    }
 85
 86    /// <summary>
 87    /// Convert a subnet mask to a CIDR. IPv4 only.
 88    /// https://stackoverflow.com/questions/36954345/get-cidr-from-netmask.
 89    /// </summary>
 90    /// <param name="mask">Subnet mask.</param>
 91    /// <returns>Byte CIDR representing the mask.</returns>
 92    public static byte MaskToCidr(IPAddress mask)
 93    {
 094        ArgumentNullException.ThrowIfNull(mask);
 95
 096        byte cidrnet = 0;
 097        if (mask.Equals(IPAddress.Any))
 98        {
 099            return cidrnet;
 100        }
 101
 102        // GetAddressBytes
 0103        Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.IPv4MaskB
 0104        if (!mask.TryWriteBytes(bytes, out var bytesWritten))
 105        {
 0106            Console.WriteLine("Unable to write address bytes, only {0} bytes written.", bytesWritten.ToString(CultureInf
 107        }
 108
 0109        var zeroed = false;
 0110        for (var i = 0; i < bytes.Length; i++)
 111        {
 0112            for (int v = bytes[i]; (v & 0xFF) != 0; v <<= 1)
 113            {
 0114                if (zeroed)
 115                {
 116                    // Invalid netmask.
 0117                    return (byte)~cidrnet;
 118                }
 119
 0120                if ((v & 0x80) == 0)
 121                {
 0122                    zeroed = true;
 123                }
 124                else
 125                {
 0126                    cidrnet++;
 127                }
 128            }
 129        }
 130
 0131        return cidrnet;
 132    }
 133
 134    /// <summary>
 135    /// Converts an IPAddress into a string.
 136    /// IPv6 addresses are returned in [ ], with their scope removed.
 137    /// </summary>
 138    /// <param name="address">Address to convert.</param>
 139    /// <returns>URI safe conversion of the address.</returns>
 140    public static string FormatIPString(IPAddress? address)
 141    {
 17142        if (address is null)
 143        {
 0144            return string.Empty;
 145        }
 146
 17147        var str = address.ToString();
 17148        if (address.AddressFamily == AddressFamily.InterNetworkV6)
 149        {
 0150            int i = str.IndexOf('%', StringComparison.Ordinal);
 0151            if (i != -1)
 152            {
 0153                str = str.Substring(0, i);
 154            }
 155
 0156            return $"[{str}]";
 157        }
 158
 17159        return str;
 160    }
 161
 162    /// <summary>
 163    /// Try parsing an array of strings into <see cref="IPNetwork"/> objects, respecting exclusions.
 164    /// Elements without a subnet mask will be represented as <see cref="IPNetwork"/> with a single IP.
 165    /// </summary>
 166    /// <param name="values">Input string array to be parsed.</param>
 167    /// <param name="result">Collection of <see cref="IPNetwork"/>.</param>
 168    /// <param name="negated">Boolean signaling if negated or not negated values should be parsed.</param>
 169    /// <returns><c>True</c> if parsing was successful.</returns>
 170    public static bool TryParseToSubnets(string[] values, [NotNullWhen(true)] out IReadOnlyList<IPNetwork>? result, bool
 171    {
 156172        if (values is null || values.Length == 0)
 173        {
 88174            result = null;
 88175            return false;
 176        }
 177
 68178        var tmpResult = new List<IPNetwork>();
 296179        for (int a = 0; a < values.Length; a++)
 180        {
 80181            if (TryParseToSubnet(values[a], out var innerResult, negated))
 182            {
 40183                tmpResult.Add(innerResult);
 184            }
 185        }
 186
 68187        result = tmpResult;
 68188        return tmpResult.Count > 0;
 189    }
 190
 191    /// <summary>
 192    /// Try parsing a string into an <see cref="IPNetwork"/>, respecting exclusions.
 193    /// Inputs without a subnet mask will be represented as <see cref="IPNetwork"/> with a single IP.
 194    /// </summary>
 195    /// <param name="value">Input string to be parsed.</param>
 196    /// <param name="result">An <see cref="IPNetwork"/>.</param>
 197    /// <param name="negated">Boolean signaling if negated or not negated values should be parsed.</param>
 198    /// <returns><c>True</c> if parsing was successful.</returns>
 199    public static bool TryParseToSubnet(ReadOnlySpan<char> value, [NotNullWhen(true)] out IPNetwork? result, bool negate
 200    {
 201        // If multiple IP addresses are in a comma-separated string, the individual addresses may contain leading and/or
 243202        value = value.Trim();
 203
 243204        bool isAddressNegated = false;
 243205        if (value.StartsWith('!'))
 206        {
 19207            isAddressNegated = true;
 19208            value = value[1..]; // Remove leading '!' character
 209        }
 210
 243211        if (isAddressNegated != negated)
 212        {
 40213            result = null;
 40214            return false;
 215        }
 216
 203217        if (value.Contains('/'))
 218        {
 153219            if (IPNetwork.TryParse(value, out result))
 220            {
 153221                return true;
 222            }
 223        }
 50224        else if (IPAddress.TryParse(value, out var address))
 225        {
 24226            if (address.AddressFamily == AddressFamily.InterNetwork)
 227            {
 12228                result = address.Equals(IPAddress.Any) ? NetworkConstants.IPv4Any : new IPNetwork(address, NetworkConsta
 12229                return true;
 230            }
 12231            else if (address.AddressFamily == AddressFamily.InterNetworkV6)
 232            {
 12233                result = address.Equals(IPAddress.IPv6Any) ? NetworkConstants.IPv6Any : new IPNetwork(address, NetworkCo
 12234                return true;
 235            }
 236        }
 237
 26238        result = null;
 26239        return false;
 240    }
 241
 242    /// <summary>
 243    /// Attempts to parse a host span.
 244    /// </summary>
 245    /// <param name="host">Host name to parse.</param>
 246    /// <param name="addresses">Object representing the span, if it has successfully been parsed.</param>
 247    /// <param name="isIPv4Enabled"><c>true</c> if IPv4 is enabled.</param>
 248    /// <param name="isIPv6Enabled"><c>true</c> if IPv6 is enabled.</param>
 249    /// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
 250    public static bool TryParseHost(ReadOnlySpan<char> host, [NotNullWhen(true)] out IPAddress[]? addresses, bool isIPv4
 251    {
 261252        host = host.Trim();
 261253        if (host.IsEmpty)
 254        {
 5255            addresses = null;
 5256            return false;
 257        }
 258
 259        // See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
 256260        if (host[0] == '[')
 261        {
 4262            int i = host.IndexOf(']');
 4263            if (i != -1)
 264            {
 4265                return TryParseHost(host[1..(i - 1)], out addresses);
 266            }
 267
 0268            addresses = Array.Empty<IPAddress>();
 0269            return false;
 270        }
 271
 252272        var hosts = new List<string>();
 2528273        foreach (var splitSpan in host.Split(':'))
 274        {
 1012275            hosts.Add(splitSpan.ToString());
 276        }
 277
 252278        if (hosts.Count <= 2)
 279        {
 143280            var firstPart = hosts[0];
 281
 282            // Is hostname or hostname:port
 143283            if (FqdnGeneratedRegex().IsMatch(firstPart))
 284            {
 285                try
 286                {
 287                    // .NET automatically filters only supported returned addresses based on OS support.
 15288                    addresses = Dns.GetHostAddresses(firstPart);
 12289                    return true;
 290                }
 3291                catch (SocketException)
 292                {
 293                    // Ignore socket errors, as the result value will just be an empty array.
 3294                }
 295            }
 296
 297            // Is an IPv4 or IPv4:port
 131298            if (IPAddress.TryParse(firstPart.AsSpan().LeftPart('/'), out var address))
 299            {
 123300                if (((address.AddressFamily == AddressFamily.InterNetwork) && (!isIPv4Enabled && isIPv6Enabled))
 123301                    || ((address.AddressFamily == AddressFamily.InterNetworkV6) && (isIPv4Enabled && !isIPv6Enabled)))
 302                {
 0303                    addresses = Array.Empty<IPAddress>();
 0304                    return false;
 305                }
 306
 123307                addresses = new[] { address };
 308
 309                // Host name is an IPv4 address, so fake resolve.
 123310                return true;
 311            }
 312        }
 109313        else if (hosts.Count > 0 && hosts.Count <= 9) // 8 octets + port
 314        {
 109315            if (IPAddress.TryParse(host.LeftPart('/'), out var address))
 316            {
 107317                addresses = new[] { address };
 107318                return true;
 319            }
 320        }
 321
 10322        addresses = Array.Empty<IPAddress>();
 10323        return false;
 12324    }
 325
 326    /// <summary>
 327    /// Gets the broadcast address for a <see cref="IPNetwork"/>.
 328    /// </summary>
 329    /// <param name="network">The <see cref="IPNetwork"/>.</param>
 330    /// <returns>The broadcast address.</returns>
 331    public static IPAddress GetBroadcastAddress(IPNetwork network)
 332    {
 0333        var addressBytes = network.Prefix.GetAddressBytes();
 0334        uint ipAddress = BitConverter.ToUInt32(addressBytes, 0);
 0335        uint ipMaskV4 = BitConverter.ToUInt32(CidrToMask(network.PrefixLength, AddressFamily.InterNetwork).GetAddressByt
 0336        uint broadCastIPAddress = ipAddress | ~ipMaskV4;
 337
 0338        return new IPAddress(BitConverter.GetBytes(broadCastIPAddress));
 339    }
 340
 341    /// <summary>
 342    /// Check if a subnet contains an address. This method also handles IPv4 mapped to IPv6 addresses.
 343    /// </summary>
 344    /// <param name="network">The <see cref="IPNetwork"/>.</param>
 345    /// <param name="address">The <see cref="IPAddress"/>.</param>
 346    /// <returns>Whether the supplied IP is in the supplied network.</returns>
 347    public static bool SubnetContainsAddress(IPNetwork network, IPAddress address)
 348    {
 34349        ArgumentNullException.ThrowIfNull(address);
 34350        ArgumentNullException.ThrowIfNull(network);
 351
 34352        if (address.IsIPv4MappedToIPv6)
 353        {
 0354            address = address.MapToIPv4();
 355        }
 356
 34357        return network.Contains(address);
 358    }
 359}