Thanks to visit codestin.com
Credit goes to github.com

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
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,57 @@ var maxSubnet = UniqueLocalAddress.CreateUlaSubnet(randomUla, UniqueLocalAddress
Console.WriteLine($"Subnet 1: {subnet1}"); // e.g., fd12:3456:
```

---
## ParseRange and TryParseRange

A C# utility for converting IP address ranges into optimal CIDR blocks, supporting both IPv4 and IPv6 addresses.
Both IPv4 and IPv6 ranges are supported
The algorithm generates the minimal set of CIDR blocks that exactly cover the specified range
Input format must be "startIP-endIP" with a single hyphen separator
Whitespace around IP addresses is automatically trimmed
Mixed IPv4/IPv6 ranges are not supported (both addresses must be the same family)

### IPv4 Example

```C#
string ipv4Range = "192.168.1.45 - 192.168.1.65";
var ipv4Blocks = IPNetwork2.ParseRange(ipv4Range);

Console.WriteLine($"CIDR blocks for {ipv4Range}:");
foreach (var block in ipv4Blocks)
{
Console.WriteLine($" {block}");
}
```

Output
```
CIDR blocks for 192.168.1.45 - 192.168.1.65:
192.168.1.45/32
192.168.1.46/31
192.168.1.48/28
192.168.1.64/31
```

### IPv6 Example

```C#
string ipv6Range = "2001:db8::1000 - 2001:db8::1fff";
var ipv6Blocks = IPNetwork2.ParseRange(ipv6Range);

Console.WriteLine($"CIDR blocks for {ipv6Range}:");
foreach (var block in ipv6Blocks)
{
Console.WriteLine($" {block}");
}
```

Ouput
````
CIDR blocks for 2001:db8::1000 - 2001:db8::1fff:
2001:db8::1000/116
````

---

## IPNetwork utility command line
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '3.3.{build}'
version: '3.4.{build}'
image: Visual Studio 2022

assembly_info:
Expand Down
24 changes: 6 additions & 18 deletions src/System.Net.IPNetwork/IPNetwork2InternalParse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,6 @@ private static bool InternalParse(bool tryParse, string network, ICidrGuess cidr

StringSplitOptions splitOptions = sanitize ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None;
string[] args = network.Split([' ', '/'], splitOptions);

if (args.Length == 0)
{
if (!tryParse)
{
throw new ArgumentNullException(nameof(network));
}

ipnetwork = null;
return false;
}

if (args.Length == 1)
{
Expand Down Expand Up @@ -151,15 +140,14 @@ private static bool InternalParse(bool tryParse, string network, ICidrGuess cidr
bool parsed3 = InternalParse(tryParse, args[0], args[1], out ipnetwork);
return parsed3;
}
else

if (!tryParse)
{
if (!tryParse)
{
throw new ArgumentNullException(nameof(network));
}
ipnetwork = null;
return false;
throw new ArgumentNullException(nameof(network));
}

ipnetwork = null;
return false;
}

/// <summary>
Expand Down
294 changes: 294 additions & 0 deletions src/System.Net.IPNetwork/IPNetwork2ParseRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
// <copyright file="IPNetwork2Parse.cs" company="IPNetwork">
// Copyright (c) IPNetwork. All rights reserved.
// </copyright>

using System.Collections.Generic;
using System.Net.Sockets;
using System.Numerics;

namespace System.Net;

/// <summary>
/// the parse methods.
/// </summary>
public partial class IPNetwork2
{
/// <summary>
/// 192.168.1.45 - 192.168.1.65
///
/// ```
/// 192.168.1.45/32 (covers: 192.168.1.45)
/// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
/// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
/// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
/// ```
///
/// </summary>
/// <param name="range">A string containing an ip range to convert (192.168.1.45 - 192.168.1.65).</param>
/// <param name="ipnetworks">An IPNetwork List equivalent to the network contained in the range</param>
/// <returns>true if parse was successful, false if the parse failed.</returns>
public static bool TryParseRange(string range, out IEnumerable<IPNetwork2> ipnetworks)
{
return InternalParseRange(true, range, out ipnetworks);
}

/// <summary>
/// 192.168.1.45 - 192.168.1.65
///
/// ```
/// 192.168.1.45/32 (covers: 192.168.1.45)
/// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
/// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
/// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
/// ```
///
/// </summary>
/// <param name="start">A string containing an ip range start (**192.168.1.45** - 192.168.1.65).</param>
/// <param name="end">A string containing an ip range end (192.168.1.45 - **192.168.1.65**).</param>
/// <param name="ipnetworks">An IPNetwork List equivalent to the network contained in the range</param>
/// <returns>true if parse was successful, false if the parse failed.</returns>
public static bool TryParseRange(string start, string end, out IEnumerable<IPNetwork2> ipnetworks)
{
return InternalParseRange(true, start, end, out ipnetworks);
}

/// <summary>
/// 192.168.1.45 - 192.168.1.65
///
/// ```
/// 192.168.1.45/32 (covers: 192.168.1.45)
/// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
/// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
/// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
/// ```
///
/// </summary>
/// <param name="range">A string containing an ip range to convert (192.168.1.45 - 192.168.1.65).</param>
/// <returns>An IPNetwork List equivalent to the network contained in the range.</returns>
public static IEnumerable<IPNetwork2> ParseRange(string range)
{
InternalParseRange(false, range, out IEnumerable<IPNetwork2> ipnetworks);
return ipnetworks;
}

/// <summary>
/// 192.168.1.45, 192.168.1.65
///
/// ```
/// 192.168.1.45/32 (covers: 192.168.1.45)
/// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
/// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
/// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
/// ```
/// </summary>
/// <param name="start">A string containing a start range ip address.</param>
/// <param name="end">A string containing a end range ip address.</param>
/// <returns>An IPNetwork List equivalent to the network contained in the range.</returns>
public static IEnumerable<IPNetwork2> ParseRange(string start, string end)
{
InternalParseRange(false, start, end, out IEnumerable<IPNetwork2> ipnetworks);
return ipnetworks;
}

/// <summary>
/// 192.168.1.45 - 192.168.1.65
///
/// ```
/// 192.168.1.45/32 (covers: 192.168.1.45)
/// 192.168.1.46/31 (covers: 192.168.1.46 - 192.168.1.47)
/// 192.168.1.48/28 (covers: 192.168.1.48 - 192.168.1.63)
/// 192.168.1.64/31 (covers: 192.168.1.64 - 192.168.1.65)
/// ```
/// </summary>
/// <param name="tryParse">Whether to throw exception or not during conversion.</param>
/// <param name="start">A string containing a start range ip address.</param>
/// <param name="end">A string containing a end range ip address.</param>
/// <param name="ipnetworks">The resulting IPNetworks.</param>
internal static bool InternalParseRange(bool tryParse, string start, string end, out IEnumerable<IPNetwork2> ipnetworks)
{
bool startParsed = IPAddress.TryParse(start, out IPAddress startIp);
if (!startParsed)
{
if (!tryParse)
{
throw new ArgumentException("Invalid start IPAddress", nameof(start));
}

ipnetworks = null;
return false;
}

bool endParsed = IPAddress.TryParse(end, out IPAddress endIp);
if (!endParsed)
{
if (!tryParse)
{
throw new ArgumentException("Invalid end IPAddress", nameof(end));
}

ipnetworks = null;
return false;
}

bool parsed = InternalParseRange(tryParse, startIp, endIp, out ipnetworks);
return parsed;
}

/// <summary>
/// Internal parse an IPNetwork2.
/// </summary>
/// <param name="tryParse">Prevent exception.</param>
/// <param name="range">The network range parse.</param>
/// <param name="ipnetworks">The resulting IPNetworks.</param>
/// <exception cref="ArgumentNullException">When network is null.</exception>
/// <exception cref="ArgumentException">When network is not valid.</exception>
/// <returns>true if parsed, otherwise false</returns>
internal static bool InternalParseRange(bool tryParse, string range, out IEnumerable<IPNetwork2> ipnetworks)
{
if (string.IsNullOrEmpty(range))
{
if (!tryParse)
{
throw new ArgumentNullException(nameof(range));
}

ipnetworks = null;
return false;
}

string[] args = range.Split([' ', '-'], StringSplitOptions.RemoveEmptyEntries);
if (args.Length == 2)
{
bool parsed3 = InternalParseRange(tryParse, args[0], args[1], out ipnetworks);
return parsed3;
}

if (!tryParse)
{
throw new ArgumentOutOfRangeException(nameof(range));
}
ipnetworks = null;
return false;
}

/// <summary>
/// 192.168.168.100 255.255.255.0
///
/// Network : 192.168.168.0
/// Netmask : 255.255.255.0
/// Cidr : 24
/// Start : 192.168.168.1
/// End : 192.168.168.254
/// Broadcast : 192.168.168.255.
/// </summary>
/// <param name="tryParse">Whether to throw exception or not during conversion.</param>
/// <param name="start">A start range ip address.</param>
/// <param name="end">An end range ip address.</param>
/// <param name="ipnetworks">The resulting IPNetworks.</param>
internal static bool InternalParseRange(bool tryParse, IPAddress start, IPAddress end, out IEnumerable<IPNetwork2> ipnetworks)
{
if (start == null)
{
if (!tryParse)
{
throw new ArgumentNullException(nameof(start));
}

ipnetworks = null;
return false;
}

if (end == null)
{
if (!tryParse)
{
throw new ArgumentNullException(nameof(end));
}
ipnetworks = null;
return false;
}

if (end.AddressFamily != start.AddressFamily)
{
if (!tryParse)
{
throw new ArgumentException(nameof(AddressFamily));
}
ipnetworks = null;
return false;
}

var result = new List<IPNetwork2>();

var startValue = ToBigInteger(start);
var endValue = ToBigInteger(end);

if (startValue > endValue)
{
throw new ArgumentException("Start IP must be less than or equal to end IP", nameof(end));
}

var addressFamily = start.AddressFamily;
byte addressBits = addressFamily == AddressFamily.InterNetworkV6 ? (byte)128 : (byte)32;

var current = startValue;
while (current <= endValue)
{
// Find the largest CIDR block that starts at current and doesn't exceed endValue
byte prefixLength = FindOptimalPrefixLength(current, endValue, addressBits);

var network = new IPNetwork2(current, addressFamily, prefixLength);
result.Add(network);

// Move to the next IP after this block
uint blockSize = (uint)(1 << (addressBits - prefixLength));
current += blockSize;
}

ipnetworks = result;
return true;
}

private static byte FindOptimalPrefixLength(BigInteger startIp, BigInteger endIp, int addressBits)
{
BigInteger remainingIps = endIp - startIp + 1;

// Find the number of trailing zeros in startIp (alignment)
int alignment = startIp.IsZero ? addressBits : CountTrailingZeros(startIp);

// Find the largest power of 2 that fits in the remaining range
int maxBlockSizeBits = remainingIps.IsZero ? 0 : GetHighestBitPosition(remainingIps);

// Take the minimum of alignment and what fits in range
int blockSizeBits = Math.Min(alignment, maxBlockSizeBits);

// Convert to prefix length
return (byte)(addressBits - blockSizeBits);
}

private static int CountTrailingZeros(BigInteger value)
{
if (value.IsZero) return 0;

int count = 0;
while ((value & BigInteger.One) == 0)
{
value >>= 1;
count++;
}
return count;
}

private static int GetHighestBitPosition(BigInteger value)
{
if (value.IsZero) return 0;

int position = 0;
while (value > 1)
{
value >>= 1;
position++;
}
return position;
}
}
Loading
Loading