Click here to Skip to main content
13,199,318 members (62,027 online)
Click here to Skip to main content
Add your own
alternative version

Stats

29.8K views
1K downloads
48 bookmarked
Posted 15 Nov 2014

IP address blocking in ASP.NET

, 15 Nov 2014
Rate this:
Please Sign up or sign in to vote.
How to restrict site access by blocking IP addresses

Introduction

Sometimes you might want to block a user, perhaps spamming your site, from accessing your site based on the user's IP address.  This article demonstrates how to do this by validating the request IP addresses in the Global.asax, Application_BeginRequest() event. The solution has support for both IPv4 and IPv6 addresses and uses caching to avoid bottlenecks caused by reading the banned IP addresses each page request.

The same is also possible to accomplish by configuring IIS, as explained in this Microsoft Knowledge Base article: HOW TO: Restrict Site Access by IP Address or Domain Name.

However, if you have your site on shared hosting you may not have access to the IIS configuration, in such scenarios my suggested solution may come handy.

Basic functionality

The IPAddressBlocker works as following. We have a file with blocked IP addresses. The IP addresses can be given as whole addresses or as masks, for example 86.234.*. This file is read when the Application_BeginRequest() event is triggered and searched for the IP address given by HttpContext.Current.Request.UserHostAddress. If some entry in the file maches the IP address, the user is transferred to a page telling him he is banned from the site. The blocked IP address file contents is cahed in order to improve performance.

The file with blocked IP addresses can hold addresses both in IPv4 and IPv6 format, mixed with each other. Here is an example of file contents:

85.154.90.243
85.234.50.*
213.100.*
2001:0db8:85a3:0000:0000:8a2e:0370:7334
2001:cdba:0000:0000:0000:0000:*

The wildcard character * means that any numbers is accepted in the comparison with the user IP address. This allows blocking of IP address ranges with a single entry.

A few words about IP addresses

An IP address (Internet Protocol address) is a numerical label assigned to each computer (device) participating in a computer network that uses the Internet Protocol for communication. An IP address serves two principal functions: host or network interface identification and location addressing. There are two different IP address formats in common use; IPv4 and IPv6.

IPv4

IPv4 or Internet Protocol version 4 is version four of the Internet Protocol (IP) . IPv4 was the first version that was widely distributed and is the version that the internet primarily based.

An IP address in IPv4 consists of 32 pieces and limiting the protocol to 4,294,967,296 unique addresses, many of which are reserved for special purposes, such as multicast and local networks. IPv6 has been developed as a successor to IPv4, mainly due to the limited space of the available IP addresses in IPv4.

An address in IPv4 consists of 32 bits and is usually written as four bytes with a period in between, so-called dot-decimal notation. For example 207.142.131.235

IPv6

The biggest difference between IPv4 and IPv6 is the length of the addresses . IPv6 addresses are 128 bits long. IPv6 addresses are usually composed of two logical parts: a 64-bit network prefix, and a 64-bit local part. The latter part is sometimes generated automatically using the network card's MAC address . 

IPv6 addresses are normally written as eight groups of four characters. The address is often accompanied by a slash, and then the length of the prefix. An example: 2001: 0db8: 85a3: 08d3: 1319: 8a2e: 0370: 7334/64 is a valid IPv6 address.

Sequences of zeros can be shortened and made more readable to humans by writing together two colons and omit the zeros in between. Leading zeros in a group of 16 bits (or 4 hexadecimal digits) can be shortened away. For example, the address 2001: fe0c: 0000: 0000: 0000: 0000: 00db: 1dc0 can be written 2001: fe0c :: db: 1dc0 . The process is described in detail in RFC 4291.

This software only supports fully expanded IPv6 addresses, and not shortened formats.

The code

Global.asax file

Fist we add the following event handler to the Global.asax file.

protected void Application_BeginRequest(Object sender, EventArgs e)
{

    BlockedIpHandler biph = new BlockedIpHandler();
    if (biph.IsIpBlocked(HttpContext.Current.Request.UserHostAddress))
    {
        Server.Transfer("~/banned.aspx");
    }

}

