Skip to content

Obsolete Microsoft.AspNetCore.HttpOverrides.IPNetwork #62490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.AspNetCore.HttpOverrides;
using IPAddress = System.Net.IPAddress;
using IPNetwork = System.Net.IPNetwork;

namespace Microsoft.AspNetCore.Builder;

Expand Down Expand Up @@ -83,7 +84,10 @@ public class ForwardedHeadersOptions
/// <summary>
/// Address ranges of known proxies to accept forwarded headers from.
/// </summary>
public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>()
{
IPNetwork.Parse("127.0.0.0/8")
};

/// <summary>
/// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed.
Expand Down
179 changes: 20 additions & 159 deletions src/Middleware/HttpOverrides/src/IPNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,204 +3,65 @@

using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets;

namespace Microsoft.AspNetCore.HttpOverrides;

/// <summary>
/// A representation of an IP network based on CIDR notation.
/// </summary>
[System.Obsolete("Please use System.Net.IPNetwork instead")]
public class IPNetwork
{
private readonly System.Net.IPNetwork _network;

/// <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)
public IPNetwork(IPAddress prefix, int prefixLength)
{
_network = new(prefix, prefixLength);
}

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();
}
private IPNetwork(System.Net.IPNetwork network) => _network = network;

/// <summary>
/// Get the <see cref="IPAddress"/> that represents the prefix for the network.
/// </summary>
public IPAddress Prefix { get; }

private byte[] PrefixBytes { get; }
public IPAddress Prefix => _network.BaseAddress;

/// <summary>
/// The CIDR notation of the subnet mask
/// </summary>
public int PrefixLength { get; }

private byte[] Mask { get; }
public int PrefixLength => _network.PrefixLength;

/// <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;
}
}
public bool Contains(IPAddress address) => _network.Contains(address);

return true;
}
/// <inheritdoc cref="System.Net.IPNetwork.Parse(ReadOnlySpan{char})"/>
public static IPNetwork Parse(ReadOnlySpan<char> networkSpan) => System.Net.IPNetwork.Parse(networkSpan);

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)
/// <inheritdoc cref="System.Net.IPNetwork.TryParse(ReadOnlySpan{char}, out System.Net.IPNetwork)"/>
public static bool TryParse(ReadOnlySpan<char> networkSpan, [NotNullWhen(true)] out IPNetwork? network)
{
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))
if (System.Net.IPNetwork.TryParse(networkSpan, out var ipNetwork))
{
throw new ArgumentOutOfRangeException(nameof(networkSpan), "The prefix length was out of range.");
network = ipNetwork;
return true;
}

return new IPNetwork(prefix, prefixLength, false);
network = null;
return 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.
/// Convert <see cref="System.Net.IPNetwork" /> to <see cref="Microsoft.AspNetCore.HttpOverrides.IPNetwork" /> implicitly
/// </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;
}
public static implicit operator IPNetwork(System.Net.IPNetwork ipNetwork) => new IPNetwork(ipNetwork);
}
3 changes: 3 additions & 0 deletions src/Middleware/HttpOverrides/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
#nullable enable
*REMOVED*Microsoft.AspNetCore.Builder.ForwardedHeadersOptions.KnownNetworks.get -> System.Collections.Generic.IList<Microsoft.AspNetCore.HttpOverrides.IPNetwork!>!
Microsoft.AspNetCore.Builder.ForwardedHeadersOptions.KnownNetworks.get -> System.Collections.Generic.IList<System.Net.IPNetwork>!
static Microsoft.AspNetCore.HttpOverrides.IPNetwork.implicit operator Microsoft.AspNetCore.HttpOverrides.IPNetwork!(System.Net.IPNetwork ipNetwork) -> Microsoft.AspNetCore.HttpOverrides.IPNetwork!
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ public async Task PartiallyEnabledForwardsPartiallyChangesRequest()
[InlineData("22.33.44.55,::ffff:172.123.142.121", "172.123.142.121", "", "22.33.44.55")]
[InlineData("22.33.44.55,::ffff:172.123.142.121", "::ffff:172.123.142.121", "", "22.33.44.55")]
[InlineData("22.33.44.55,::ffff:172.123.142.121,172.32.24.23", "", "172.0.0.0/8", "22.33.44.55")]
[InlineData("2a00:1450:4009:802::200e,2a02:26f0:2d:183::356e,::ffff:172.123.142.121,172.32.24.23", "", "172.0.0.0/8,2a02:26f0:2d:183::1/64", "2a00:1450:4009:802::200e")]
[InlineData("2a00:1450:4009:802::200e,2a02:26f0:2d:183::356e,::ffff:172.123.142.121,172.32.24.23", "", "172.0.0.0/8,2a02:26f0:2d:183::/64", "2a00:1450:4009:802::200e")]
[InlineData("22.33.44.55,2a02:26f0:2d:183::356e,::ffff:127.0.0.1", "2a02:26f0:2d:183::356e", "", "22.33.44.55")]
public async Task XForwardForIPv4ToIPv6Mapping(string forHeader, string knownProxies, string knownNetworks, string expectedRemoteIp)
{
Expand All @@ -1092,7 +1092,7 @@ public async Task XForwardForIPv4ToIPv6Mapping(string forHeader, string knownPro
var knownNetworkParts = knownNetwork.Split('/');
var networkIp = IPAddress.Parse(knownNetworkParts[0]);
var prefixLength = int.Parse(knownNetworkParts[1], CultureInfo.InvariantCulture);
options.KnownNetworks.Add(new IPNetwork(networkIp, prefixLength));
options.KnownNetworks.Add(new System.Net.IPNetwork(networkIp, prefixLength));
}

using var host = new HostBuilder()
Expand Down
Loading
Loading