diff --git a/IPNetworkHelper.Tests/NetworkHelperTests.cs b/IPNetworkHelper.Tests/NetworkHelperTests.cs
index cb3c9ca..7daa0ab 100644
--- a/IPNetworkHelper.Tests/NetworkHelperTests.cs
+++ b/IPNetworkHelper.Tests/NetworkHelperTests.cs
@@ -114,7 +114,7 @@ public void SplitThrowsOnUnsplittableNetworkIPv6()
[TestMethod]
- [ExpectedException(typeof(AddressFamilyMismatchException))]
+ [ExpectedException(typeof(IPNetworkNotInIPNetworkException))]
public void ExtractThrowsOnAddressFamilyMismatch()
{
var ipv4 = IPNetwork.Parse("192.168.0.0/24");
@@ -267,16 +267,46 @@ public void Network_Contains_OtherNetwork()
var network_d = IPNetwork.Parse("192.168.0.64/28"); // 192.168.0.64 - ..79
var network_e = IPNetwork.Parse("192.168.0.0/26"); // 192.168.0.0 - ..63
- Assert.IsTrue(network_a.Contains(network_e));
+ Assert.IsFalse(network_a.Contains(network_e));
Assert.IsTrue(network_e.Contains(network_a));
- Assert.IsTrue(network_b.Contains(network_e));
+ Assert.IsFalse(network_b.Contains(network_e));
Assert.IsTrue(network_e.Contains(network_b));
- Assert.IsTrue(network_c.Contains(network_e));
+ Assert.IsFalse(network_c.Contains(network_e));
Assert.IsTrue(network_e.Contains(network_c));
Assert.IsFalse(network_d.Contains(network_e));
Assert.IsFalse(network_e.Contains(network_d));
}
+
+ [TestMethod]
+ public void Network_Overlaps_OtherNetwork()
+ {
+
+ /* 0 16 32 48 64 80 128 255
+ * |----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+ * |\_A_/ \__B_/\_C_/\_D_/
+ * | |
+ * \________E__________/
+ */
+
+ var network_a = IPNetwork.Parse("192.168.0.0/28"); // 192.168.0.0 - ..15
+ var network_b = IPNetwork.Parse("192.168.0.32/28"); // 192.168.0.32 - ..47
+ var network_c = IPNetwork.Parse("192.168.0.48/28"); // 192.168.0.48 - ..63
+ var network_d = IPNetwork.Parse("192.168.0.64/28"); // 192.168.0.64 - ..79
+ var network_e = IPNetwork.Parse("192.168.0.0/26"); // 192.168.0.0 - ..63
+
+ Assert.IsTrue(network_a.Overlaps(network_e));
+ Assert.IsTrue(network_e.Overlaps(network_a));
+
+ Assert.IsTrue(network_b.Overlaps(network_e));
+ Assert.IsTrue(network_e.Overlaps(network_b));
+
+ Assert.IsTrue(network_c.Overlaps(network_e));
+ Assert.IsTrue(network_e.Overlaps(network_c));
+
+ Assert.IsFalse(network_d.Overlaps(network_e));
+ Assert.IsFalse(network_e.Overlaps(network_d));
+ }
}
diff --git a/IPNetworkHelper/NetworkHelper.cs b/IPNetworkHelper/NetworkHelper.cs
index ef0608f..e03c79c 100644
--- a/IPNetworkHelper/NetworkHelper.cs
+++ b/IPNetworkHelper/NetworkHelper.cs
@@ -10,43 +10,58 @@ namespace IPNetworkHelper;
public static class NetworkHelper
{
+ ///
+ /// Checks if this network contains the given network entirely.
+ ///
+ /// The network to check if it contains the other network entirely.
+ /// The network to be checked if it is contained in this network.
+ /// True when this network contains the other network entirely.
public static bool Contains(this IPNetwork thisNetwork, IPNetwork otherNetwork)
- => thisNetwork.Contains(otherNetwork.BaseAddress)
- || otherNetwork.Contains(thisNetwork.BaseAddress);
+ => thisNetwork.Contains(otherNetwork.GetFirstIP())
+ && thisNetwork.Contains(otherNetwork.GetLastIP());
+
+ ///
+ /// Checks if this network overlaps with the given network.
+ ///
+ /// The network to check if it overlaps with the other network.
+ /// The network to be checked if it overlaps with this network.
+ /// True when this network overlaps with the other network.
+ public static bool Overlaps(this IPNetwork thisNetwork, IPNetwork otherNetwork)
+ => thisNetwork.Contains(otherNetwork.GetFirstIP())
+ || thisNetwork.Contains(otherNetwork.GetLastIP())
+ || otherNetwork.Contains(thisNetwork.GetFirstIP())
+ || otherNetwork.Contains(thisNetwork.GetLastIP());
+
+ ///
+ /// Gets the first IP address of the given network (which is the .
+ ///
+ /// The network to get the first IP address from.
+ /// Returns the first IP address of the given network.
public static IPAddress GetFirstIP(this IPNetwork network)
- => new(CalculateFirstBytes(network.BaseAddress.GetAddressBytes(), network.PrefixLength));
-
- private static byte[] CalculateFirstBytes(byte[] prefixBytes, int prefixLength)
- {
- var result = new byte[prefixBytes.Length];
- var mask = CreateMask(prefixBytes, prefixLength);
- for (var i = 0; i < prefixBytes.Length; i++)
- {
- result[i] = (byte)(prefixBytes[i] & mask[i]);
- }
-
- return result;
- }
+ => network.BaseAddress;
+ ///
+ /// Gets the last IP address of the given network.
+ ///
+ /// The network to get the last IP address from.
+ /// Returns the last IP address of the given network.
public static IPAddress GetLastIP(this IPNetwork network)
- => new(CalculateLastBytes(network.BaseAddress.GetAddressBytes(), network.PrefixLength));
-
- internal static byte[] CalculateLastBytes(byte[] prefixBytes, int prefixLength)
{
- var result = new byte[prefixBytes.Length];
- var mask = CreateMask(prefixBytes, prefixLength);
- for (var i = 0; i < prefixBytes.Length; i++)
+ var addressbytes = network.BaseAddress.GetAddressBytes();
+ var result = new byte[addressbytes.Length];
+ var mask = CreateMask(addressbytes, network.PrefixLength);
+ for (var i = 0; i < addressbytes.Length; i++)
{
- result[i] = (byte)(prefixBytes[i] | ~mask[i]);
+ result[i] = (byte)(addressbytes[i] | ~mask[i]);
}
- return result;
+ return new(result);
}
- private static byte[] CreateMask(byte[] prefixBytes, int prefixLength)
+ private static byte[] CreateMask(byte[] addressBytes, int prefixLength)
{
- var mask = new byte[prefixBytes.Length];
+ var mask = new byte[addressBytes.Length];
var remainingbits = prefixLength;
var i = 0;
while (remainingbits >= 8)
@@ -63,27 +78,40 @@ private static byte[] CreateMask(byte[] prefixBytes, int prefixLength)
return mask;
}
+ ///
+ /// Splits the given network into two halves.
+ ///
+ /// The network to split.
+ /// Returns the left and right half of the given network.
+ /// Thrown when the network is already at its maximum prefixlength.
public static (IPNetwork left, IPNetwork right) Split(this IPNetwork network)
{
- var prefixbytes = CalculateFirstBytes(network.BaseAddress.GetAddressBytes(), network.PrefixLength);
- var maxprefix = prefixbytes.Length * 8;
+ var addressbytes = network.BaseAddress.GetAddressBytes();
+ var maxprefix = addressbytes.Length * 8;
if (network.PrefixLength >= maxprefix)
{
throw new UnableToSplitIPNetworkException(network);
}
// Left part of split is simply first half of network
- var left = new IPNetwork(new(prefixbytes), network.PrefixLength + 1);
+ var left = new IPNetwork(new(addressbytes), network.PrefixLength + 1);
// Right part of split is second half of network
// We need to set the "network MSB" for the second half
var byteindex = network.PrefixLength / 8;
var bitinbyteindex = 7 - (network.PrefixLength % 8);
- prefixbytes[byteindex] |= (byte)(1 << bitinbyteindex);
+ addressbytes[byteindex] |= (byte)(1 << bitinbyteindex);
- return (left, new IPNetwork(new(prefixbytes), network.PrefixLength + 1));
+ return (left, new IPNetwork(new(addressbytes), network.PrefixLength + 1));
}
+ ///
+ /// Takes a random subnet with the given prefix from the network and returns all subnets after taking the desired subnet, including the desired subnet.
+ ///
+ /// The network to extract the subnet from.
+ /// The prefixlength of the subnect to extract from the network.
+ /// Returns all subnets after taking the desired subnet, including the desired subnet.
+ /// Thrown for all non-IPv4/6 networks.
public static IEnumerable Extract(this IPNetwork network, int prefixLength)
=> Extract(network, network.BaseAddress.AddressFamily switch
{
@@ -92,9 +120,22 @@ public static IEnumerable Extract(this IPNetwork network, int prefixL
_ => throw new NotSupportedException($"Network addressfamily '{network.BaseAddress.AddressFamily}' not supported")
});
+ ///
+ /// Extracts the given subnet from the network and returns all subnets after taking the desired subnet, including the desired subnet.
+ ///
+ /// The network to extract the desired subnet from.
+ /// The subnet to extract from the network.
+ /// Returns all subnets after taking the desired subnet, including the desired subnet.
public static IEnumerable Extract(this IPNetwork network, IPNetwork desiredNetwork)
- => ExtractImpl(network, desiredNetwork).OrderBy(i => i, IPNetworkComparer.Default);
-
+ => Extract(network, [desiredNetwork]);
+
+ ///
+ /// Extracts the given subnets from the network and returns all subnets after taking the desired subnet, including the desired subnets.
+ ///
+ /// The network to extract the desired subnets from.
+ /// The subnets to extract from the network.
+ /// Returns all subnets after taking the desired subnet, including the desired subnets.
+ /// Thrown when any of the desired subnets is not in the network.
public static IEnumerable Extract(this IPNetwork network, IEnumerable desiredNetworks)
{
// We start with a single network
@@ -109,12 +150,11 @@ public static IEnumerable Extract(this IPNetwork network, IEnumerable
networks.Remove(target);
// Extract the network from the target and add the results to our networks list
- networks.AddRange(target.Extract(d));
+ networks.AddRange(ExtractImpl(target, d));
}
return networks.OrderBy(i => i, IPNetworkComparer.Default);
}
- private static readonly Random _rng = new();
private static IEnumerable ExtractImpl(IPNetwork network, IPNetwork desiredNetwork)
{
if (desiredNetwork.BaseAddress.AddressFamily != network.BaseAddress.AddressFamily)
@@ -137,8 +177,8 @@ private static IEnumerable ExtractImpl(IPNetwork network, IPNetwork d
{
var (left, right) = network.Split(); // Split the given network into two halves
var goleft = pickatrandom // If "pick at random"
- ? _rng.Next(0, 2) == 0 // ... use a 50/50 chance to pick a half
- : left.Contains(desiredNetwork.BaseAddress);// ... else: is the desired prefix in the left half?
+ ? Random.Shared.Next(0, 2) == 0 // ... use a 50/50 chance to pick a half
+ : left.Contains(desiredNetwork.BaseAddress);// ... else: is the desired address in the left half?
yield return goleft ? right : left; // Return half that DOESN'T contain desired network
network = goleft ? left : right; // This is the part containing our desired network
}
diff --git a/README.md b/README.md
index 9de578f..6a7feae 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
# ![logo](https://raw.githubusercontent.com/RobThree/IPNetworkHelper/master/logo_24x24.png) IPNetworkHelper
-Provides helper (extension)methods for working with (IPv4 and/or IPv6) [IPNetworks](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.httpoverrides.ipnetwork). These include parsing, splitting and extracting networks from larger networks. Available as [NuGet package](https://www.nuget.org/packages/IPNetworkHelper/).
+Provides helper (extension)methods for working with (IPv4 and/or IPv6) [IPNetworks](https://learn.microsoft.com/en-us/dotnet/api/system.net.ipnetwork). These include splitting and extracting networks from larger networks. Available as [NuGet package](https://www.nuget.org/packages/IPNetworkHelper/).
Note that since version 2.0 we use the [`System.Net.IPNetwork`](https://learn.microsoft.com/en-us/dotnet/api/system.net.ipnetwork) struct, which, unfortunately, is only available in .NET 8.0 and later. If you need support for earlier versions of .NET, use version 1.0 of this library.
+Version 3.0 has a breaking change where `Contains()` now does the more intuitive thing and checks if the network is entirely contained within another network. If you want to check if two networks overlap, use the `Overlaps()` method.
+
## Quickstart
All of the below examples use IPv4 but IPv6 works just as well.
@@ -18,14 +20,14 @@ if (IPNetwork.TryParse("192.168.0.0/16", out var othernetwork))
// ...
}
-// Get first/last IP from network
+// Get last IP from network
var first = network.GetFirstIP(); // Network (192.168.0.0)
var last = network.GetLastIP(); // Broadcast (192.168.255.255)
// Splits a network into two halves
var (left, right) = network.Split(); // Returns 192.168.0.0/17 and 192.168.128.0/17
-// Remove a subnet from a network
+// Extract a subnet from a network
var desired = IPNetwork.Parse("192.168.10.16/28");
var result = network.Extract(desired);
@@ -33,7 +35,7 @@ var result = network.Extract(desired);
// 192.168.0.0/21
// 192.168.8.0/23
// 192.168.10.0/28
-// 192.168.10.16/28
+// 192.168.10.16/28 <- desired
// 192.168.10.32/27
// 192.168.10.64/26
// 192.168.10.128/25
@@ -45,6 +47,8 @@ var result = network.Extract(desired);
// 192.168.128.0/17
```
+The `Contains(IPNetwork)` method can be used to check if a network is contained (entirely) within another network and the `Overlaps(IPNetwork)` method can be used to check if two networks overlap.
+
This library also includes an `IPAddressComparer` and `IPNetworkComparer` to be used when sorting IPAddresses or networks:
```c#