diff --git a/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs b/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs index ceb8ab32b997..6715668688d4 100644 --- a/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs +++ b/src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.HttpOverrides; using IPAddress = System.Net.IPAddress; +using IPNetwork = System.Net.IPNetwork; namespace Microsoft.AspNetCore.Builder; @@ -83,7 +84,10 @@ public class ForwardedHeadersOptions /// /// Address ranges of known proxies to accept forwarded headers from. /// - public IList KnownNetworks { get; } = new List() { new IPNetwork(IPAddress.Loopback, 8) }; + public IList KnownNetworks { get; } = new List() + { + IPNetwork.Parse("127.0.0.0/8") + }; /// /// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed. diff --git a/src/Middleware/HttpOverrides/src/IPNetwork.cs b/src/Middleware/HttpOverrides/src/IPNetwork.cs index 9888de2d1535..e28862bf229e 100644 --- a/src/Middleware/HttpOverrides/src/IPNetwork.cs +++ b/src/Middleware/HttpOverrides/src/IPNetwork.cs @@ -3,204 +3,65 @@ using System.Diagnostics.CodeAnalysis; using System.Net; -using System.Net.Sockets; namespace Microsoft.AspNetCore.HttpOverrides; /// /// A representation of an IP network based on CIDR notation. /// +[System.Obsolete("Please use System.Net.IPNetwork instead")] public class IPNetwork { + private readonly System.Net.IPNetwork _network; + /// /// Create a new with the specified and prefix length. /// /// The . /// The prefix length. /// is out of range. - 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; /// /// Get the that represents the prefix for the network. /// - public IPAddress Prefix { get; } - - private byte[] PrefixBytes { get; } + public IPAddress Prefix => _network.BaseAddress; /// /// The CIDR notation of the subnet mask /// - public int PrefixLength { get; } - - private byte[] Mask { get; } + public int PrefixLength => _network.PrefixLength; /// /// Determine whether a given The is part of the IP network. /// /// The . /// if the is part of the IP network. Otherwise, . - 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; - } + /// + public static IPNetwork Parse(ReadOnlySpan 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 - }; - } - - /// - /// Converts the specified of representation of - /// an IP address and a prefix length to its equivalent. - /// - /// The of to convert, in CIDR notation. - /// - ///The equivalent to the IP address and prefix length contained in . - /// - /// is not in the correct format. - /// The prefix length contained in is out of range. - /// - public static IPNetwork Parse(ReadOnlySpan networkSpan) + /// + public static bool TryParse(ReadOnlySpan 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; } /// - /// Converts the specified of representation of - /// an IP address and a prefix length to its equivalent, and returns a value - /// that indicates whether the conversion succeeded. + /// Convert to implicitly /// - /// The of to validate. - /// - /// When this method returns, contains the equivalent to the IP Address - /// and prefix length contained in , if the conversion succeeded, - /// or if the conversion failed. This parameter is passed uninitialized. - /// - /// - /// if the parameter was - /// converted successfully; otherwise . - /// - /// - public static bool TryParse(ReadOnlySpan 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; - } - - /// - /// - /// 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. - /// - /// - /// e.g. "192.168.0.1/31" for IPv4, "2001:db8:3c4d::1/127" for IPv6 - /// - /// - private static bool TryParseComponents( - ReadOnlySpan 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); } diff --git a/src/Middleware/HttpOverrides/src/PublicAPI.Unshipped.txt b/src/Middleware/HttpOverrides/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..81fc23c7a834 100644 --- a/src/Middleware/HttpOverrides/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/HttpOverrides/src/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +*REMOVED*Microsoft.AspNetCore.Builder.ForwardedHeadersOptions.KnownNetworks.get -> System.Collections.Generic.IList! +Microsoft.AspNetCore.Builder.ForwardedHeadersOptions.KnownNetworks.get -> System.Collections.Generic.IList! +static Microsoft.AspNetCore.HttpOverrides.IPNetwork.implicit operator Microsoft.AspNetCore.HttpOverrides.IPNetwork!(System.Net.IPNetwork ipNetwork) -> Microsoft.AspNetCore.HttpOverrides.IPNetwork! diff --git a/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs b/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs index 4fd1341acc45..96c1242cc5f3 100644 --- a/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs +++ b/src/Middleware/HttpOverrides/test/ForwardedHeadersMiddlewareTest.cs @@ -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) { @@ -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() diff --git a/src/Middleware/HttpOverrides/test/IPNetworkTest.cs b/src/Middleware/HttpOverrides/test/IPNetworkTest.cs deleted file mode 100644 index c8f33f7a333b..000000000000 --- a/src/Middleware/HttpOverrides/test/IPNetworkTest.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -using System.Net; - -namespace Microsoft.AspNetCore.HttpOverrides; - -public class IPNetworkTest -{ - [Theory] - [InlineData("10.1.1.0", 8, "10.1.1.10")] - [InlineData("174.0.0.0", 7, "175.1.1.10")] - [InlineData("10.174.0.0", 15, "10.175.1.10")] - [InlineData("10.168.0.0", 14, "10.171.1.10")] - [InlineData("192.168.0.1", 31, "192.168.0.0")] - [InlineData("192.168.0.1", 31, "192.168.0.1")] - [InlineData("192.168.0.1", 32, "192.168.0.1")] - [InlineData("192.168.1.1", 0, "0.0.0.0")] - [InlineData("192.168.1.1", 0, "255.255.255.255")] - [InlineData("2001:db8:3c4d::", 127, "2001:db8:3c4d::1")] - [InlineData("2001:db8:3c4d::1", 128, "2001:db8:3c4d::1")] - [InlineData("2001:db8:3c4d::1", 0, "::")] - [InlineData("2001:db8:3c4d::1", 0, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] - public void Contains_Positive(string prefixText, int length, string addressText) - { - var network = new IPNetwork(IPAddress.Parse(prefixText), length); - Assert.True(network.Contains(IPAddress.Parse(addressText))); - } - - [Theory] - [InlineData("10.1.0.0", 16, "10.2.1.10")] - [InlineData("174.0.0.0", 7, "173.1.1.10")] - [InlineData("10.174.0.0", 15, "10.173.1.10")] - [InlineData("10.168.0.0", 14, "10.172.1.10")] - [InlineData("192.168.0.1", 31, "192.168.0.2")] - [InlineData("192.168.0.1", 32, "192.168.0.0")] - [InlineData("2001:db8:3c4d::", 127, "2001:db8:3c4d::2")] - public void Contains_Negative(string prefixText, int length, string addressText) - { - var network = new IPNetwork(IPAddress.Parse(prefixText), length); - Assert.False(network.Contains(IPAddress.Parse(addressText))); - } - - [Theory] - [InlineData("192.168.1.1", 0)] - [InlineData("192.168.1.1", 32)] - [InlineData("2001:db8:3c4d::1", 0)] - [InlineData("2001:db8:3c4d::1", 128)] - public void Ctor_WithValidFormat_IsSuccessfullyCreated(string prefixText, int prefixLength) - { - // Arrange - var address = IPAddress.Parse(prefixText); - - // Act - var network = new IPNetwork(address, prefixLength); - - // Assert - Assert.Equal(prefixText, network.Prefix.ToString()); - Assert.Equal(prefixLength, network.PrefixLength); - } - - [Theory] - [InlineData("192.168.1.1", -1)] - [InlineData("192.168.1.1", 33)] - [InlineData("2001:db8:3c4d::1", -1)] - [InlineData("2001:db8:3c4d::1", 129)] - public void Ctor_WithPrefixLengthOutOfRange_ThrowsArgumentOutOfRangeException(string prefixText, int prefixLength) - { - // Arrange - var address = IPAddress.Parse(prefixText); - - // Act - var ex = Assert.Throws(() => new IPNetwork(address, prefixLength)); - - // Assert - Assert.StartsWith("The prefix length was out of range.", ex.Message); - } - - [Theory] - [MemberData(nameof(ValidPrefixWithPrefixLengthData))] - public void Parse_WithValidFormat_ParsedCorrectly(string input, string expectedPrefix, int expectedPrefixLength) - { - // Act - var network = IPNetwork.Parse(input); - - // Assert - Assert.Equal(expectedPrefix, network.Prefix.ToString()); - Assert.Equal(expectedPrefixLength, network.PrefixLength); - } - - [Theory] - [InlineData(null)] - [MemberData(nameof(InvalidPrefixOrPrefixLengthData))] - public void Parse_WithInvalidFormat_ThrowsFormatException(string input) - { - // Arrange & Act & Assert - var ex = Assert.Throws(() => IPNetwork.Parse(input)); - Assert.Equal("An invalid IP address or prefix length was specified.", ex.Message); - } - - [Theory] - [MemberData(nameof(PrefixLengthOutOfRangeData))] - public void Parse_WithOutOfRangePrefixLength_ThrowsArgumentOutOfRangeException(string input) - { - // Arrange & Act & Assert - var ex = Assert.Throws(() => IPNetwork.Parse(input)); - Assert.StartsWith("The prefix length was out of range.", ex.Message); - } - - [Theory] - [MemberData(nameof(ValidPrefixWithPrefixLengthData))] - public void TryParse_WithValidFormat_ParsedCorrectly(string input, string expectedPrefix, int expectedPrefixLength) - { - // Act - var result = IPNetwork.TryParse(input, out var network); - - // Assert - Assert.True(result); - Assert.NotNull(network); - Assert.Equal(expectedPrefix, network.Prefix.ToString()); - Assert.Equal(expectedPrefixLength, network.PrefixLength); - } - - [Theory] - [InlineData(null)] - [MemberData(nameof(InvalidPrefixOrPrefixLengthData))] - [MemberData(nameof(PrefixLengthOutOfRangeData))] - public void TryParse_WithInvalidFormat_ReturnsFalse(string input) - { - // Act - var result = IPNetwork.TryParse(input, out var network); - - // Assert - Assert.False(result); - Assert.Null(network); - } - - public static TheoryData ValidPrefixWithPrefixLengthData() => new() - { - // IPv4 - { "10.1.0.0/16", "10.1.0.0", 16 }, - { "10.1.1.0/8", "10.1.1.0", 8 }, - { "174.0.0.0/7", "174.0.0.0", 7 }, - { "10.174.0.0/15", "10.174.0.0", 15 }, - { "10.168.0.0/14", "10.168.0.0", 14 }, - { "192.168.0.1/31", "192.168.0.1", 31 }, - { "192.168.0.1/31", "192.168.0.1", 31 }, - { "192.168.0.1/32", "192.168.0.1", 32 }, - { "192.168.1.1/0", "192.168.1.1", 0 }, - { "192.168.1.1/0", "192.168.1.1", 0 }, - - // IPv6 - { "2001:db8:3c4d::/127", "2001:db8:3c4d::", 127 }, - { "2001:db8:3c4d::1/128", "2001:db8:3c4d::1", 128 }, - { "2001:db8:3c4d::1/0", "2001:db8:3c4d::1", 0 }, - { "2001:db8:3c4d::1/0", "2001:db8:3c4d::1", 0 } - }; - - public static TheoryData InvalidPrefixOrPrefixLengthData() => new() - { - string.Empty, - "abcdefg", - - // Missing forward slash - "10.1.0.016", - "2001:db8:3c4d::1127", - - // Invalid prefix - "/16", - "10.1./16", - "10.1.0./16", - "10.1.ABC.0/16", - "200123:db8:3c4d::/127", - ":db8:3c4d::/127", - "2001:?:3c4d::1/0", - - // Invalid prefix length - "10.1.0.0/", - "10.1.0.0/16-", - "10.1.0.0/ABC", - "2001:db8:3c4d::/", - "2001:db8:3c4d::1/128-", - "2001:db8:3c4d::1/ABC" - }; - - public static TheoryData PrefixLengthOutOfRangeData() => new() - { - // Negative prefix length - "10.1.0.0/-16", - "2001:db8:3c4d::/-127", - - // Prefix length out of range (IPv4) - "10.1.0.0/33", - - // Prefix length out of range (IPv6) - "2001:db8:3c4d::/129" - }; -}