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 } } }
Published Mar 17, 2015
Version 1.0

Was this article helpful?

No CommentsBe the first to comment