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:
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.
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.
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.
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.
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
.
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.
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.
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:
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
.
public IPList()
{
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.
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:
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.
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();
}
}
}
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.
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.
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.
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.
public void AddRange(uint fromIP, uint toIP) {
if (fromIP>toIP) {
uint tempIP = fromIP;
fromIP=toIP;
toIP=tempIP;
}
if (fromIP==toIP) {
this.Add(fromIP);
} 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++;
}
uint mask =
(uint)maskList.GetKey(maskList.IndexOfValue(diffLevel));
uint minIP = fromIP & mask;
if (minIP<fromIP) minIP+=range;
if (minIP>fromIP) {
this.AddRange(fromIP,minIP-1);
fromIP=minIP;
}
if (fromIP==toIP) {
this.Add(fromIP);
} else {
if ((minIP+(range-1))<=toIP) {
this.Add(minIP,mask);
fromIP = minIP+range;
}
if (fromIP==toIP) {
this.Add(toIP);
} else {
if (fromIP<toIP) this.AddRange(fromIP,toIP);
}
}
}
}
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.