Rate Limiting Against DOS
ColdFusion, CommandBox, ContentBox, Raspberry PiSince I was feeling adventurous, I posted this blog on Reddit. I used the word "ColdFusion' in the title knowing full well the knee-jerk reaction many people have with the hopes it would spark some good discussion. That's also why I added an "About CFML" page to the site. I've been enjoying some good conversation, though I'm generally disappointed at the lopsided attention given to performance above all things.
To "prove" to me how slow CFML is, several of the Reddit readers launched a DOS attack on my Pi with load testing tools. Not very nice of them, but again, this is the Internet. I mostly just don't want my ISP to shut my project down. By the time at least 3 people were all hammering my site, the CPU had managed to get as high as I've seen it yet :) I've only owned by Pi for less than a week and have done little tuning on it so I was pleased so see the server stayed up and kept processing even though it got a little backlogged with the flood of requests. Honestly, my Wifi connection was likely to be as much of a bottleneck as well.
I quickly added a simple rate-limiter to my Application.cfc that I found on Charlie Arehart's blog and converted to script.
function limiter( count=4, duration=1) { /* Written by Charlie Arehart, [email protected], in 2009, updated 2012 - Throttles requests made more than "count" times within "duration" seconds from single IP. - sends 503 status code for bots to consider as well as text for humans to read - also logs to a new "limiter.log" that is created automatically in cf logs directory, tracking when limits are hit, to help fine tune - note that since it relies on the application scope, you need to place the call to it AFTER a cfapplication tag in application.cfm - updated 10/16/12: now adds a test around the actual throttling code, so that it applies only to requests that present no cookie, so should only impact spiders, bots, and other automated requests. A "legit" user in a regular browser will be given a cookie by CF after their first visit and so would no longer be throttled. - I also tweaked the cflog output to be more like a csv-format output */ if (!structKeyExists(application, 'rate_limiter' )) { application.rate_limiter = StructNew(); application.rate_limiter[CGI.REMOTE_ADDR] = StructNew(); application.rate_limiter[CGI.REMOTE_ADDR].attempts = 1; application.rate_limiter[CGI.REMOTE_ADDR].last_attempt = Now(); } else { if( cgi.http_cookie is "" or 1 ) { if( StructKeyExists(application.rate_limiter, CGI.REMOTE_ADDR) and DateDiff("s",application.rate_limiter[CGI.REMOTE_ADDR].last_attempt,Now()) LT arguments.duration) { if( application.rate_limiter[CGI.REMOTE_ADDR].attempts GT arguments.count) { writeOutput( '<p>You are making too many requests too fast, please slow down and wait #arguments.duration# seconds</p>' ); header statuscode="503" statustext="Service Unavailable"; header name="Retry-After" value="#arguments.duration#"; log file="limiter" text="'limiter invoked for:','#cgi.remote_addr#',#application.rate_limiter[CGI.REMOTE_ADDR].attempts#,#cgi.request_method#,'#cgi.SCRIPT_NAME#', '#cgi.QUERY_STRING#','#cgi.http_user_agent#','#application.rate_limiter[CGI.REMOTE_ADDR].last_attempt#',#listlen(cgi.http_cookie,";")#"; application.rate_limiter[CGI.REMOTE_ADDR].attempts = application.rate_limiter[CGI.REMOTE_ADDR].attempts + 1; application.rate_limiter[CGI.REMOTE_ADDR].last_attempt = Now(); abort; } else { application.rate_limiter[CGI.REMOTE_ADDR].attempts = application.rate_limiter[CGI.REMOTE_ADDR].attempts + 1; application.rate_limiter[CGI.REMOTE_ADDR].last_attempt = Now(); } } else { application.rate_limiter[CGI.REMOTE_ADDR] = StructNew(); application.rate_limiter[CGI.REMOTE_ADDR].attempts = 1; application.rate_limiter[CGI.REMOTE_ADDR].last_attempt = Now(); } } } }
Instantly, CPU usage returned to low levels once the bots were filtered out. Note, the deluge of requests were still being processed by CF, they just didn't go any farther and start hitting the DB, etc. I'd like to do some load testing, but not with a full Hibernate ORM-based blog. Especially when several others were all comparing their Pi's performance running simple single-page requests.
Here's what the live stats looked like at the time. The giant drop in CPU load is when I enabled the rate limiter.