HTTP throttle alternative

Problem this snippet solves:

An alternative HTTP throttle iRule

Features

2 modes of operations: strict and window (burst) mode

  • Strict mode: Strictly enforces maximum number of requests per Time Slot. For example, one time slot may allow 50 requests.
    • Window mode: Enforce number of requests per Time Window (which is a combination of multiple Time Slots). However, number of requests per Time Window is updated every Time Slot. For example, one time window, which is equals to 100 time slots, may allow 5000 requests. However, request statistics will be updated every time slot.
    • Support multiple policies.
    • Customizable unique key. This iRule use source IP as a unique key by default.
    • Statistics profile
    • Customizable action when limit is reached
    • Not CMP compatible. (This iRule relies heavily on global variable)

Code :

# Configuration steps

   * configure stats profile
   * create iRule
   * configure http virtual and apply http profile

   1) Stats profile

Create stats profile containing policy name follow by request and blocked as follow

           profile stats http_throttle {
               defaults from stats
               field1 total request
               field2 total blocked
               field3 default_policy request
               field4 default_policy blocked
               field5 request_post request
               field6 request_post blocked
           }

   2) Create iRule

Copy the following iRule to bigip.conf or configuration GUI

           when RULE_INIT {
           
               # set default policy or set to "" if no default policy is required
               # set static::default_policy ""
               set static::default_policy "default_policy"
               
               # Customize per-policy parameter here
               # mode of operation
               # 0 - strict mode: rate is strictly enforced per time slot
               # 1 - window mode: rate is enforced per time window
               array set static::Mode {
                   "default_policy"          1
                   "request_post"            1
               }
               
               # Request per time slot
               array set static::Rs {
                   "default_policy"          10
                   "request_post"            1
               }
                   
               # Time slot in millisecond
               array set static::Ts {
                   "default_policy"          10
                   "request_post"            3000
               }
                
               # Number of slot
               array set static::Slot {
                   "default_policy"          100
                   "request_post"            5
               }
               
               # Tw - Time window = Time slot x Slot
               foreach p [array name static::Mode] {
                   if {![info exists static::Ts($p)]}{ set static::Ts($p) $static::Ts("default_policy") }
                   if {![info exists static::Slot($p)]}{ set static::Slot($p) $static::Slot("default_policy") }
                   set static::Tw($p)  [expr $static::Ts($p) * $static::Slot($p)]
               }
               
               # Rw - Request per time window
               foreach p [array name static::Mode] {
                   if { ![info exists static::Rs($p)] } {  set static::Rs($p) $static::Rs("default_policy") }
                   set static::Rw($p)  [expr $static::Rs($p) * $static::Slot($p)]
               }
                   
               # clean up
               after 1000 -periodic {
                   foreach p [array name static::Mode] {
                       foreach k [array name ::rate_${p}_key] {
                           set delete 1
                           foreach t [array name ::rate_${p}_${k}] {
                               set delete 0
                               set ctime [clock clicks -milliseconds]
                               if { $ctime < 0 } {
                                   set ctime [expr 4294967296 + $ctime]
                               }
                               set ltime [expr ($ctime / $static::Ts(${p})) - $static::Slot(${p})]
                               if { $t < $ltime } {
                                   unset rate_${p}_${k}(${t})
                               }
                           }
                           foreach t [array name ::sum_${p}_${k}] {
                               set delete 0
                               set ctime [clock clicks -milliseconds]
                               if { $ctime < 0 } {
                                   set ctime [expr 4294967296 + $ctime]
                               }
                               set ltime [expr ($ctime / $static::Ts(${p})) - $static::Slot(${p})]
                               if { $t < $ltime } {
                                   unset sum_${p}_${k}(${t})
                               }
                           }
                           if { $delete } {
                               unset ::rate_${p}_key($k)
                           }
                       }
                   }
               }
           }
           when HTTP_REQUEST {
           
               # Policy assignment
               # - default policy
               set p $static::default_policy
           
               # - custom routine to assign policy
               #   (customize policy assignment here)
               if { [HTTP::method] eq "POST" } {
                       set p "request_post"
               }
           
               STATS::incr http_throttle "total request"
               
               # Policy enforcement
               if { $p eq "" } {
                   # request does not match any policy
                   # and there is no default policy configured
                   return
               }
               STATS::incr http_throttle "$p request"
           
               set block 0
           
               # current time (millisecond)
               set ctime [clock clicks -milliseconds]
               if { $ctime < 0 } {
                   set ctime [expr 4294967296 + $ctime]
               }
               set ctime [expr $ctime / $static::Ts($p)]
           
               # Customize unique key here
               set k [IP::client_addr]
           
               if { ![info exists ::rate_${p}_key(${k})] } {
                   set ::rate_${p}_key(${k}) 1
               }
                   
               set static::Mode($p) 1
           
               if { $static::Mode($p) == 0 } {
                   # strict mode
                   if { ![info exist ::rate_${p}_${k}(${ctime}) ] } {
                       set ::rate_${p}_${k}(${ctime}) 1
                   } elseif { [subst \$\{::rate_${p}_${k}(${ctime})\}] < $static::Rs(${p}) } {
                       # in strict mode, don't allow rate in each time slot to go beyond Rs
                       incr ::rate_${p}_${k}(${ctime})
                   } else {
                       set block 1
                   }
               } else {
                   # window mode
                   if { ![info exist ::sum_${p}_${k}(${ctime}) ] } {
                       # calculate sum of requests for last ($static::Slot - 1) slots
                       set ptime [expr $ctime - 1]
                       set ltime [expr $ctime - $static::Slot($p)]
                       if { [info exist ::sum_${p}_${k}(${ptime}) ] } {
                           set ::sum_${p}_${k}(${ctime}) [subst \$\{::sum_${p}_${k}(${ptime})\}]
                           if { [info exist ::rate_${p}_${k}(${ltime}) ] } {
                               incr ::sum_${p}_${k}(${ctime}) -[subst \$\{::rate_${p}_${k}(${ltime})\}]
                           }
                       } else {
                           set ::sum_${p}_${k}(${ctime}) 0
                           for { set xtime [expr $ctime - 1] } { $xtime > $ltime } { incr xtime -1 } {
                               if { [info exist ::rate_${p}_${k}(${xtime}) ] } {
                                   incr ::sum_${p}_${k}(${ctime}) [subst \$\{::rate_${p}_${k}(${xtime})\}]
                               }
                           }
                       }
                       # in window mode, allow rate to go beyond Rs but not Rw
                       if { [subst \$\{::sum_${p}_${k}(${ctime})\}] < $static::Rw(${p}) } {
                           # update statistics
                               set ::rate_${p}_${k}(${ctime}) 1
                               incr ::sum_${p}_${k}(${ctime})
                       } else {
                           set block 1
                       }
                   } else {
                       # sum of requests exists
                       if {  [subst \$\{::sum_${p}_${k}(${ctime})\}] < $static::Rw($p) } {
                           incr ::rate_${p}_${k}(${ctime})
                           incr ::sum_${p}_${k}(${ctime})
                       } else {
                           set block 1
                       }
                   }
               }
               
               # Customize throttle action here
               # default is responding with 500 status code (no content)
               if { $block } { 
                   HTTP::respond 500 
                   STATS::incr http_throttle "$p blocked"
                   STATS::incr http_throttle "total blocked"
               }
           }
           
    3) HTTP virtual server
    
        Configure http virtual and apply http profile to it. This version of iRule does not support CMP so CMP should be disabled. 
    
           virtual http {
               cmp disable
               pool http
               destination 10.10.71.200:http
               ip protocol tcp
               rules http_throttle_test
               profiles {
                  http {}
                  http_throttle {}
                  tcp {}
               }
           }

