using System.Net.Sockets;
internal sealed class V6Address
private IPAddress ipAddress;
private const byte BitsPerByte = 8;
private const byte BitSize = 128;
private readonly ulong upper;
private readonly ulong lower;
internal V6Address(IPAddress ipAddress)
this.ipAddress = ipAddress;
byte[] ipv6Address = ipAddress.GetAddressBytes();
this.upper = ToUInt64(ipv6Address, 0);
this.lower = ToUInt64(ipv6Address, sizeof(ulong));
private V6Address(ulong upper, ulong lower)
public IPAddress IPAddress
if (this.ipAddress != null)
byte[] upperBytes = GetBigEndianBytes(this.upper);
byte[] lowerBytes = GetBigEndianBytes(this.lower);
byte[] addressBytes = new byte[2 * sizeof(ulong)];
upperBytes.CopyTo(addressBytes, 0);
lowerBytes.CopyTo(addressBytes, sizeof(ulong));
this.ipAddress = new IPAddress(addressBytes);
internal Tuple<V6Address, V6Address> GetCidrRange(byte routingBitCount)
ulong upperMask, lowerMask;
const byte HalfBitSize = BitSize / 2;
if (routingBitCount == 0)
else if (routingBitCount <= HalfBitSize)
upperMask = unchecked(~(((ulong)1 << (HalfBitSize - routingBitCount)) - 1));
lowerMask = ulong.MinValue;
upperMask = ulong.MaxValue;
lowerMask = unchecked(~(((ulong)1 << (BitSize - routingBitCount)) - 1));
ulong startUpper = this.upper & upperMask;
ulong startLower = this.lower & lowerMask;
ulong endUpper = startUpper | ~upperMask;
ulong endLower = startLower | ~lowerMask;
var startAddress = new V6Address(startUpper, startLower);
var endAddress = new V6Address(endUpper, endLower);
var result = Tuple.Create(startAddress, endAddress);
public static bool TryParse(string address, out V6Address value)
if (IPAddress.TryParse(address, out var ipAddress))
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
value = new V6Address(ipAddress);
private static ulong ToUInt64(byte[] bigEndianBytes, int startIndex)
for (int i = 0; i < sizeof(ulong); i++)
result |= (ulong)bigEndianBytes[i + startIndex] << ((sizeof(ulong) - i - 1) * BitsPerByte);
private static byte[] GetBigEndianBytes(ulong value)
byte[] result = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
internal sealed class CidrBlock
private CidrBlock(V6Address networkAddress, byte routingBitCount, V6Address start, V6Address end)
this.NetworkAddress = networkAddress;
this.RoutingBitCount = routingBitCount;
this.StartAddress = start;
public V6Address NetworkAddress { get; }
public byte RoutingBitCount { get; }
public V6Address StartAddress { get; }
public V6Address EndAddress { get; }
public static bool TryParse(string ipAddressAndBits, out CidrBlock value)
int slashIndex = ipAddressAndBits?.IndexOf('/') ?? -1;
string addressText = ipAddressAndBits.Substring(0, slashIndex);
string bitsText = ipAddressAndBits.Substring(slashIndex + 1);
const byte MaxBitCount = 128;
if (byte.TryParse(bitsText, out byte bits)
&& V6Address.TryParse(addressText, out V6Address address)
Tuple<V6Address, V6Address> range = address.GetCidrRange(bits);
value = new CidrBlock(address, bits, range.Item1, range.Item2);
public static CidrBlock Parse(string ipAddressAndBits)
if (!TryParse(ipAddressAndBits, out CidrBlock result))
throw new FormatException($"Unable to parse CIDR block: {ipAddressAndBits}");
public static void Main(string[] args)
TestBlock("2c0f:fe40:8000::/48");
TestBlock("2c0f:feb0::/43");
TestBlock("2c0f:feb0:20::/45");
private static void TestBlock(string blockText)
CidrBlock block = CidrBlock.Parse(blockText);
Console.WriteLine($"Start address: {block.StartAddress.IPAddress}");
Console.WriteLine($"End address: {block.EndAddress.IPAddress}");