Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8258d90
Update IMicrosoft.AspNetCore.HttpOverrides.PNetwork implementation
WeihanLi Jun 27, 2025
8bbc434
Update IPNetwork constructor for implicit convert
WeihanLi Jun 27, 2025
7372e0b
Obsolete IPNetwork.cs
WeihanLi Jun 27, 2025
1e44ff7
Update invalid comment tag
WeihanLi Jun 27, 2025
7e5db2a
fix IPNetwork.cs error
WeihanLi Jun 27, 2025
f71426d
use System.Net.IPNetwork for ForwardedHeadersOptions
WeihanLi Jun 27, 2025
7993c1f
update publicapi
WeihanLi Jun 27, 2025
996592b
Update IPNetwork.cs comment inherit error
WeihanLi Jun 27, 2025
2d015aa
include implicit convert into public api
WeihanLi Jun 27, 2025
6346519
fix IPNetwork.TryParse comment
WeihanLi Jun 27, 2025
21547f6
Merge branch 'dotnet:main' into patch-1
WeihanLi Jun 27, 2025
74be29f
fix comment and publish api error
WeihanLi Jun 28, 2025
61c9246
fix KnownNetwork PublicAPI
WeihanLi Jun 28, 2025
dfc068c
update PublicAPI
WeihanLi Jun 28, 2025
a0f13a0
remove IPNetwork implicit from PublicAPI.Shipped.txt
WeihanLi Jun 28, 2025
4f63a58
test: fix test cases
WeihanLi Jun 28, 2025
1c2b21f
refactor: remove the implicit convert for obsolete IPNetwork
WeihanLi Jul 11, 2025
b74969e
remove missed implicit convert
WeihanLi Jul 11, 2025
f637aec
revert unnecessay changes
WeihanLi Jul 11, 2025
d8c7cb3
test: update test cases
WeihanLi Jul 14, 2025
51dba56
Merge branch 'patch-1' of https://github.com/WeihanLi/aspnetcore into…
WeihanLi Jul 18, 2025
8795915
feat: add KnownIPNetworks
WeihanLi Jul 18, 2025
22748db
feat: update PublicAPI.Unshipped.txt
WeihanLi Jul 18, 2025
5f59285
feat: add back Clear for legacy IPNetwork
WeihanLi Jul 18, 2025
720d723
feat: revert Microsoft.AspNetCore.HttpOverrides.IPNetwork
WeihanLi Jul 18, 2025
f3a8519
mark Microsoft.AspNetCore.HttpOverrides.IPNetwork obsolete
WeihanLi Jul 18, 2025
3d4f91a
update obsolete message and diagnosticId
WeihanLi Jul 19, 2025
90840a1
update obsolete message for HttpOverride.IPNetwork
WeihanLi Jul 19, 2025
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