diff --git a/src/libraries/System.Net.Primitives/src/Resources/Strings.resx b/src/libraries/System.Net.Primitives/src/Resources/Strings.resx index 65d4809398b3b2..c9cc5be51acdf2 100644 --- a/src/libraries/System.Net.Primitives/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Primitives/src/Resources/Strings.resx @@ -81,9 +81,6 @@ An invalid IP network was specified. - - The specified baseAddress has non-zero bits after the network prefix. - An error occurred when adding a cookie to the container. diff --git a/src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs b/src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs index 99b4b13e82aeff..cc522b950c0bfc 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs @@ -52,19 +52,11 @@ public IPNetwork(IPAddress baseAddress, int prefixLength) ThrowArgumentOutOfRangeException(); } - if (HasNonZeroBitsAfterNetworkPrefix(baseAddress, prefixLength)) - { - ThrowInvalidBaseAddressException(); - } - - _baseAddress = baseAddress; + _baseAddress = ClearNonZeroBitsAfterNetworkPrefix(baseAddress, prefixLength); PrefixLength = prefixLength; [DoesNotReturn] static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(prefixLength)); - - [DoesNotReturn] - static void ThrowInvalidBaseAddressException() => throw new ArgumentException(SR.net_bad_ip_network_invalid_baseaddress, nameof(baseAddress)); } // Non-validating ctor @@ -203,8 +195,7 @@ public static bool TryParse(ReadOnlySpan s, out IPNetwork result) if (IPAddress.TryParse(ipAddressSpan, out IPAddress? address) && int.TryParse(prefixLengthSpan, NumberStyles.None, CultureInfo.InvariantCulture, out int prefixLength) && - prefixLength <= GetMaxPrefixLength(address) && - !HasNonZeroBitsAfterNetworkPrefix(address, prefixLength)) + prefixLength <= GetMaxPrefixLength(address)) { Debug.Assert(prefixLength >= 0); // Parsing with NumberStyles.None should ensure that prefixLength is always non-negative. result = new IPNetwork(address, prefixLength, false); @@ -232,8 +223,7 @@ public static bool TryParse(ReadOnlySpan utf8Text, out IPNetwork result) if (IPAddress.TryParse(ipAddressSpan, out IPAddress? address) && int.TryParse(prefixLengthSpan, NumberStyles.None, CultureInfo.InvariantCulture, out int prefixLength) && - prefixLength <= GetMaxPrefixLength(address) && - !HasNonZeroBitsAfterNetworkPrefix(address, prefixLength)) + prefixLength <= GetMaxPrefixLength(address)) { Debug.Assert(prefixLength >= 0); // Parsing with NumberStyles.None should ensure that prefixLength is always non-negative. result = new IPNetwork(address, prefixLength, false); @@ -247,36 +237,51 @@ public static bool TryParse(ReadOnlySpan utf8Text, out IPNetwork result) private static int GetMaxPrefixLength(IPAddress baseAddress) => baseAddress.AddressFamily == AddressFamily.InterNetwork ? 32 : 128; - private static bool HasNonZeroBitsAfterNetworkPrefix(IPAddress baseAddress, int prefixLength) + private static IPAddress ClearNonZeroBitsAfterNetworkPrefix(IPAddress baseAddress, int prefixLength) { if (baseAddress.AddressFamily == AddressFamily.InterNetwork) { - // The cast to long ensures that the mask becomes 0 for the case where 'prefixLength == 0'. - uint mask = (uint)((long)uint.MaxValue << (32 - prefixLength)); + // Bitwise shift works only for lower 5-bits count operands. + if (prefixLength == 0) + { + // Corresponds to 0.0.0.0 + return IPAddress.Any; + } + + uint mask = uint.MaxValue << (32 - prefixLength); if (BitConverter.IsLittleEndian) { mask = BinaryPrimitives.ReverseEndianness(mask); } - return (baseAddress.PrivateAddress & mask) != baseAddress.PrivateAddress; + uint newAddress = baseAddress.PrivateAddress & mask; + return newAddress == baseAddress.PrivateAddress + ? baseAddress + : new IPAddress(newAddress); } else { - UInt128 value = default; - baseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span(ref value)), out int bytesWritten); - Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes); + // Bitwise shift works only for lower 7-bits count operands. if (prefixLength == 0) { - return value != UInt128.Zero; + // Corresponds to [::] + return IPAddress.IPv6Any; } + UInt128 value = default; + baseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span(ref value)), out int bytesWritten); + Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes); + UInt128 mask = UInt128.MaxValue << (128 - prefixLength); if (BitConverter.IsLittleEndian) { mask = BinaryPrimitives.ReverseEndianness(mask); } - return (value & mask) != value; + UInt128 newAddress = value & mask; + return newAddress == value + ? baseAddress + : new IPAddress(MemoryMarshal.AsBytes(new Span(ref newAddress))); } } diff --git a/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPNetworkTest.cs b/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPNetworkTest.cs index ea8d31cd808f40..b73a2831b9a2d8 100644 --- a/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPNetworkTest.cs +++ b/src/libraries/System.Net.Primitives/tests/FunctionalTests/IPNetworkTest.cs @@ -1,9 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Text; using Xunit; @@ -26,17 +30,17 @@ public class IPNetworkTest { { "127.0.0.1/33" }, // PrefixLength max is 32 for IPv4 { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/129" }, // PrefixLength max is 128 for IPv6 - { "127.0.0.1/31" }, // Bits exceed the prefix length of 31 (32nd bit is on) - { "198.51.255.0/23" }, // Bits exceed the prefix length of 23 - { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/127" }, // Bits exceed the prefix length of 31 - { "2a01:110:8012::/45" }, // Bits exceed the prefix length of 45 (47th bit is on) }; public static TheoryData ValidIPNetworkData = new TheoryData() { { "0.0.0.0/32" }, // the whole IPv4 space { "0.0.0.0/0" }, + { "192.168.0.10/0" }, { "128.0.0.0/1" }, + { "127.0.0.1/8" }, + { "127.0.0.1/31" }, + { "198.51.255.0/23" }, { "::/128" }, // the whole IPv6 space { "255.255.255.255/32" }, { "198.51.254.0/23" }, @@ -44,20 +48,46 @@ public class IPNetworkTest { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" }, { "2a01:110:8012::/47" }, { "2a01:110:8012::/100" }, + { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/127" }, + { "2a01:110:8012::/45" }, }; + private uint GetMask32(int prefix) + { + Debug.Assert(prefix != 0); + + uint mask = uint.MaxValue << (32 - prefix); + return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(mask) : mask; + } + private UInt128 GetMask128(int prefix) + { + Debug.Assert(prefix != 0); + + UInt128 mask = UInt128.MaxValue << (128 - prefix); + return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(mask) : mask; + } + private IPAddress GetBaseAddress(IPAddress address, int prefix) + => (address.AddressFamily, prefix) switch + { + (AddressFamily.InterNetwork, 0) => new IPAddress([0, 0, 0, 0]), + (AddressFamily.InterNetwork, _) => new IPAddress(MemoryMarshal.Read(address.GetAddressBytes()) & GetMask32(prefix)), + (AddressFamily.InterNetworkV6, 0) => new IPAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + (AddressFamily.InterNetworkV6, _) => new IPAddress(MemoryMarshal.AsBytes([MemoryMarshal.Read(address.GetAddressBytes()) & GetMask128(prefix)])), + _ => throw new ArgumentOutOfRangeException($"Unexpected address family {address.AddressFamily} of {address}.") + }; + [Theory] [MemberData(nameof(ValidIPNetworkData))] public void Constructor_Valid_Succeeds(string input) { string[] splitInput = input.Split('/'); IPAddress address = IPAddress.Parse(splitInput[0]); - int prefixLegth = int.Parse(splitInput[1]); + int prefixLength = int.Parse(splitInput[1]); - IPNetwork network = new IPNetwork(address, prefixLegth); + IPNetwork network = new IPNetwork(address, prefixLength); - Assert.Equal(address, network.BaseAddress); - Assert.Equal(prefixLegth, network.PrefixLength); + Assert.Equal(GetBaseAddress(address, prefixLength), network.BaseAddress); + Assert.Equal(prefixLength, network.PrefixLength); } [Fact] @@ -77,17 +107,6 @@ public void Constructor_PrefixLenghtOutOfRange_ThrowsArgumentOutOfRangeException Assert.Throws(() => new IPNetwork(address, prefixLength)); } - [Theory] - [InlineData("192.168.0.1", 31)] - [InlineData("42.42.192.0", 17)] - [InlineData("128.0.0.0", 0)] - [InlineData("2a01:110:8012::", 46)] - public void Constructor_NonZeroBitsAfterNetworkPrefix_ThrowsArgumentException(string ipStr, int prefixLength) - { - IPAddress address = IPAddress.Parse(ipStr); - Assert.Throws(() => new IPNetwork(address, prefixLength)); - } - [Theory] [MemberData(nameof(IncorrectFormatData))] public void Parse_IncorrectFormat_ThrowsFormatException(string input)