high performance rate limiting
Problem this snippet solves:
Note that this iRule is not CMP compatible and shouldn't be used on 9.4.4 or higher. On CMP capable versions (all current ones), you should use session table to track global counts in a CMP compatible manner.
This rule will limit the number of request to a particular vhost and uri to a specific rate. The calculations of hits are delayed to allow for quicker handling. Tested on LTM version 9.1.2 up to 25000 concurrent connections.
You need to add the paths you want limited to a data class: limit_uris
This will not start limiting until at least ::purge_interval has passed. It logs only when limiting starts and stops.
You could stop counting hits once you've gone over the limit to speed things up a little, but you don't have as good a record of how many there where that way.
You could also increase ::purge_interval to get a little better performance, but again you lose accuracy.
Code :
when RULE_INIT {
set ::rate 30
set ::window 300
set ::purge_interval 10
set ::last_purge [clock seconds]
array set ::hit_list ""
array unset ::hit_list
array set ::block_state ""
array unset ::block_state
array set ::hit_count ""
array unset ::hit_count
}
when HTTP_REQUEST {
if { [matchclass [string tolower [HTTP::path]] ends_with $::limit_uris] } {
# add this hit to the list
set current_time [clock seconds]
set request_id "[HTTP::host]_[expr { int(1000000 * rand()) }]"
set ::hit_list($request_id) $current_time
set window_start [expr $current_time - $::window]
if { not [info exists ::block_state([HTTP::host])] } {
set ::block_state([HTTP::host]) 0
set ::hit_count([HTTP::host]) 0
}
# count hits and purge old ones
if { [expr $current_time - $::last_purge] > $::purge_interval } {
set ::last_purge $current_time
set count 0
foreach { request_id request_time } [array get ::hit_list [HTTP::host]*] {
if { $request_time < $window_start } {
unset ::hit_list($request_id)
} else {
incr count
}
}
set ::hit_count([HTTP::host]) $count
}
# block the page
if { $::hit_count([HTTP::host]) > $::rate } {
HTTP::respond 503 content "Page Limited This page is being limited due to excessive use. Please try again later
"
#log only if first block of this page for this go-round
if { $::block_state([HTTP::host]) == 0 } {
log local0. "Started blocking [HTTP::host]"
set ::block_state([HTTP::host]) 1
}
# log that blocking has stopped
} elseif { $::block_state([HTTP::host]) == 1 } {
log local0. "Stopped blocking [HTTP::host]"
set ::block_state([HTTP::host]) 0
}
}
}Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)