Forum Discussion
iRule for rate limiting.
Hi Eblakely,
the iRule includes a logicical flaw in its Sliding-Window mechanic, that may cause the iRule to allow more requests then expected.
Let me short explain how
$methodCount
and [table set]
are causing havoc if table entries are beginning to expire...
Request Rate = 10 Requests / Second
0,0sec | 1st request > $methodCount = 0 > creating subtable entry 1 with duration of 1 sec
0,1sec | 2nd request > $methodCount = 1 > creating subtable entry 2 with duration of 1 sec
0,2sec | 3rd request > $methodCount = 2 > creating subtable entry 3 with duration of 1 sec
0,3sec | 4th request > $methodCount = 3 > creating subtable entry 4 with duration of 1 sec
0,4sec | 5th request > $methodCount = 4 > creating subtable entry 5 with duration of 1 sec
0,5sec | 6th request > $methodCount = 5 > creating subtable entry 6 with duration of 1 sec
0,6sec | 7th request > $methodCount = 6 > creating subtable entry 7 with duration of 1 sec
0,7sec | 8th request > $methodCount = 7 > creating subtable entry 8 with duration of 1 sec
0,8sec | 9th request > $methodCount = 8 > creating subtable entry 9 with duration of 1 sec
0,9sec | 10th request > $methodCount = 9 > creating subtable entry 10 with duration of 1 sec
1,0sec > subtable entry 1 expires (created at 0,0 sec)
1,0sec | 11th request > $methodCount = 9 > overwriting subtable entry 10 with duration of 1 sec
1,1sec > subtable entry 2 expires (created at 0,1 sec)
1,1sec | 13th request > $methodCount = 8 > overwriting subtable entry 9 with duration of 1 sec
1,2sec > subtable entry 3 expires (created at 0,2 sec)
1,2sec | 14th request > $methodCount = 7 > overwriting subtable entry 8 with duration of 1 sec
1,3sec > subtable entry 4 expires (created at 0,3 sec)
1,3sec | 15th request > $methodCount = 6 > overwriting subtable entry 7 with duration of 1 sec
1,4sec > subtable entry 5 expires (created at 0,4 sec)
1,4sec | 16th request > $methodCount = 5 > overwriting subtable entry 6 with duration of 1 sec
1,5sec | 17th request > $methodCount = 5 > overwriting subtable entry 6 with duration of 1 sec
1,6sec | 18th request > $methodCount = 5 > overwriting subtable entry 6 with duration of 1 sec
1,7sec | 19th request > $methodCount = 5 > overwriting subtable entry 6 with duration of 1 sec
1,8sec | 20th request > $methodCount = 5 > overwriting subtable entry 6 with duration of 1 sec
1,9sec | 21th request > $methodCount = 5 > overwriting subtable entry 6 with duration of 1 sec
2,0sec > subtable entry 10 expires (last overwrite at 1,0 sec)
2,0sec | 22th request > $methodCount = 4 > creating subtable entry 5 with duration of 1 sec
2,1sec > subtable entry 9 expires (last overwrite at 1,1 sec)
2,1sec | 23th request > $methodCount = 4 > overwriting subtable entry 5 with duration of 1 sec
... etc ...
As you can see, your iRule will start to overwrite
[table]
records in the case that records are starting to expire and then result in slightly inaccurate enforcements.
The problem in your iRule can be easily solved by using either a unique identifier to build the Sliding-Window queue, or by using a rather simple Request-Per-Timeframe mechanic (less CPU/RAM ressources required).
Fixed Sliding-Window-Counting mechanic using
as [clock clicks]
identifier to avoid queue colisions[table]
when RULE_INIT {
set static::maxRate 120
set static::timeout 1
}
when HTTP_REQUEST {
if { [HTTP::uri] starts_with "/v3" } then {
set methodCount [table key -count -subtable [IP::client_addr]]
log local0. "[IP::client_addr]: methodCount=$methodCount"
if { $methodCount < $static::maxRate } then {
incr methodCount 1 log local0. "Adding entry for [IP::client_addr]"
table set -subtable [IP::client_addr] [clock clicks] "1" indef $static::timeout
} else {
log local0. "[IP::client_addr] exceeded max HTTP requests per second"
HTTP::respond 429 content "Request blockedExceeded requests/sec limit."
return
}
}
}
Request-Per-Timeframe Mechanic
when RULE_INIT {
set static::maxRate 120
set static::timeout 1
}
when HTTP_REQUEST {
if { [HTTP::uri] starts_with "/v3" } then {
if { [set methodCount [table incr -mustexist "Count_[IP::client_addr]"]] ne "" } then {
if { $methodCount > $static::maxRate } then {
log local0. "[IP::client_addr] exceeded max HTTP requests per second"
HTTP::respond 429 content "Request blockedExceeded requests/sec limit."
return
}
} else {
table set "Count_[IP::client_addr]" 1 indef $static::timeout
}
log local0. "[IP::client_addr]: methodCount=$methodCount"
}
}
Note: In my opinion the Sliding-Window mechanic is way to accurate and complex for a request limiter that has to deal with 120 request per seconds. I would recommend to try the Request-Per-Timeframe mechanic, since its almost as accurate when dealing with 120 RPS but its using much less ressources to maintain the counters.
Cheers, Kai
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com