This event is trigged when a page is loaded from the web site. If the IP address retrieved by Request.UserHostAddress is blocked, the user is transferred to the banned.aspx page.

BlockedIpHandler.cs file

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Caching;
using System.Web.Hosting;
using System.Net;

/// <summary>
/// Handler providing functionality for blocking user access 
/// to a web site based on IP-address.
/// </summary>
public class BlockedIpHandler
{
    // Define constants
    private const string FILE_PATH = "~\\banned.txt";
    private const double CACHE_EXPIRATION = 30.0; // Seconds

    // Define member variables
    private FileContents _fileContents;

    // Constructor
    public BlockedIpHandler()
    {
        _fileContents = ReadBannedIpListFile();
    }

    public bool IsIpBlocked(string ip)
    {
        IPAddress ipAddress;

        if (IPAddress.TryParse(ip, out ipAddress)) {

            if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) {
                // IPv4 address
                string[] ipParts = ip.Split('.');

                foreach (string banned in _fileContents.Ipv4Masks) {
                    string[] blockedParts = banned.Split('.');
                    if (blockedParts.Length > 4) continue; // Not valid IP mask.

                    if (IsIpBlocked(ipParts, blockedParts)) {
                        return true;
                    }
                }
            }
            else if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) {
                // IPv6 address
                string[] ipParts = ExpandIpv6Address(ipAddress).Split(':');

                foreach (string banned in _fileContents.Ipv6Masks) {
                    string bannedIP = banned.Split('/')[0]; // Take IP address part.
                    string[] blockedParts = bannedIP.Split(':');
                    if (blockedParts.Length > 8) continue; // Not valid IP mask.

                    if (IsIpBlocked(ipParts, blockedParts)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private bool IsIpBlocked(string[] ipParts, string[] blockedIpParts)
    {
        for (int i = 0; i < blockedIpParts.Length; i++) {
            // Compare if not wildcard
            if (blockedIpParts[i] != "*") {
                // Compare IP address part
                if (ipParts[i] != blockedIpParts[i].ToLower()) {
                    return false;
                }
            }
        }

        return true;
    }

    private string ExpandIpv6Address(IPAddress ipAddress)
    {
        string expanded = "", separator = "";
        byte[] bytes = ipAddress.GetAddressBytes();

        for (int i = 0; i < bytes.Length; i += 2) {
            expanded += separator + bytes[i].ToString("x2");
            expanded += bytes[i + 1].ToString("x2");
            separator = ":";
        }

        return expanded;
    }

    private FileContents ReadBannedIpListFile()
    {

        ObjectCache cache = MemoryCache.Default;
        FileContents fileContents = cache["filecontents"] as FileContents;

        if (fileContents == null)
        {
            FileContents tempFileContents = new FileContents();

            string cachedFilePath = HostingEnvironment.MapPath(FILE_PATH);
            if (File.Exists(cachedFilePath)) 
            {
                List<string> filePaths = new List<string>();
                filePaths.Add(cachedFilePath);

                CacheItemPolicy policy = new CacheItemPolicy();
                policy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(CACHE_EXPIRATION);
                policy.ChangeMonitors.Add(new HostFileChangeMonitor(filePaths));

                List<string> tempIpv4List = new List<string>();
                List<string> tempIpv6List = new List<string>();

                // Read the file line by line.
                using (StreamReader file = new StreamReader(cachedFilePath)) {
                    string line;
                    while ((line = file.ReadLine()) != null) {
                        if (line.Contains(".")) {
                            tempIpv4List.Add(line);
                        }
                        else if (line.Contains(":")) {
                            tempIpv6List.Add(line);
                        }
                    }
                }

                tempFileContents.Ipv4Masks = tempIpv4List.ToArray();
                tempFileContents.Ipv6Masks = tempIpv6List.ToArray();

                cache.Set("filecontents", tempFileContents, policy);
            }

            fileContents = tempFileContents;
        }

        return fileContents;
    }
}

public class FileContents
{
    public string[] Ipv4Masks = new string[0];
    public string[] Ipv6Masks = new string[0];
}

The code is pretty straight forward, but here is a few notes. First we have constant declarations for the path to the file with the blocked IP addresses are and cache timeout. The IsIpBlocked() method is public and called from the event handler in the Global.asax file. The method first determines if the given address is IPv4 or IPv6 and then compare the address with the address masks in the file.

The ExpandIpv6Address() function creates an expanded version of the IPv6 address. This means all zeroes are written out in contrast to the .NET Framework IPAddress::ToString() method which returns a compressed version of the IP address.

The ReadBannedIpListFile() reads the file with the blocked IP addresses, if the file if found. If not, it returns an empty FileContents object. The contents of the file is stored in the cache, so if the file has been read within the CACHE_EXPIRATION time, the cached data will be returned. The file is at read time sorted in IPv4 and IPv6 addresses in order to make lookup faster.

The FileContents class contains the file contents returned by the ReadBannedIpListFile() function and is also stored in the cache.

That’s all I have to say about the code. You can download the code as a Website project and try it out on your own computer.

History

  • November 15, 2014: Article first published.
  • November 17, 2014: IPv6 addresses now case insensitive

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mikael Sundfors
Software Developer (Senior)
Finland Finland
Mikael Sundfors is a software developer living in Pietarsaari, Finland. He has been working with software development in .NET and in embedded Linux systems for over a decade. He is also a member of the Finnish Microsoft .NET Architect Council.

Here are some web pages he has made:
Naturplats
Mushroom World

You may also be interested in...

Comments and Discussions

 
QuestionHow HttpContext.Current.Request.UserHostAddress get ip when user behind firewall Pin
Mou_kol5-Jul-17 22:50
memberMou_kol5-Jul-17 22:50 
QuestionWhy not just use the built-in method of doing this in web.config? Pin
nmg19626-Mar-15 6:29
membernmg19626-Mar-15 6:29 
AnswerRe: Why not just use the built-in method of doing this in web.config? Pin
Mikael Sundfors26-Mar-15 10:32
memberMikael Sundfors26-Mar-15 10:32 
Questionhoneypot trap Pin
Douglas M. Weems15-Feb-15 16:27
memberDouglas M. Weems15-Feb-15 16:27 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun3-Feb-15 19:14
memberHumayun Kabir Mamun3-Feb-15 19:14 
GeneralMy Vote of 5 Pin
Arlen Navasartian16-Dec-14 6:54
memberArlen Navasartian16-Dec-14 6:54 
QuestionNice! Pin
Peter Carrasco27-Nov-14 9:24
memberPeter Carrasco27-Nov-14 9:24 
GeneralMy vote of 5 Pin
Suraj Sahoo | Coding Passion23-Nov-14 17:04
memberSuraj Sahoo | Coding Passion23-Nov-14 17:04 
GeneralSome criticism/questions Pin
Polity17-Nov-14 23:27
memberPolity17-Nov-14 23:27 
GeneralRe: Some criticism/questions Pin
Mikael Sundfors18-Nov-14 3:08
memberMikael Sundfors18-Nov-14 3:08 
AnswerIIS ip-number restriction Pin
Member 195810617-Nov-14 21:01
memberMember 195810617-Nov-14 21:01 
AnswerRe: IIS ip-number restriction Pin
Mikael Sundfors18-Nov-14 9:16
memberMikael Sundfors18-Nov-14 9:16 
QuestionWouldn't Session_Start be a better place to add the check? Pin
Anton Damhuis17-Nov-14 8:15
memberAnton Damhuis17-Nov-14 8:15 
AnswerRe: Wouldn't Session_Start be a better place to add the check? Pin
Mikael Sundfors17-Nov-14 9:05
memberMikael Sundfors17-Nov-14 9:05 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.171020.1 | Last Updated 15 Nov 2014
Article Copyright 2014 by Mikael Sundfors
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid