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
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)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