F5 LTM – Rate-limiting via iRules

Within this article we look at how to rate-limit traffic via the use of an iRule.

iRule

The Table Command

So that we can rate-limit traffic the iRule command ‘table’ is used. The table command (as the name suggests) provides the ability to create, delete, and append tables, along with being able to define timeouts for each table entry.

Each table that’s created contains the following columns.

  • Key – This is a key that is assigned to the table entry and reference during table look up.
  • Value – This is a value that is assigned to the key.
  • Timeout – This is the timeout for the key.
  • Lifetime – This is the lifetime for the key. The difference between the timeout and the lifetime value is that at the point of table look up the timeout value is reset.
  • Touch Time – This value indicates when the key entry was last touched. This value is mainly used internally.
  • Create Time – This value indicates when the key entry was created. This value is mainly used internally.

Table Example

Below shows an example of the table that is created.

 Table : 8.8.8.8:/database/lookup

KEYVALUETIMEOUTLIFETIMETOUCH TIMECREATE TIME
18.8.8.8:/database/lookupindefinite 112238301121223830112
28.8.8.8:/database/lookupindefinite 112238301101223830110
38.8.8.8:/database/lookupindefinite 112238301071223830107

 

Example

Within our example we will rate-limit on a per IP, per URI basis.

Below is the iRule:

# Function : RateLimit HTTP GET requests per IP, per URI
# Created : 29/10/12
 
when RULE_INIT {
    set static::maxRate 3
    set static::windowSecs 1 
}
 
when HTTP_REQUEST {
    if { ([HTTP::method] eq "GET") and ([class match [string tolower [HTTP::uri]] starts_with DATAGROUP-RATELIMIT-URI] ) } {
 
        # whitelist
        if { [class match [IP::client_addr] equals DATAGROUP-RATELIMIT-WHITELIST] }{
           return
        }
 
        # set variables
        set limiter [string tolower [HTTP::uri]]
        set clientip_limitervar [IP::client_addr]:$limiter
        set get_count [table key -count -subtable $clientip_limitervar]
 
        # main condition
        if { $get_count < $static::maxRate } {
            incr get_count 1
             table set -subtable $clientip_limitervar $get_count $clientip_limitervar indefinite $static::windowSecs
        } else {
            log local0. "$clientip_limitervar has exceeded the number of requests allowed."
            drop
            return
        }
    }
}

How It Works

The way in which this iRule works is by creating a new table, named IP + URI and appending subsequent requests to the same table.
Each entry has a lifetime value and is removed once reached.

Finally the table is then counted to ensure that the total amount of requests for a given IP and URI has not exceeded the configured threshold.

Datagroups

Within this iRule 2 datagroups are used.

DATAGROUP-RATELIMIT-WHITELIST – (Type:Address) – Contains addresses that are not to be rate-limited.
DATAGROUP-RATELIMIT-URI – (Type:String) – Contains URIs that are to be rate-limited. If all URIs should be rate-limited then just add an / to this datagroup.

Customization

In order to customize this iRule review the following 2 variables:

et limiter [string tolower [HTTP::uri]]
set clientip_limitervar [IP::client_addr]:$limiter

Ratelimit using different Attributes

If you wanted to ratelimit on a per IP, per hostname basis then then the line would be:

set limiter [string tolower [HTTP::host]]

Ratelimit using a single Attribute

Likewise if you wanted to only rate-limit of a per IP basis then the ‘:$limiter’ would be removed from the ‘$clientip_limitvar’ variable.

Ratelimit Connections

If you want to rate limit traffic that is not HTTP based or the traffic is encrypted (SSL) then the following iRule can be used:

# Function : RateLimit connections per IP
# Created : 1/11/12
 
when RULE_INIT {
    set static::maxRate 3
    set static::windowSecs 1 
}
 
when CLIENT_ACCEPTED {
    if { [class match [IP::client_addr] equals DATAGROUP-RATELIMIT-WHITELIST] } {
           return
        } else {
            
        # set variables
        set clientip [IP::client_addr]
        set get_count [table key -count -subtable $clientip]

        # main condition
        if { $get_count < $static::maxRate } {
            incr get_count 1
            table set -subtable $clientip $get_count $clientip indefinite $static::windowSecs
        } else {
            log local0. "$clientip has exceeded the number of connections allowed."
            drop
            return
        }
    }
}

Good to Know

Memory

It is worth nothing that the F5 places NO limits on the amount of memory that can be consumed when using the table command. Because of this it is recommended that once you have implemented this iRule that the memory of the device is monitored via the use of the command “show sys mem” within TMSH.

Bugs

There are a number of bugs that can cause excessive memory usage or TMM instability when using the table/session command.
Below are the main F5 bug IDs, ref : BIG-IP cumulative hotfix version 10.2.4

ID381096 – Fixed a TMM connflow memory leak caused by iRule commands that temporarily suspend execution
ID363612 – Memory utilization for TCL string cache has been optimized. Ref : sol13889
ID374923 – Fixed a defect which could cause TMM to restart and leave a core when using the table command in an iRule on an SSL-enabled virtual server.

All of the above are fixed within HF5. However it is recommended to upgrade to the latest hotfix, which at time of writing this is HF7.

Mirroring

When session or tables are used within iRules Session DB mirroring is still performed, even if mirroring is not enabled on the virtual server.

Rick Donato

Want to become an F5 Loadbalancers expert?

Here is our hand-picked selection of the best courses you can find online:
F5 BIG-IP 101 Certification Exam – Complete Course
F5 BIG-IP 201 Certification Exam – Complete Course
and our recommended certification practice exams:
AlphaPrep Practice Tests - Free Trial