C# – Check if an IP range is valid

Given an IP range as a starting IP address and an ending IP address (as strings, like from user input or a config file), you can check if the IP range is valid by doing the following steps:

  • Parse and validate the IP addresses.
  • Get byte arrays for the IP addresses.
  • Convert the byte arrays to integers (uint for 32-bit IPv4, BigInteger for 128-bit IPv6).
  • Check if the starting IP integer < ending IP integer.

Here’s an example. Let’s say you’re given starting IP “192.168.0.1” and ending “192.168.0.11”. The following table shows the string, byte array, and integer representations of these IP addresses:

StringByte arrayInteger
Starting IP“192.168.0.1”[192,168,0,1]3232235521
Ending IP“192.168.0.11”[192,168,0,11]3232235531

Now compare the integers. Starting IP integer (3232235521) < Ending IP integer (3232235531), therefore the range is valid.

Here’s an example of how to implement this in the code (this handles IPv4 and IPv6):

using System.Net;
using System.Net.Sockets;
using System.Numerics;

bool IsRangeValid(string ipRangeStart, string ipRangeEnd)
{
    //1 - Validate the IP addresses
    if (!IPAddress.TryParse(ipRangeStart, out IPAddress startIP)
        ||
        !IPAddress.TryParse(ipRangeEnd, out IPAddress endIP)
        ||
        startIP.AddressFamily != endIP.AddressFamily)
    {
        return false;
    }

    //2 - Get IP address bytes in reverse order
    byte[] startIPBytes = startIP.GetAddressBytes();
    Array.Reverse(startIPBytes);
    byte[] endIPBytes = endIP.GetAddressBytes();
    Array.Reverse(endIPBytes);

    //3 - Convert to integers and compare
    if (startIP.AddressFamily == AddressFamily.InterNetwork)
    {
        //IPv4 = 32-bit unsigned integer
        return BitConverter.ToUInt32(startIPBytes, 0) < BitConverter.ToUInt32(endIPBytes, 0);
    }
    else
    {
        //IPv6 = 128-bit, so gotta use BigInteger
        //Append a 0
        Array.Resize(ref startIPBytes, startIPBytes.Length + 1);
        Array.Resize(ref endIPBytes, endIPBytes.Length + 1);

        return new BigInteger(startIPBytes) < new BigInteger(endIPBytes);
    }
    //Note: Use Array operations instead of converting to List and to Array again!

}
Code language: C# (cs)

Note: Use BitConverter and BitInteger for handling converting the byte array to an integer (instead of doing it yourself with bit math).

To show this working, here’s an example of using IsRangeValid() to check various IP ranges:

using System;
using System.Collections.Generic;

