Converting CIDR format IP blocking lists to IIS format entries

In your travels as a web developer you will invariably run into the situation where you have rogue bots from strange countries racking up your bandwidth, and basically contributing absolutely nothing to legitimate site traffic. They don't identify themselves or they masquerade as something else, and they don't obey robots.txt

This happened to us recently and we decided to start out by blocking all traffic from China and Korea. Nigeria is another candidate but lately it seems their activity has subsided. I'm sorry, I know there are good people in those countries, but that's the way it has to be.

There are some very good IP block lists available from reliable sources, but they are often in what is called CIDR format. For example, the IPv4 block 192.168.0.0/22 represents the 1024 IPv4 addresses from 192.168.0.0 to 192.168.3.255.

You will see a lot of attempts by ASP.NET developers to deny IP addresses in their web.config or with HttpModules. You don't want to do this. You want to block at the IIS level, before the request even gets to see the ASP.NET runtime.

As .NET Developers, we have the new System.Web.Administration namespace that allows us to work with the applicationHost.config file, which is the de facto main IIS "metabase" for IIS7 and above.  If you aren't familiar with this XML configuration file, it is normally found in C:\windows\System32\Inetsrv\config . Whenever IIS starts, it always reads this file.

However, I discovered that with some of these larger block lists such as Korea or China, this API is incredibly slow. I'm talking literally hours of waiting. Being an efficiency - minded sort of guy, I thought about how this situation could be rectified.  The answer was pretty simple: write a utility that would convert the block lists to  IIS format "deny" elements, and simply paste the long list of elements directly into the applicationHost.config file at the appropriate location. End of problem.

Here is what an IIS deny Element looks like:

<system.webServer>
            <security>
                <ipSecurity>
                   <add ipAddress="41.58.0.0" subnetMask="255.255.0.0" allowed="false" />
                </ipSecurity>
            </security>
</system.webServer>

You would have an <ipSecurity> node like the above for each web site that is running under your IIS. If you cannot find it, just add  an IP address in the "IP Address and Domain Restrictions" applet in IIS Manager, and you'll have no problem finding it when you edit applicationHost.config in your favorite text editor.

So what we need to do, in exact order, is:

1. Load the CIDR format IP block list.
2. Parse each line to get the IP Address and the netmask abbreviation (e.g., "/24" )
3. Look up the proper netmask in an appropriate lookup table
4. Add the entry to a StringBuilder that holds the created IIS applicationHost.config elements.
5. Save your "fixed" file for pasting into the config file.

Before I go any farther, I want to make a quick note that represents simple common sense:

Before you do ANYTHING to your applicationHost.config file, MAKE A BACKUP FIRST!

Some of these IP block lists have comment lines at the beginning and also some have comments at the end of each line, so you either need to clean up the file before processing, or write additional parsing code. Since this is something you don't do very often, I chose to simply clean up the files.

Here is the simple code that I wrote in a Windows Form project to do all this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace NetmaskFixer
{
    public partial class Form1 : Form
    {
         // declare a Dictionary (for the lookup table), a string for the results to write to file, and the filename
        private Dictionary<string, string> dict = new Dictionary<string, string>();
         private String results;
        private string fileName;
        public Form1()
        {
             InitializeComponent();
        }

         private void Form1_Load(object sender, EventArgs e)
        {
   // add the netmask equivalents to our dictionary
            dict.Add("32", "255.255.255.255");
            dict.Add("31", "255.255.255.254");
            dict.Add("30", "255.255.255.252");
            dict.Add("29", "255.255.255.248");
            dict.Add("28", "255.255.255.240");
            dict.Add("27", "255.255.255.224");
            dict.Add("26", "255.255.255.192");
            dict.Add("25", "255.255.255.128");
            dict.Add("24", "255.255.255.0");
            dict.Add("23", "255.255.254.0");
            dict.Add("22", "255.255.252.0");
            dict.Add("21", "255.255.248.0");
            dict.Add("20", "255.255.240.0");
            dict.Add("19", "255.255.224.0");
            dict.Add("18", "255.255.192.0");
            dict.Add("17", "255.255.128.0");
            dict.Add("16", "255.255.0.0");
            dict.Add("15", "255.254.0.0");
            dict.Add("14", "255.252.0.0");
            dict.Add("13", "255.248.0.0");
            dict.Add("12", "255.240.0.0");
            dict.Add("11", "255.224.0.0");
            dict.Add("10", "255.192.0.0");
            dict.Add("9", "255.128.0.0");
            dict.Add("8", "255.0.0.0");
            dict.Add("7", "254.0.0.0");
            dict.Add("6", "252.0.0.0");
            dict.Add("5", "248.0.0.0");
            dict.Add("4", "240.0.0.0");
            dict.Add("3", "224.0.0.0");
            dict.Add("2", "192.0.0.0");
            dict.Add("1", "128.0.0.0");
        }

         private void button1_Click(object sender, EventArgs e)
        {
            DialogResult res = this.openFileDialog1.ShowDialog();
    // read in the input file
            if (res == DialogResult.OK)
            {
              string[] lines=  System.IO.File.ReadAllLines(openFileDialog1.FileName);
                fileName = openFileDialog1.FileName;
              foreach (string line in lines)
              {
                  string temp = line.Trim();
  // split the line into it's two parts
                  string[] items = temp.Split("/".ToCharArray());

                  string ip = items[0];
  // lookup the correct netmask
                  string netmask = dict[items[1]];
  //add the item to our string of config elements in the format of the line below
                   //  <add ipAddress="60.191.73.186" subnetMask="255.255.255.255" allowed="false" />
                  results += "<add ipAddress=\"" + ip + "\" subnetMask=\"" + netmask + "\"" +
                             " allowed=\"false\" />\r\n";
               }
             }
    // Let the user know they can save the file.
            label1.Text = "Ready to Save.";
        }

         private void button2_Click(object sender, EventArgs e)
        {
   // save the file in the same folder as the original with "fixed" at the end of the filename
            fileName = fileName.Substring(0, fileName.IndexOf('.')) + "Fixed.txt";
            System.IO.File.WriteAllText(fileName,results);
        }
    }
}


The above code, which is well commented, should serve to be self-explanatory. And here's a pic of the cute little UI I put together:




You can download the Visual Studio 2010 solution here.

By Peter Bromberg   Popularity  (2817 Views)