# Modifying Parameters

# 1) Default Policy    

set static::default_policy variable to desired value or set to "" if no default policy is required. For example:
set static::default_policy "default_policy"

# 2) Set mode of operation (0=strict mode, 1=window mode)

    
Add policy name follow by selected mode of operation to array static::Mode. For example:
    
array set static::Mode {
  "default_policy"          1
  "request_post"            1
}

# 3) Customize routine to assign policy - The following example use switch statement to select policy based on HTTP::method and HTTP::uri. 

Set variable p to selected policy name. 
    
if { [HTTP::method] eq "POST" } {
  set p "request_post"
}

# 4) Set request limit

Strict mode
    - In Strict mode, 2 parameters are needed to be configured: Ts and Rs.
    - Ts (or Time Slot) is a period of time (in millisecond).
    - Rs is a number of requests allowed per Time Slot interval (Ts). 
    - Ts and Rs for each policy have to be put together in array. For example, 

        # Request per time slot
        array set static::Rs {
            "default_policy"          10
            "request_post"            1
        }
        
        # Time slot in millisecond
        array set static::Ts {
            "default_policy"          10
            "request_post"            3000
        }

    - In this example, the “request_post” policy allows 1 request per every 3000 milliseconds.

Window mode    
    - In Window mode, 3 parameters are needed to be configured: Ts, Rs and Slot.
    - Ts (or Time Slot) is a period of time (in millisecond).
    - Rs is a number of requests allowed per Time Slot interval (Ts). 
    - In window mode, number of requests allowed is not enforced per Time slot but per Sliding Window
    - Slot is a number of Time Slot (Ts) to be combined as a Sliding Window. 
      Maximum requests per Sliding Window equal to Slot x Rs. 
    - Rs, Ts and Slot should be configured per policy in an array. Here is an example:

        # Request per time slot
        array set static::Rs {
            "default_policy"          10
            "request_post"            1
        }
        
        # Time slot in millisecond
        array set static::Ts {
            "default_policy"          10
            "request_post"            3000
        }
     
        # Number of slot
        array set static::Slot {
            "default_policy"          100
            "request_post"            5
        }

    - For the request_post policy 
        -- Rs=1, Ts=3,000 and Slot=5. 
        -- Sliding window = Slot x Ts = 5 x 3,000 = 15,000 milliseconds
        -- Maximum requests allowed per sliding window = Slot x Rs = 5 x 1 = 5 requests
        -- Request statistics will be updated every 3000 milliseconds.
Published Mar 18, 2015
Version 1.0

Was this article helpful?

No CommentsBe the first to comment