Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

IP list, Check an IP number against a list in C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (19 votes)
8 Jul 2002BSD6 min read 203.3K   5.2K   61   21
A class for holding a list of IP numbers, which you can check new IP numbers against.

Introduction

Do you want to build an Internet Application that has some kind of simple access control based on IP numbers, - or do you need to validate an IP number against a list for some other purpose? If so, you found just the class for that.

Using the Class

The class should be easy to use, make an instance of IPList, Add one or more IP numbers, and begin to check IP numbers against the list, like this:

C#
IPList iplist = new IPList();
iplist.Add("10.0.0.7");
if (iplist.CheckNumber("10.0.0.7")) 
    System.Console.WriteLine("10.0.0.7 found.");

That's it … ok, you are also able to add ranges of IP numbers, but basically that's really all you have to do to use the class.

Available methods

The IPList class has the following available methods:

Adding IP numbers and Ranges

Adding a single IP number as string ("10.1.1.1") or unsigned integer (0x0A010101). The IP number is actually added with a default mask 255.255.255.255 (level /32) which means that all 32 bits are fixed so we have a 'range' of one number.

C#
void Add(string ipNumber)
void Add(uint ip)

Adding one or more IP numbers using IP masks, as ex. "10.1.1.1" , "255.255.0.0", which will add the range from 10.1.0.0 to 10.1.255.255.

C#
void Add(string ipNumber, string mask)
void Add(uint ip, uint umask)

Adding one or more IP numbers using levels, as ex. ex. 192.168.1.0/24, which will add 192.168.1.0 to 192.168.1.255. The method uses the level to find the correct IP mask and calls the above add method.

C#
void Add(string ipNumber, int maskLevel)

Adding a range of IP numbers defined by a From IP and a To IP number. The method breaks up the range into standard IP ranges and finds their masks. So the range "10.0.0.5" to "10.0.0.20" will be broken up to the following ranges and added to the list: 10.0.0.5, 10.0.0.20, 10.0.0.6/31, 10.0.0.16/30 and 10.0.0.8/29.

C#
void AddRange(string fromIP, string toIP)
void AddRange(uint fromIP, uint toIP)

Checking IP numbers

The CheckNumber method simply checks if an IP address is inside any of the IP ranges added to the class, returning true or false.

C#
bool CheckNumber(string ipNumber)
bool CheckNumber(uint ip)

General

The IPList takes no parameters in the constructor, and has overridden the ToString method to give a printable list of all the IP ranges in the list. The Clear method simply clears all IP ranges from the Class.

C#
IPList()
void Clear()
string ToString()

Class implementation

You are now able to use the IPList class. But if you are interested in how it was made, read on and I'll try to explain the internal stuff and the design of the class.

The class is actually two classes, one public available and one for internal use only.

IPArrayList

The IPList has an internal Class called IPArrayList, which holds a list of IP numbers with the same IP mask. The IPArrayList uses an ArrayList to hold the IP numbers, which provide us with both sort and binary search functionality. The class also holds a dirty flag, so the list is only sorted when someone messed up our list.

C#
internal class IPArrayList {
    private bool isSorted = false;
    private ArrayList ipNumList = new ArrayList();
    private uint ipmask;

    public IPArrayList(uint mask) ;
    public void Add(uint IPNum) ;
    public bool Check(uint IPNum);
    public void Clear();
    public override string ToString();
    public uint Mask { get { return ipmask; } }
}

The class contains two important methods, one for adding an IP number to the list, and one that uses a binary search to check for a given IP number.

The IP mask is set in the constructor and the class has some management methods, which is used to clear the list and get a list of all contained IP numbers as a string.

IPList

The IPList contains an ArrayList named ipRangeList that contains 32 IPArrayList objects - one for each of the possible IP masks.

The usedList will hold the number of the IPArrayList that we actually have entered ranges into, so we only have to check these active IPArrayLists.

The last list is a sorted list which contains all possible masks with their level number. This list is used to convert between level number and IP mask.

Here is the full interface of the IPList class:

C#
public class IPList
{   
    private ArrayList ipRangeList = new ArrayList();
    private SortedList maskList = new SortedList();
    private ArrayList usedList = new ArrayList();

    public IPList();
    private uint parseIP(string IPNumber);
    public void Add(string ipNumber);
    public void Add(uint ip);
    public void Add(string ipNumber, string mask);
    public void Add(uint ip, uint umask);
    public void Add(string ipNumber, int maskLevel);
    public void AddRange(string fromIP, string toIP);
    public void AddRange(uint fromIP, uint toIP);
    public bool CheckNumber(string ipNumber);
    public bool CheckNumber(uint ip);
    public void Clear();
    public override string ToString();
}

The constructor