var tests = new List<(string start, string end)>()
{
    //valid ranges
    (start: "192.168.0.1", end: "192.168.0.11"),
    (start: "192.168.1.50", end: "192.168.2.10"),
    (start: "0000:0000:0000:0000:0000:0000:0000:0000", end: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"),

    //invalid ranges
    (start: "abc", end: "192.168.0.11"), //invalid ip
    (start: "192.168.0.1", end: "0000:0000:0000:0000:0000:0000:0000:0000"), //ipv4 vs ipv6
    (start: "192.168.0.11", end: "192.168.0.1"), //ipv4 end < start 
    (start: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", end:"0000:0000:0000:0000:0000:0000:0000:0000") //ipv6 end < start

};

foreach(var testCase in tests)
{
    var rangeValid = IsRangeValid(testCase.start, testCase.end);
    Console.WriteLine($"Range: {testCase.start} - {testCase.end}. Is Valid: {rangeValid}");
}
Code language: C# (cs)

Note: This is using a list of tuples to specify the IP ranges.

This outputs the following results:

Range: 192.168.0.1 - 192.168.0.11. Is Valid: True
Range: 192.168.1.50 - 192.168.2.10. Is Valid: True
Range: 0000:0000:0000:0000:0000:0000:0000:0000 - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff. Is Valid: True
Range: abc - 192.168.0.11. Is Valid: False
Range: 192.168.0.1 - 0000:0000:0000:0000:0000:0000:0000:0000. Is Valid: False
Range: 192.168.0.11 - 192.168.0.1. Is Valid: False
Range: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - 0000:0000:0000:0000:0000:0000:0000:0000. Is Valid: False
Code language: plaintext (plaintext)

Check if an IP address is contained within an IP range

Once you have a valid IP range, it’s useful to be able to check if it contains an IP address (let’s say you’re getting this from user input).

First, let’s assume you already know the IP range is valid because you already checked it and saved the valid IP range somewhere. To check if an IP address is contained in the IP range, do the following:

  • Validate the IP address.
  • Get the byte arrays for all the IP addresses (starting IP, IP you’re checking, and the ending IP).
  • Convert the byte arrays to integers.
  • Check if starting IP <= checking IP <= ending IP.

Here’s an example of how to implement this in the code (this handles IPv4 and IPv6):

using System.Net;
using System.Net.Sockets;
using System.Numerics;

bool RangeContainsIPAddress(string ipRangeStart, string ipRangeEnd, string ipAddress)
{
    //Assumption: You already know the IP range is valid
    var startIP = IPAddress.Parse(ipRangeStart);
    var endIP = IPAddress.Parse(ipRangeEnd);

    //1 - Validate the IP address
    if (!IPAddress.TryParse(ipAddress, out IPAddress ipAddressToCheck)
        ||
        startIP.AddressFamily != ipAddressToCheck.AddressFamily)
    {
        return false;
    }

    //2 - Get IP address bytes in reverse order
    byte[] startIPBytes = startIP.GetAddressBytes();
    Array.Reverse(startIPBytes);
    byte[] endIPBytes = endIP.GetAddressBytes();
    Array.Reverse(endIPBytes);
    byte[] checkIPBytes = ipAddressToCheck.GetAddressBytes();
    Array.Reverse(checkIPBytes);

    //3 - Convert to integers and compare
    if (startIP.AddressFamily == AddressFamily.InterNetwork)
    {
        var checkIPInteger = BitConverter.ToUInt32(checkIPBytes);

        //IPv4 = 32-bit unsigned integer
        return BitConverter.ToUInt32(startIPBytes, 0) <= checkIPInteger && 
            checkIPInteger <= BitConverter.ToUInt32(endIPBytes, 0);
    }
    else
    {
        //IPv6 = 128-bit, so gotta use BigInteger
        //Append a 0
        Array.Resize(ref startIPBytes, startIPBytes.Length + 1);
        Array.Resize(ref endIPBytes, endIPBytes.Length + 1);
        Array.Resize(ref checkIPBytes, checkIPBytes.Length + 1);

        var checkIPBigInteger = new BigInteger(checkIPBytes);

        return new BigInteger(startIPBytes) <= checkIPBigInteger &&
            checkIPBigInteger <= new BigInteger(endIPBytes);
    }
}
Code language: C# (cs)

Note: You may benefit from storing the IP range integers so you don’t have to recalculate them. That would make sense if you’re checking tons of IP addresses, otherwise it’s probably a bit of a micro-optimization.

Here’s an example of using this with a few IP ranges and IPs to check:

using System;
using System.Collections.Generic;

var tests = new List<(string start, string end, string check)>()
{
    //IP address is in range
    (start: "192.168.0.1", end: "192.168.0.11", check: "192.168.0.5"),
    (start: "192.168.0.1", end: "192.168.0.11", check: "192.168.0.1"),
    (start: "192.168.0.1", end: "192.168.0.11", check: "192.168.0.11"),
    (start: "192.168.0.50", end: "192.168.1.5", check: "192.168.0.100"),
    (start: "0000:0000:0000:0000:0000:0000:0000:0000", 
    end: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
    check: "0000:0000:0000:0000:0000:0000:0000:0001"),

    //IP address is not in range
    (start: "192.168.0.1", end: "192.168.0.11", check: "abc"), //it's invalid
    (start: "192.168.0.1", end: "192.168.0.11", check: "192.168.0.50"), //ip < start
    (start: "192.168.0.50", end: "192.168.1.5", check: "192.168.1.6"), //ip > end
    (start: "1111:1111:1111:1111:1111:1111:1111:1111",
    end: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
    check: "0000:0000:0000:0000:0000:0000:0000:0001"), //ip < start

};

foreach (var testCase in tests)
{
    var rangeContains = RangeContainsIPAddress(testCase.start, testCase.end, testCase.check);
    Console.WriteLine($"Does range {testCase.start} - {testCase.end} contain {testCase.check}? {rangeContains}.");
}
Code language: C# (cs)

This outputs the following:

Does range 192.168.0.1 - 192.168.0.11 contain 192.168.0.5? True.
Does range 192.168.0.1 - 192.168.0.11 contain 192.168.0.1? True.
Does range 192.168.0.1 - 192.168.0.11 contain 192.168.0.11? True.
Does range 192.168.0.50 - 192.168.1.5 contain 192.168.0.100? True.
Does range 0000:0000:0000:0000:0000:0000:0000:0000 - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff contain 0000:0000:0000:0000:0000:0000:0000:0001? True.
Does range 192.168.0.1 - 192.168.0.11 contain abc? False.
Does range 192.168.0.1 - 192.168.0.11 contain 192.168.0.50? False.
Does range 192.168.0.50 - 192.168.1.5 contain 192.168.1.6? False.
Does range 1111:1111:1111:1111:1111:1111:1111:1111 - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff contain 0000:0000:0000:0000:0000:0000:0000:0001? False.Code language: plaintext (plaintext)

Leave a Comment