Skip to content

Commit 7d11140

Browse files
committed
IPNetwork clears the lower bits instead of throwing.
1 parent 4cc3020 commit 7d11140

File tree

3 files changed

+60
-44
lines changed

3 files changed

+60
-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: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,11 @@ public IPNetwork(IPAddress baseAddress, int prefixLength)
5252
ThrowArgumentOutOfRangeException();
5353
}
5454

55-
if (HasNonZeroBitsAfterNetworkPrefix(baseAddress, prefixLength))
56-
{
57-
ThrowInvalidBaseAddressException();
58-
}
59-
60-
_baseAddress = baseAddress;
55+
_baseAddress = ClearNonZeroBitsAfterNetworkPrefix(baseAddress, prefixLength);
6156
PrefixLength = prefixLength;
6257

6358
[DoesNotReturn]
6459
static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(prefixLength));
65-
66-
[DoesNotReturn]
67-
static void ThrowInvalidBaseAddressException() => throw new ArgumentException(SR.net_bad_ip_network_invalid_baseaddress, nameof(baseAddress));
6860
}
6961

7062
// Non-validating ctor
@@ -203,8 +195,7 @@ public static bool TryParse(ReadOnlySpan<char> s, out IPNetwork result)
203195

204196
if (IPAddress.TryParse(ipAddressSpan, out IPAddress? address) &&
205197
int.TryParse(prefixLengthSpan, NumberStyles.None, CultureInfo.InvariantCulture, out int prefixLength) &&
206-
prefixLength <= GetMaxPrefixLength(address) &&
207-
!HasNonZeroBitsAfterNetworkPrefix(address, prefixLength))
198+
prefixLength <= GetMaxPrefixLength(address))
208199
{
209200
Debug.Assert(prefixLength >= 0); // Parsing with NumberStyles.None should ensure that prefixLength is always non-negative.
210201
result = new IPNetwork(address, prefixLength, false);
@@ -232,8 +223,7 @@ public static bool TryParse(ReadOnlySpan<byte> utf8Text, out IPNetwork result)
232223

233224
if (IPAddress.TryParse(ipAddressSpan, out IPAddress? address) &&
234225
int.TryParse(prefixLengthSpan, NumberStyles.None, CultureInfo.InvariantCulture, out int prefixLength) &&
235-
prefixLength <= GetMaxPrefixLength(address) &&
236-
!HasNonZeroBitsAfterNetworkPrefix(address, prefixLength))
226+
prefixLength <= GetMaxPrefixLength(address))
237227
{
238228
Debug.Assert(prefixLength >= 0); // Parsing with NumberStyles.None should ensure that prefixLength is always non-negative.
239229
result = new IPNetwork(address, prefixLength, false);
@@ -247,36 +237,46 @@ public static bool TryParse(ReadOnlySpan<byte> utf8Text, out IPNetwork result)
247237

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

250-
private static bool HasNonZeroBitsAfterNetworkPrefix(IPAddress baseAddress, int prefixLength)
240+
private static IPAddress ClearNonZeroBitsAfterNetworkPrefix(IPAddress baseAddress, int prefixLength)
251241
{
252242
if (baseAddress.AddressFamily == AddressFamily.InterNetwork)
253243
{
254-
// The cast to long ensures that the mask becomes 0 for the case where 'prefixLength == 0'.
255-
uint mask = (uint)((long)uint.MaxValue << (32 - prefixLength));
244+
// Bitwise shift works only for lower 5-bits count operands.
245+
if (prefixLength == 0)
246+
{
247+
// Corresponds to 0.0.0.0
248+
return IPAddress.Any;
249+
}
250+
251+
uint mask = uint.MaxValue << (32 - prefixLength);
256252
if (BitConverter.IsLittleEndian)
257253
{
258254
mask = BinaryPrimitives.ReverseEndianness(mask);
259255
}
260256

261-
return (baseAddress.PrivateAddress & mask) != baseAddress.PrivateAddress;
257+
return new IPAddress(baseAddress.PrivateAddress & mask);
262258
}
263259
else
264260
{
265-
UInt128 value = default;
266-
baseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span<UInt128>(ref value)), out int bytesWritten);
267-
Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes);
261+
// Bitwise shift works only for lower 7-bits count operands.
268262
if (prefixLength == 0)
269263
{
270-
return value != UInt128.Zero;
264+
// Corresponds to [::]
265+
return IPAddress.IPv6Any;
271266
}
272267

268+
UInt128 value = default;
269+
baseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span<UInt128>(ref value)), out int bytesWritten);
270+
Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes);
271+
273272
UInt128 mask = UInt128.MaxValue << (128 - prefixLength);
274273
if (BitConverter.IsLittleEndian)
275274
{
276275
mask = BinaryPrimitives.ReverseEndianness(mask);
277276
}
278277

279-
return (value & mask) != value;
278+
value &= mask;
279+
return new IPAddress(MemoryMarshal.AsBytes(new Span<UInt128>(ref value)));
280280
}
281281
}
282282

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)