The constructor generate the 32 masks, adds them to the maskList and instantiates 32 IPArrayList objects which is added to the ipRangeList.

C#
public IPList()
{
    // Initialize IP mask list and create
    // IPArrayList into the ipRangeList
    uint mask = 0x00000000;
    for (int level = 1; level<33; level++) 
    {
        mask = (mask >> 1) | 0x80000000;
        maskList.Add(mask,level);
        ipRangeList.Add(new IPArrayList(mask));
    }
}

IP string parser

The class has a simple non-validating string address to a 32 bit unsigned integer parser. We can't use the parser in System.Net.IPAddress as it contains to much validation, so it will not parse our masks correctly eg. 255.255.0.0 is passed as 65535.

To Do: It would be fine to throw some exceptions here if the IP address doesn't have four elements, three periods and all elements are integers between 0 and 255.

C#
private uint parseIP(string IPNumber) 
{
    uint res = 0;
    string[] elements = IPNumber.Split(new Char[] {'.'});
    if (elements.Length==4) {
        res  = (uint) Convert.ToInt32(elements[0])<<24;
        res += (uint) Convert.ToInt32(elements[1])<<16;
        res += (uint) Convert.ToInt32(elements[2])<<8;
        res += (uint) Convert.ToInt32(elements[3]);
    }
    return res;
}

Using the string parser

The string parser is used in all our overloaded methods to quickly convert the string to an unsigned integer and call the overloaded method that takes this as parameter.

Here is an example of one of the Add methods:

C#
public void Add(string ipNumber, string mask) 
{
    this.Add(parseIP(ipNumber),parseIP(mask));
}

Adding to the list

When adding to the list we need the mask to trim the IP number to the nearest range start number, and the mask level to add the IP number to the correct list in our ipRangeList. We also update the usedList and sort it if necessary.

The mask level is decreased because our ipRangeList has index values from 0 to 31, where our levels goes from 1 to 32.

C#
public void Add(uint ip, uint umask) {
    object Level = maskList[umask];
    if (Level!=null) {
        ip = ip & umask;
        ((IPArrayList)ipRangeList[(int)Level-1]).Add(ip);
        if (!usedList.Contains((int)Level-1)) {
            usedList.Add((int)Level-1);
            usedList.Sort();
        }
    } // else throw exception !
}

Checking the list

When searching for an IP number we ensure that we break out of the check as soon as we found a positive match. Further we only searches the lists that have active ranges, and by sorting usedList we ensure that all searches starts with the largest ranges and ends by searching for individual IP numbers.

C#
public bool CheckNumber(uint ip) {
    bool found = false;
    int i=0;
    while (!found && i<usedList.Count) {
        found = 
            ((IPArrayList)ipRangeList[(int)usedList[i]]).Check(ip);
        i++;
    }
    return found;
}

Clearing the list

To clear the lists we loop over all used lists and call the list clear method. Finally we clear the used list.

C#
public void Clear() 
{
    foreach (int i in usedList) 
    {
        ((IPArrayList)ipRangeList[i]).Clear();
    }
    usedList.Clear();
}

Getting a printable list of all contained IP ranges

The Class overrides the standard ToString to provide an easy way of getting a printable list of all contained IP numbers. This is an effective tool when debugging.

As with the clear method, the ToString iterates over all used lists and adds their list to a string builder, and returns the concatenated result to the caller.

C#
public override string ToString() {
    StringBuilder buffer = new StringBuilder();
    foreach (int i in usedList) {
        buffer.Append(
            "\r\nRange with mask of ").Append(i+1).Append("\r\n");
        buffer.Append(((IPArrayList)ipRangeList[i]).ToString());
    }
    return buffer.ToString();
}

Breaking up a random range of IP numbers into normal IP/Mask ranges

Ok, we saved the best as the last. Breaking up a range of IP numbers to standard ranges.

