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#