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 } } }