Simple Connection Rate Limit

I’m having some really odd results in using the following aFlex rule. I would expect that the logic would reject inbound requests for a period of 20 seconds following 10 consecutive bad requests. After 5 (not 10) I see the blocks and once the delay time has expired I see the requests fulfilled. However without sending any further bad requests I still get blocked after about 10 sec.

I’m writing direct to the sessions table and incrementing a value. Personally the logic seems right but obviously it’s not. Can someone help?

when RULE_INIT { set ::BAD_REQUESTS 10 set ::DELAY 20 }

when HTTP_REQUEST { set IP [IP::client_addr] set bad_request_limit [table lookup rate_limit $IP] if {$bad_request_limit >= $::BAD_REQUESTS} { reject return }

}

when HTTP_RESPONSE { if {([HTTP::status] == 404) or ([HTTP::status] == 500) or ([HTTP::status] == 503)} { set counter [table lookup rate_limit $IP] switch $counter { “” { table set rate_limit $IP 1 indefinite $::DELAY } $::BAD_REQUESTS { table incr rate_limit $IP table lifetime rate_limit $IP $::DELAY } default {table incr rate_limit $IP} } return } }

Try the attached file.


#################################################
#
# Rate-limit for Failed Requests per timeframe
#  (c) A10 Networks -- MP
#   v1 20140312
#
#################################################
#
# aFleX script to rate-limit based on requests
# per second and failed responses from the server.
#
# ::MAX_FAILED holds the number of requests that can be
# done before the client is blacklisted.
#
# The ::HOLDTIME_FAILED is the time in seconds.
#
# ::RATE is the amount of requests per timeframe (in seconds)
#
# ::DEBUG can be set to 1, 2 or 3.
#
# Scalability of this aFlex is unknown.
#
# Questions & comments welcome.
#  mpeters AT a10networks DOT com
#
#################################################

when RULE_INIT {
  set ::DEBUG 0
  set ::MAX_FAILED 10
  set ::HOLDTIME_FAILED 20
  set ::RATE 2
}

when HTTP_REQUEST {
  set IP [IP::client_addr]
  if { [table lookup blacklist $IP] != "" } {
    reject
    if { $::DEBUG > 1 } { log "$IP -> blacklist expires in [table lifetime blacklist -remaining $IP] seconds" }
    return
  }
  if { [table lookup tmp_blacklist $IP] == "" } {
    table set tmp_blacklist $IP 1
    if { $::DEBUG > 2 } { log "$IP -> request counter created" }
  }
}

when HTTP_RESPONSE {
  if { ([HTTP::status] == 404) or ([HTTP::status] == 500) or ([HTTP::status] == 503) } {
    if { [table lookup tmp_failed $IP] == "" } {
      table set tmp_failed $IP $::RATE
      if { $::DEBUG > 2 } { log "$IP -> failed response counter created" }
    }
  
    set failed_count [table incr tmp_failed $IP]
    if { $::DEBUG > 2 } { log "$IP -> $failed_count of $::MAX_FAILED failed requests" }
    table lifetime tmp_failed $IP $::RATE
  
    if { $failed_count > $::MAX_FAILED } {
      table add blacklist $IP "failed response" indef $::HOLDTIME_FAILED
      if { $::DEBUG >= 1 } { log "$IP -> blacklisted for $::HOLDTIME_FAILED seconds" }
      table delete tmp_failed $IP
      if { $::DEBUG > 2 } { log "$IP -> removed from tmp_failed" }
      return
    }
  }
}

If you want to view what is inside the table you can use the attached file. Bind this to a free VIP or VPORT and use it as: http://[IP]:[PORT]/status:[table_name] For example: http://192.168.1.34:8884/status:blacklist


#################################################
#
# View / Flush contents of a table
#  (c) A10 Networks -- MP
#   v1 20140312
#
#################################################
#
# aFleX script to view contents of a table with
# the option to flush the complete table.
#
# Scalability of this aFlex is unknown.
#
# Questions & comments welcome.
#  mpeters AT a10networks DOT com
#
#################################################

when HTTP_REQUEST {
  set ACTION [getfield [HTTP::uri] ":" 1]
  set TABLE [getfield [HTTP::uri] ":" 2]

  if { $ACTION eq "/flush" } {
    table delete $TABLE -all
    HTTP::respond 200 content "Table $TABLE deleted... <a href=\"/status:$TABLE\">Back to STATUS</a>" Content-Type "text/html"
  } elseif { $ACTION eq "/status" } {
    set response "<html><head><meta http-equiv=\"refresh\" content=\"60\"><title>Contents of Table: $TABLE</title></head>"
    append response "<body><center><h1>Contents of Table: $TABLE</h1><table border=\"1\" cellpadding=\"5\" cellspacing=\"0\">"
    append response "<tr><th>Key</th><th>Value</th></tr>"
    log "TABLE: [table keys $TABLE]"
    set i 0
    foreach tr [table keys $TABLE] {
      incr i
      if { $i == 1 } {
        append response "<tr><td>$tr</td>"
      }
      if { $i == 2 } {
        append response "<td>$tr</td></tr>"
        set i 0
      }
    }
    append response "</table><p>DELETE TABLE: <a href=\"/flush:$TABLE\">$TABLE</a></p>"
    append response "</center></body></html>"
    HTTP::respond 200 content $response Content-Type "text/html"
  } else {
    HTTP::respond 200 content "Usage is prohibited!"
  }
}