| File: IPNetwork.cs | Web Access |
| Project: src\aspnetcore\src\Middleware\HttpOverrides\src\Microsoft.AspNetCore.HttpOverrides.csproj (Microsoft.AspNetCore.HttpOverrides) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; using Microsoft.AspNetCore.Shared; namespace Microsoft.AspNetCore.HttpOverrides; /// <summary> /// A representation of an IP network based on CIDR notation. /// Please use <see cref="System.Net.IPNetwork"/> instead /// </summary> [Obsolete("Please use System.Net.IPNetwork instead. For more information, visit https://aka.ms/aspnet/deprecate/005.", DiagnosticId = "ASPDEPR005", UrlFormat = Obsoletions.AspNetCoreDeprecate005Url)] public class IPNetwork { /// <summary> /// Create a new <see cref="IPNetwork"/> with the specified <see cref="IPAddress"/> and prefix length. /// </summary> /// <param name="prefix">The <see cref="IPAddress"/>.</param> /// <param name="prefixLength">The prefix length.</param> /// <exception cref="ArgumentOutOfRangeException"><paramref name="prefixLength"/> is out of range.</exception> public IPNetwork(IPAddress prefix, int prefixLength) : this(prefix, prefixLength, true) { } private IPNetwork(IPAddress prefix, int prefixLength, bool checkPrefixLengthRange) { if (checkPrefixLengthRange && !IsValidPrefixLengthRange(prefix, prefixLength)) { throw new ArgumentOutOfRangeException(nameof(prefixLength), "The prefix length was out of range."); } Prefix = prefix; PrefixLength = prefixLength; PrefixBytes = Prefix.GetAddressBytes(); Mask = CreateMask(); } /// <summary> /// Get the <see cref="IPAddress"/> that represents the prefix for the network. /// </summary> public IPAddress Prefix { get; } private byte[] PrefixBytes { get; } /// <summary> /// The CIDR notation of the subnet mask /// </summary> public int PrefixLength { get; } private byte[] Mask { get; } /// <summary> /// Determine whether a given The <see cref="IPAddress"/> is part of the IP network. /// </summary> /// <param name="address">The <see cref="IPAddress"/>.</param> /// <returns><see langword="true"/> if the <see cref="IPAddress"/> is part of the IP network. Otherwise, <see langword="false"/>.</returns> public bool Contains(IPAddress address) { if (Prefix.AddressFamily != address.AddressFamily) { return false; } var addressBytes = address.GetAddressBytes(); for (int i = 0; i < PrefixBytes.Length && Mask[i] != 0; i++) { if ((PrefixBytes[i] & Mask[i]) != (addressBytes[i] & Mask[i])) { return false; } } return true; } private byte[] CreateMask() { var mask = new byte[PrefixBytes.Length]; int remainingBits = PrefixLength; int i = 0; while (remainingBits >= 8) { mask[i] = 0xFF; i++; remainingBits -= 8; } if (remainingBits > 0) { mask[i] = (byte)(0xFF << (8 - remainingBits)); } return mask; } private static bool IsValidPrefixLengthRange(IPAddress prefix, int prefixLength) { if (prefixLength < 0) { return false; } return prefix.AddressFamily switch { AddressFamily.InterNetwork => prefixLength <= 32, AddressFamily.InterNetworkV6 => prefixLength <= 128, _ => true }; } /// <summary> /// Converts the specified <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> representation of /// an IP address and a prefix length to its <see cref="IPNetwork"/> equivalent. /// </summary> /// <param name="networkSpan">The <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> to convert, in CIDR notation.</param> /// <returns> ///The <see cref="IPNetwork"/> equivalent to the IP address and prefix length contained in <paramref name="networkSpan"/>. /// </returns> /// <exception cref="FormatException"><paramref name="networkSpan"/> is not in the correct format.</exception> /// <exception cref="ArgumentOutOfRangeException">The prefix length contained in <paramref name="networkSpan"/> is out of range.</exception> /// <inheritdoc cref="TryParseComponents(ReadOnlySpan{char}, out IPAddress?, out int)"/> public static IPNetwork Parse(ReadOnlySpan<char> networkSpan) { if (!TryParseComponents(networkSpan, out var prefix, out var prefixLength)) { throw new FormatException("An invalid IP address or prefix length was specified."); } if (!IsValidPrefixLengthRange(prefix, prefixLength)) { throw new ArgumentOutOfRangeException(nameof(networkSpan), "The prefix length was out of range."); } return new IPNetwork(prefix, prefixLength, false); } /// <summary> /// Converts the specified <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> representation of /// an IP address and a prefix length to its <see cref="IPNetwork"/> equivalent, and returns a value /// that indicates whether the conversion succeeded. /// </summary> /// <param name="networkSpan">The <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> to validate.</param> /// <param name="network"> /// When this method returns, contains the <see cref="IPNetwork"/> equivalent to the IP Address /// and prefix length contained in <paramref name="networkSpan"/>, if the conversion succeeded, /// or <see langword="null"/> if the conversion failed. This parameter is passed uninitialized. /// </param> /// <returns> /// <see langword="true"/> if the <paramref name="networkSpan"/> parameter was /// converted successfully; otherwise <see langword="false"/>. /// </returns> /// <inheritdoc cref="TryParseComponents(ReadOnlySpan{char}, out IPAddress?, out int)"/> public static bool TryParse(ReadOnlySpan<char> networkSpan, [NotNullWhen(true)] out IPNetwork? network) { network = null; if (!TryParseComponents(networkSpan, out var prefix, out var prefixLength)) { return false; } if (!IsValidPrefixLengthRange(prefix, prefixLength)) { return false; } network = new IPNetwork(prefix, prefixLength, false); return true; } /// <remarks> /// <para> /// The specified representation must be expressed using CIDR (Classless Inter-Domain Routing) notation, or 'slash notation', /// which contains an IPv4 or IPv6 address and the subnet mask prefix length, separated by a forward slash. /// </para> /// <example> /// e.g. <c>"192.168.0.1/31"</c> for IPv4, <c>"2001:db8:3c4d::1/127"</c> for IPv6 /// </example> /// </remarks> private static bool TryParseComponents( ReadOnlySpan<char> networkSpan, [NotNullWhen(true)] out IPAddress? prefix, out int prefixLength) { prefix = null; prefixLength = default; var forwardSlashIndex = networkSpan.IndexOf('/'); if (forwardSlashIndex < 0) { return false; } if (!IPAddress.TryParse(networkSpan.Slice(0, forwardSlashIndex), out prefix)) { return false; } if (!int.TryParse(networkSpan.Slice(forwardSlashIndex + 1), out prefixLength)) { return false; } return true; } }