An IP Address Blocking HttpModule for ASP.NET in 9 minutes
I'm sure this has been done before, but it was faster to write it than to google for it. There's some IP Addresses that have been bothering me and I don't have access to a firewall or IIS at my ISP, so...
I can upload a text file called blockedips.txt to my site and the changes happen immediately.
10 {
11 public class IPBlackList : IHttpModule
12 {
13 private EventHandler onBeginRequest;
14
15 public IPBlackList()
16 {
17 onBeginRequest = new EventHandler(this.HandleBeginRequest);
18 }
19
20 void IHttpModule.Dispose()
21 {
22 }
23
24 void IHttpModule.Init(HttpApplication context)
25 {
26 context.BeginRequest += onBeginRequest;
27 }
28
29 const string BLOCKEDIPSKEY = "blockedips";
30 const string BLOCKEDIPSFILE = "SiteConfig/blockedips.config";
31
32 public static StringDictionary GetBlockedIPs(HttpContext context)
33 {
34 StringDictionary ips = (StringDictionary)context.Cache[BLOCKEDIPSKEY ];
35 if (ips == null)
36 {
37 ips = GetBlockedIPs(GetBlockedIPsFilePathFromCurrentContext(context));
38 context.Cache.Insert(BLOCKEDIPSKEY , ips, new CacheDependency(GetBlockedIPsFilePathFromCurrentContext(context)));
39 }
40 return ips;
41 }
42
43 private static string BlockedIPFileName = null;
44 private static object blockedIPFileNameObject = new object();
45 public static string GetBlockedIPsFilePathFromCurrentContext(HttpContext context)
46 {
47 if (BlockedIPFileName != null)
48 return BlockedIPFileName;
49 lock(blockedIPFileNameObject)
50 {
51 if (BlockedIPFileName == null)
52 {
53 BlockedIPFileName = context.Server.MapPath(BLOCKEDIPSFILE);
54 }
55 }
56 return BlockedIPFileName;
57 }
58
59 public static StringDictionary GetBlockedIPs(string configPath)
60 {
61 StringDictionary retval = new StringDictionary();
62 using (StreamReader sr = new StreamReader(configPath))
63 {
64 String line;
65 while ((line = sr.ReadLine()) != null)
66 {
67 line = line.Trim();
68 if (line.Length != 0)
69 {
70 retval.Add(line, null);
71 }
72 }
73 }
74 return retval;
75 }
76
77 private void HandleBeginRequest( object sender, EventArgs evargs )
78 {
79 HttpApplication app = sender as HttpApplication;
80
81 if ( app != null )
82 {
83 string IPAddr = app.Context.Request.ServerVariables["REMOTE_ADDR"];
84 if (IPAddr == null || IPAddr.Length == 0)
85 {
86 return;
87 }
88
89 StringDictionary badIPs = GetBlockedIPs(app.Context);
90 if (badIPs != null && badIPs.ContainsKey(IPAddr))
91 {
92 app.Context.Response.StatusCode = 404;
93 app.Context.Response.SuppressContent = true;
94 app.Context.Response.End();
95 return;
96 }
97 }
98 }
99 }
100 }
And in your web.config:
43 <httpModules>
44 <add type="YourModuleNameHere.IPBlackList, YourAssemblyNameHere"
45 name="IPBlackList" />
46 </httpModules>
47 </system.web>
No warrenty, express or implied. If it sucks or has bugs/security holes, let me know as it's 9 minutes work.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
However, I don't think I fully understood the reason, or when it was appropriate to use one over the other, so I'm not saying your code is "wrong".
The only difference I can tell is that Response.End raises a ThreadAbortException, while CompleteRequest() just ends the request. And raising an exception is more expensive than not, so maybe the guidance was performance related?
Anyone have any input on this?
public void CompleteRequest()
{
this._requestCompleted = true;
}
and HttpResponse.End does this (notice that it calls CompleteRequest() also...
public void End()
{
if (this._context.IsInCancellablePeriod)
{
InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
else if (!this._flushing)
{
this.Flush();
this._ended = true;
this._context.ApplicationInstance.CompleteRequest();
}
}
I'll do some testing and look at the differences...thanks!
Scott
What would it take to make it reference some DNS based RBL?
Would that DNS RBL end comment spam for it's users?
2. Show me a blacklist to point to.
3. Probably not.
line.Trim();
if (line.Length != 0)
{
retval.Add(line, null);
}
Just an explanation for anyone who does not know, since I brought it up. Trim() only returns a new trimmed string, it does not operate on the variable itself. You need to assign the Trim() method's return value to a string in order to get the trimmed value. If the 'line' variable only contained four spaces, it's tested length would still be 4. To trim the string the code should be:
line = line.Trim();
Comments are closed.
As someone who keeps meaning to get more into ASP.NET and doesn't seem to ever find the time, it's interesting to see this kind of solution, along with the cache use.
Nice use of 9 minutes!