C#
public void AddRange(uint fromIP, uint toIP) {
    // If the order is not asending, 
    // switch the IP numbers.
    if (fromIP>toIP) {
        uint tempIP = fromIP;
        fromIP=toIP;
        toIP=tempIP;
    }
    //First we check if the range is ascending, 
    //else we switch the numbers.
    if (fromIP==toIP) {
        this.Add(fromIP);
        //If it is a single IP number, just add it.
    } else {
        uint diff = toIP-fromIP;
        int diffLevel = 1;
        uint range = 0x80000000;
        if (diff<256) {
            diffLevel = 24;
            range = 0x00000100;
        }
        while (range>diff) {
            range = range>>1;
            diffLevel++;
        }
        //Then we find the largest standard range that will fit
        // into the supplied range. As most ranges are small 
        // we make a shortcut for these.

        uint mask = 
            (uint)maskList.GetKey(maskList.IndexOfValue(diffLevel));
        uint minIP = fromIP & mask;
        if (minIP<fromIP) minIP+=range;
        //Then we find the mask for the standard range and 
        //ensures that the standard 
        //range starts within 
        //our supplied range.

        if (minIP>fromIP) {
            this.AddRange(fromIP,minIP-1);
            fromIP=minIP;
        }
        //If there are IP numbers between the start  
        //of the supplied range and the start of 
        //the standard range, add them recursively.

        if (fromIP==toIP) {
            this.Add(fromIP);

            //If the new range is of one IP number, 
            //then just add it.

        } else {
            if ((minIP+(range-1))<=toIP) {
                this.Add(minIP,mask);
                fromIP = minIP+range;

                //If the standard range fits within the 
                //supplied range, add the standard 
                //range with the correct mask.

            }
            if (fromIP==toIP) {
                this.Add(toIP);

                //If the new range is of one IP number, 
                //then just add it.

            } else {
                if (fromIP<toIP) this.AddRange(fromIP,toIP);

                //Add the rest range recursively.
            }
        }
    }
}

Source information

In the source file are two directories, ipnumbers, which contains the class described here, and ipnumberstest, which contains a simple console application that uses the class. The Visual Studio solution file in the ipnumbers directory contains both projects.

To-do information

The Class is only tested with simple data sets, and the Class lacks validation of the IP numbers and IP masks provided. This should be fixed before it is used in production environments.

The Class should be expanded with functionality, which can serialize and un-serialize all the information stored in the lists to a storage format, like XML or something similar.

Copyright

The code is copyrighted by Bo Norgaard, but using the code is free, it requires no license and no royalty charge applies.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Chief Technology Officer Neupart A/S
Denmark Denmark
I started programming in TI-Basic (extended) back in 1979, and been through Comal 80, Pascal, C, ADA, PLM, ASM (intel), OO Paccal, Delphi, C++, Perl, PHP and now C# and Java.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 1138456225-Sep-20 12:56
Member 1138456225-Sep-20 12:56 
QuestionThanks Pin
Member 1223990617-Aug-19 10:40
Member 1223990617-Aug-19 10:40 
GeneralMy vote of 5 Pin
peteSJ28-Jul-15 9:14
peteSJ28-Jul-15 9:14 
QuestionSpecify the full address range? Pin
smithkl42@gmail.com19-Jun-14 12:57
smithkl42@gmail.com19-Jun-14 12:57 
QuestionIPv6 Pin
th0x20-Jul-13 10:28
th0x20-Jul-13 10:28 
GeneralI found a piece of this work in a book. Verbatum Pin
Webon15-May-13 11:31
Webon15-May-13 11:31 
Questionnuget package Pin
Member 275157231-Jul-12 8:06
Member 275157231-Jul-12 8:06 
AnswerRe: nuget package Pin
jstring22-Feb-22 3:05
jstring22-Feb-22 3:05 
Questionany updates ?? Pin
kiquenet.com16-Jan-11 0:09
professionalkiquenet.com16-Jan-11 0:09 
Generalappreciate the class Pin
ITS_Roberts_Chris11-Jul-07 8:22
ITS_Roberts_Chris11-Jul-07 8:22 
GeneralRe: appreciate the class Pin
EdwinY28-Jul-08 19:53
EdwinY28-Jul-08 19:53 
GeneralRe: appreciate the class Pin
arandall26-Oct-10 3:46
arandall26-Oct-10 3:46 
GeneralRe: appreciate the class Pin
Patrice Boissonneault3-Aug-11 7:32
Patrice Boissonneault3-Aug-11 7:32 
GeneralUse a binarytrie instead Pin
Andreas Saurwein12-Oct-06 8:18
Andreas Saurwein12-Oct-06 8:18 
Generalreinventing the wheel here Pin
Andy Smith9-Jul-02 9:04
Andy Smith9-Jul-02 9:04 
GeneralRe: reinventing the wheel here Pin
Bo Norgaard9-Jul-02 23:48
Bo Norgaard9-Jul-02 23:48 
GeneralRe: reinventing the wheel here Pin
Nathan Blomquist10-Jul-02 1:49
Nathan Blomquist10-Jul-02 1:49 
GeneralRe: reinventing the wheel here Pin
Bo Norgaard10-Jul-02 4:04
Bo Norgaard10-Jul-02 4:04 
GeneralRe: reinventing the wheel here Pin
csinge8-Sep-05 14:08
csinge8-Sep-05 14:08 
GeneralRe: reinventing the wheel here Pin
kiquenet.com16-Jan-11 0:08
professionalkiquenet.com16-Jan-11 0:08 
GeneralRe: reinventing the wheel here Pin
WolfgangBaeck12-Aug-03 6:46
WolfgangBaeck12-Aug-03 6:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.