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)