ASP.NET MVC ActionFilter to allow or deny IPv4 addresses with optional subnet mask. For use with AppHarbor or any other server solution which uses the X-Forwarded-For header.
// Copyright by Bo Norgaard, All rights reserved.
// original article: http://www.codeproject.com/Articles/2553/IP-list-Check-an-IP-number-against-a-list-in-C
using System;
using System.Text;
using System.Collections;
namespace AppHarbor.Web {
/// <summary>
/// Internal class for storing a range of IP numbers with the same IP mask
/// </summary>
internal class IPArrayList {
private bool isSorted = false;
private ArrayList ipNumList = new ArrayList();
private uint ipmask;
/// <summary>
/// Constructor that sets the mask for the list
/// </summary>
public IPArrayList(uint mask) {
ipmask = mask;
}
/// <summary>
/// Add a new IP numer (range) to the list
/// </summary>
public void Add(uint IPNum) {
isSorted = false;
ipNumList.Add(IPNum & ipmask);
}
/// <summary>
/// Checks if an IP number is within the ranges included by the list
/// </summary>
public bool Check(uint IPNum) {
bool found = false;
if (ipNumList.Count > 0) {
if (!isSorted) {
ipNumList.Sort();
isSorted = true;
}
IPNum = IPNum & ipmask;
if (ipNumList.BinarySearch(IPNum) >= 0) found = true;
}
return found;
}
/// <summary>
/// Clears the list
/// </summary>
public void Clear() {
ipNumList.Clear();
isSorted = false;
}
/// <summary>
/// The ToString is overriden to generate a list of the IP numbers
/// </summary>
public override string ToString() {
StringBuilder buf = new StringBuilder();
foreach (uint ipnum in ipNumList) {
if (buf.Length > 0) buf.Append("\r\n");
buf.Append(((int)ipnum & 0xFF000000) >> 24).Append('.');
buf.Append(((int)ipnum & 0x00FF0000) >> 16).Append('.');
buf.Append(((int)ipnum & 0x0000FF00) >> 8).Append('.');
buf.Append(((int)ipnum & 0x000000FF));
}
return buf.ToString();
}
/// <summary>
/// The IP mask for this list of IP numbers
/// </summary>
public uint Mask {
get {
return ipmask;
}
}
}
public class IPList {
private ArrayList ipRangeList = new ArrayList();
private SortedList maskList = new SortedList();
private ArrayList usedList = new ArrayList();
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));
}
}
// Parse a String IP address to a 32 bit unsigned integer
// We can't use System.Net.IPAddress as it will not parse
// our masks correctly eg. 255.255.0.0 is pased as 65535 !
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;
}
/// <summary>
/// Add a single IP number to the list as a string, ex. 10.1.1.1
/// </summary>
public void Add(string ipNumber) {
this.Add(parseIP(ipNumber));
}
/// <summary>
/// Add a single IP number to the list as a unsigned integer, ex. 0x0A010101
/// </summary>
public void Add(uint ip) {
((IPArrayList)ipRangeList[31]).Add(ip);
if (!usedList.Contains((int)31)) {
usedList.Add((int)31);
usedList.Sort();
}
}
/// <summary>
/// Adds IP numbers using a mask for range where the mask specifies the number of
/// fixed bits, ex. 172.16.0.0 255.255.0.0 will add 172.16.0.0 - 172.16.255.255
/// </summary>
public void Add(string ipNumber, string mask) {
this.Add(parseIP(ipNumber), parseIP(mask));
}
/// <summary>
/// Adds IP numbers using a mask for range where the mask specifies the number of
/// fixed bits, ex. 0xAC1000 0xFFFF0000 will add 172.16.0.0 - 172.16.255.255
/// </summary>
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();
}
}
}
/// <summary>
/// Adds IP numbers using a mask for range where the mask specifies the number of
/// fixed bits, ex. 192.168.1.0/24 which will add 192.168.1.0 - 192.168.1.255
/// </summary>
public void Add(string ipNumber, int maskLevel) {
this.Add(parseIP(ipNumber), (uint)maskList.GetKey(maskList.IndexOfValue(maskLevel)));
}
/// <summary>
/// Adds IP numbers using a from and to IP number. The method checks the range and
/// splits it into normal ip/mask blocks.
/// </summary>
public void AddRange(string fromIP, string toIP) {
this.AddRange(parseIP(fromIP), parseIP(toIP));
}
/// <summary>
/// Adds IP numbers using a from and to IP number. The method checks the range and
/// splits it into normal ip/mask blocks.
/// </summary>
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;
}
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);
}
}
}
}
/// <summary>
/// Checks if an IP number is contained in the lists, ex. 10.0.0.1
/// </summary>
public bool CheckNumber(string ipNumber) {
return this.CheckNumber(parseIP(ipNumber)); ;
}
/// <summary>
/// Checks if an IP number is contained in the lists, ex. 0x0A000001
/// </summary>
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;
}
/// <summary>
/// Clears all lists of IP numbers
/// </summary>
public void Clear() {
foreach (int i in usedList) {
((IPArrayList)ipRangeList[i]).Clear();
}
usedList.Clear();
}
/// <summary>
/// Generates a list of all IP ranges in printable format
/// </summary>
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();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
namespace AppHarbor.Web {
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true,
AllowMultiple = false)]
public class Firewall : ActionFilterAttribute {
/// <summary>
/// Comma separated list of allowed IPv4 addresses with optional mask separated with a semicolon.
/// Example: "127.0.0.1,192.168.0.0;255.255.0.0,10.0.0.1"
/// </summary>
public string Allow { get; set; }
public string Deny { get; set; }
IPList AllowedIPs = new IPList();
IPList DeniedIPs = new IPList();
public Firewall() { }
public Firewall(string allow, string deny) {
Allow = allow;
Deny = deny;
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (!IsAllowed(filterContext.HttpContext.Request.Headers["X-Forwarded-For"]) ||
IsDenied(filterContext.HttpContext.Request.Headers["X-Forwarded-For"]))
throw new HttpException((int)HttpStatusCode.Forbidden, "Access Denied!");
base.OnActionExecuting(filterContext);
}
bool IsAllowed(string IP) {
if (string.IsNullOrEmpty(Allow))
return true;
var ips = Allow.Split(',');
foreach (var item in ips) {
if (item.Contains(';')) {
var parts = item.Split(';');
AllowedIPs.Add(parts[0], parts[1]);
} else {
AllowedIPs.Add(item);
}
}
return AllowedIPs.CheckNumber(IP);
}
bool IsDenied(string IP) {
if (string.IsNullOrEmpty(Deny))
return false;
var ips = Deny.Split(',');
foreach (var item in ips) {
if (item.Contains(';')) {
var parts = item.Split(';');
DeniedIPs.Add(parts[0], parts[1]);
} else {
DeniedIPs.Add(item);
}
}
return DeniedIPs.CheckNumber(IP);
}
}
}