Skip to content

Commit c1523bb

Browse files
ManickaPMihaZupan
andauthored
[NET] IPNetwork clears the lower bits instead of throwing. (#117367)
* IPNetwork clears the lower bits instead of throwing. * Update src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs Co-authored-by: Miha Zupan <[email protected]> * Update src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs Co-authored-by: Miha Zupan <[email protected]> --------- Co-authored-by: Miha Zupan <[email protected]>
1 parent d751cbc commit c1523bb

File tree

3 files changed

+65
-44
lines changed

3 files changed

+65
-44
lines changed

src/libraries/System.Net.Primitives/src/Resources/Strings.resx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@
8181
<data name="net_bad_ip_network" xml:space="preserve">
8282
<value>An invalid IP network was specified.</value>
8383
</data>
84-
<data name="net_bad_ip_network_invalid_baseaddress" xml:space="preserve">
85-
<value>The specified baseAddress has non-zero bits after the network prefix.</value>
86-
</data>
8784
<data name="net_container_add_cookie" xml:space="preserve">
8885
<value>An error occurred when adding a cookie to the container.</value>
8986
</data>

src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,11 @@ public IPNetwork(IPAddress baseAddress, int prefixLength)
5050
ThrowArgumentOutOfRangeException();
5151
}
5252

53-
if (HasNonZeroBitsAfterNetworkPrefix(baseAddress, prefixLength))
54-
{
55-
ThrowInvalidBaseAddressException();
56-
}
57-
58-
_baseAddress = baseAddress;
53+
_baseAddress = ClearNonZeroBitsAfterNetworkPrefix(baseAddress, prefixLength);
5954
PrefixLength = prefixLength;
6055

6156
[DoesNotReturn]
6257
static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(prefixLength));
63-
64-
[DoesNotReturn]
65-
static void ThrowInvalidBaseAddressException() => throw new ArgumentException(SR.net_bad_ip_network_invalid_baseaddress, nameof(baseAddress));
6658
}
6759

6860
// Non-validating ctor
@@ -201,8 +193,7 @@ public static bool TryParse(ReadOnlySpan<char> s, out IPNetwork result)
201193

202194
if (IPAddress.TryParse(ipAddressSpan, out IPAddress? address) &&
203195
int.TryParse(prefixLengthSpan, NumberStyles.None, CultureInfo.InvariantCulture, out int prefixLength) &&
204-
prefixLength <= GetMaxPrefixLength(address) &&
205-
!HasNonZeroBitsAfterNetworkPrefix(address, prefixLength))
196+
prefixLength <= GetMaxPrefixLength(address))
206197
{
207198
Debug.Assert(prefixLength >= 0); // Parsing with NumberStyles.None should ensure that prefixLength is always non-negative.
208199
result = new IPNetwork(address, prefixLength, false);
@@ -230,8 +221,7 @@ public static bool TryParse(ReadOnlySpan<byte> utf8Text, out IPNetwork result)
230221

231222
if (IPAddress.TryParse(ipAddressSpan, out IPAddress? address) &&
232223
int.TryParse(prefixLengthSpan, NumberStyles.None, CultureInfo.InvariantCulture, out int prefixLength) &&
233-
prefixLength <= GetMaxPrefixLength(address) &&
234-
!HasNonZeroBitsAfterNetworkPrefix(address, prefixLength))
224+
prefixLength <= GetMaxPrefixLength(address))
235225
{
236226
Debug.Assert(prefixLength >= 0); // Parsing with NumberStyles.None should ensure that prefixLength is always non-negative.
237227
result = new IPNetwork(address, prefixLength, false);
@@ -245,36 +235,51 @@ public static bool TryParse(ReadOnlySpan<byte> utf8Text, out IPNetwork result)
245235

246236
private static int GetMaxPrefixLength(IPAddress baseAddress) => baseAddress.AddressFamily == AddressFamily.InterNetwork ? 32 : 128;
247237

248-
private static bool HasNonZeroBitsAfterNetworkPrefix(IPAddress baseAddress, int prefixLength)
238+
private static IPAddress ClearNonZeroBitsAfterNetworkPrefix(IPAddress baseAddress, int prefixLength)
249239
{
250240
if (baseAddress.AddressFamily == AddressFamily.InterNetwork)
251241
{
252-
// The cast to long ensures that the mask becomes 0 for the case where 'prefixLength == 0'.
253-
uint mask = (uint)((long)uint.MaxValue << (32 - prefixLength));
242+
// Bitwise shift works only for lower 5-bits count operands.
243+
if (prefixLength == 0)
244+
{
245+
// Corresponds to 0.0.0.0
246+
return IPAddress.Any;
247+
}
248+
249+
uint mask = uint.MaxValue << (32 - prefixLength);
254250
if (BitConverter.IsLittleEndian)
255251
{
256252
mask = BinaryPrimitives.ReverseEndianness(mask);
257253
}
258254

259-
return (baseAddress.PrivateAddress & mask) != baseAddress.PrivateAddress;
255+
uint newAddress = baseAddress.PrivateAddress & mask;
256+
return newAddress == baseAddress.PrivateAddress
257+
? baseAddress
258+
: new IPAddress(newAddress);
260259
}
261260
else
262261
{
263-
UInt128 value = default;
264-
baseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span<UInt128>(ref value)), out int bytesWritten);
265-
Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes);
262+
// Bitwise shift works only for lower 7-bits count operands.
266263
if (prefixLength == 0)
267264
{
268-
return value != UInt128.Zero;
265+
// Corresponds to [::]
266+
return IPAddress.IPv6Any;
269267
}
270268

269+
UInt128 value = default;
270+
baseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span<UInt128>(ref value)), out int bytesWritten);
271+
Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes);
272+
271273
UInt128 mask = UInt128.MaxValue << (128 - prefixLength);
272274
if (BitConverter.IsLittleEndian)
273275
{
274276
mask = BinaryPrimitives.ReverseEndianness(mask);
275277
}
276278

277-
return (value & mask) != value;
279+
UInt128 newAddress = value & mask;
280+
return newAddress == value
281+
? baseAddress
282+
: new IPAddress(MemoryMarshal.AsBytes(new Span<UInt128>(ref newAddress)));
278283
}
279284
}
280285

src/libraries/System.Net.Primitives/tests/FunctionalTests/IPNetworkTest.cs

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers.Binary;
45
using System.Collections;
56
using System.Collections.Generic;
7+
using System.Diagnostics;
68
using System.Linq;
9+
using System.Net.Sockets;
10+
using System.Runtime.InteropServices;
711
using System.Text;
812
using Xunit;
913

@@ -26,38 +30,64 @@ public class IPNetworkTest
2630
{
2731
{ "127.0.0.1/33" }, // PrefixLength max is 32 for IPv4
2832
{ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/129" }, // PrefixLength max is 128 for IPv6
29-
{ "127.0.0.1/31" }, // Bits exceed the prefix length of 31 (32nd bit is on)
30-
{ "198.51.255.0/23" }, // Bits exceed the prefix length of 23
31-
{ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/127" }, // Bits exceed the prefix length of 31
32-
{ "2a01:110:8012::/45" }, // Bits exceed the prefix length of 45 (47th bit is on)
3333
};
3434

3535
public static TheoryData<string> ValidIPNetworkData = new TheoryData<string>()
3636
{
3737
{ "0.0.0.0/32" }, // the whole IPv4 space
3838
{ "0.0.0.0/0" },
39+
{ "192.168.0.10/0" },
3940
{ "128.0.0.0/1" },
41+
{ "127.0.0.1/8" },
42+
{ "127.0.0.1/31" },
43+
{ "198.51.255.0/23" },
4044
{ "::/128" }, // the whole IPv6 space
4145
{ "255.255.255.255/32" },
4246
{ "198.51.254.0/23" },
4347
{ "42.42.128.0/17" },
4448
{ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" },
4549
{ "2a01:110:8012::/47" },
4650
{ "2a01:110:8012::/100" },
51+
{ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/127" },
52+
{ "2a01:110:8012::/45" },
4753
};
4854

55+
private uint GetMask32(int prefix)
56+
{
57+
Debug.Assert(prefix != 0);
58+
59+
uint mask = uint.MaxValue << (32 - prefix);
60+
return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(mask) : mask;
61+
}
62+
private UInt128 GetMask128(int prefix)
63+
{
64+
Debug.Assert(prefix != 0);
65+
66+
UInt128 mask = UInt128.MaxValue << (128 - prefix);
67+
return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(mask) : mask;
68+
}
69+
private IPAddress GetBaseAddress(IPAddress address, int prefix)
70+
=> (address.AddressFamily, prefix) switch
71+
{
72+
(AddressFamily.InterNetwork, 0) => new IPAddress([0, 0, 0, 0]),
73+
(AddressFamily.InterNetwork, _) => new IPAddress(MemoryMarshal.Read<uint>(address.GetAddressBytes()) & GetMask32(prefix)),
74+
(AddressFamily.InterNetworkV6, 0) => new IPAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
75+
(AddressFamily.InterNetworkV6, _) => new IPAddress(MemoryMarshal.AsBytes([MemoryMarshal.Read<UInt128>(address.GetAddressBytes()) & GetMask128(prefix)])),
76+
_ => throw new ArgumentOutOfRangeException($"Unexpected address family {address.AddressFamily} of {address}.")
77+
};
78+
4979
[Theory]
5080
[MemberData(nameof(ValidIPNetworkData))]
5181
public void Constructor_Valid_Succeeds(string input)
5282
{
5383
string[] splitInput = input.Split('/');
5484
IPAddress address = IPAddress.Parse(splitInput[0]);
55-
int prefixLegth = int.Parse(splitInput[1]);
85+
int prefixLength = int.Parse(splitInput[1]);
5686

57-
IPNetwork network = new IPNetwork(address, prefixLegth);
87+
IPNetwork network = new IPNetwork(address, prefixLength);
5888

59-
Assert.Equal(address, network.BaseAddress);
60-
Assert.Equal(prefixLegth, network.PrefixLength);
89+
Assert.Equal(GetBaseAddress(address, prefixLength), network.BaseAddress);
90+
Assert.Equal(prefixLength, network.PrefixLength);
6191
}
6292

6393
[Fact]
@@ -77,17 +107,6 @@ public void Constructor_PrefixLenghtOutOfRange_ThrowsArgumentOutOfRangeException
77107
Assert.Throws<ArgumentOutOfRangeException>(() => new IPNetwork(address, prefixLength));
78108
}
79109

80-
[Theory]
81-
[InlineData("192.168.0.1", 31)]
82-
[InlineData("42.42.192.0", 17)]
83-
[InlineData("128.0.0.0", 0)]
84-
[InlineData("2a01:110:8012::", 46)]
85-
public void Constructor_NonZeroBitsAfterNetworkPrefix_ThrowsArgumentException(string ipStr, int prefixLength)
86-
{
87-
IPAddress address = IPAddress.Parse(ipStr);
88-
Assert.Throws<ArgumentException>(() => new IPNetwork(address, prefixLength));
89-
}
90-
91110
[Theory]
92111
[MemberData(nameof(IncorrectFormatData))]
93112
public void Parse_IncorrectFormat_ThrowsFormatException(string input)

0 commit comments

Comments
 (0)