Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@
<data name="net_bad_ip_network" xml:space="preserve">
<value>An invalid IP network was specified.</value>
</data>
<data name="net_bad_ip_network_invalid_baseaddress" xml:space="preserve">
<value>The specified baseAddress has non-zero bits after the network prefix.</value>
</data>
<data name="net_container_add_cookie" xml:space="preserve">
<value>An error occurred when adding a cookie to the container.</value>
</data>
Expand Down
49 changes: 27 additions & 22 deletions src/libraries/System.Net.Primitives/src/System/Net/IPNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -203,8 +195,7 @@ public static bool TryParse(ReadOnlySpan<char> 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);
Expand Down Expand Up @@ -232,8 +223,7 @@ public static bool TryParse(ReadOnlySpan<byte> 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The baseAddress is the original non-mask address. Is it intended?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fudge, good catch. It also means we don't have a good test coverage for this. I'll put up a PR asap.

Expand All @@ -247,36 +237,51 @@ public static bool TryParse(ReadOnlySpan<byte> 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<UInt128>(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<UInt128>(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<UInt128>(ref newAddress)));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -26,38 +30,64 @@ 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<string> ValidIPNetworkData = new TheoryData<string>()
{
{ "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" },
{ "42.42.128.0/17" },
{ "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<uint>(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<UInt128>(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]
Expand All @@ -77,17 +107,6 @@ public void Constructor_PrefixLenghtOutOfRange_ThrowsArgumentOutOfRangeException
Assert.Throws<ArgumentOutOfRangeException>(() => 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<ArgumentException>(() => new IPNetwork(address, prefixLength));
}

[Theory]
[MemberData(nameof(IncorrectFormatData))]
public void Parse_IncorrectFormat_ThrowsFormatException(string input)
Expand Down
Loading