BIG-IP Upgrades & iRules
It’s commonplace in IT departments to stay on top of system patches and upgrades. There are internally developed standards as well as industry-defined processes (ITIL for example) that assist in managing the infrastructure. Is this true with product configuration as well within your organization? Each new release of product X undoubtedly introduces a different way of accomplishing the same goals. Sometimes this is due to a bug resolution, and sometimes it’s to take advantage of performance improvements. Often, the “old way” is preserved for compatibility, which shields users from unsuspected outages, but also allows configurations that are potentially sub-optimal to reside for years in the infrastructure. You should take this to heart with regards to iRules as well. Sometimes a new version necessarily “breaks” your iRule code going forward, but often it is supported. When you upgrade versions, I implore you to look at the new functionality and look at ways you might optimize and future-proof your iRules.
In this tech tip, I’m going to highlight three versions of an iRule that solves the same problem, but each subsequent version relies on improvements in TMOS. Note that version 1, whereas not at all an ideal iRule on the latest version of TMOS, will still run as is, albeit far less efficiently. All three rules are featured in the wiki:
Version 1 – Arrays and Global Variables
In the era before clustered multi-processing (pre v9.4), there was no concern with global variables. There’s always a concern with arrays, however, as a poor garbage collection logic, or, in some cases, no garbage collection logic, you can end up with out of control arrays that consume all memory and tank the box. But that’s what was available at the time.
1: when RULE_INIT {
2: set ::maxquery 100
3: set ::holdtime 600
4: array set ::usertable { }
5: array set ::blacklist { }
6: }
7:
8: when CLIENT_DATA {
9: set srcip [IP::remote_addr]
10: set currtime [clock second]
11: if { [ info exists ::blacklist($srcip) ] } {
12: if { $::holdtime > [expr ${currtime} - $::blacklist($srcip) ] } {
13: drop
14: return
15: } else {
16: unset ::blacklist($srcip)
17: }
18: }
19: if { [ info exists ::usertable(time,$srcip)] and $currtime == $::usertable(time,$srcip) } {
20: incr ::usertable(freq,$srcip)
21: if { $::usertable(freq,$srcip) > $::maxquery } {
22: set ::blacklist($srcip) $currtime
23: unset ::usertable(freq,$srcip)
24: unset ::usertable(time,$srcip)
25: drop
26: return
27: }
28: } else {
29: set ::usertable(freq,$srcip) 1
30: set ::usertable(time,$srcip) $currtime
31: }
32: pool dnsserver
33: }
The logic here is pretty straight forward. Two global variables are set, one for the maximum queries per second and the other for the amount of time that source will be prevented from a resolution response. Also in the RULE_INIT event, two arrays are built, the usertable where source IPs will be stored with their current counts, and then the blacklist where offending source IPs will be stored. The logic in the CLIENT_DATA event is best described in a workflow diagram:
Version 2 – Session Tables and Local Variables
Version two moves the variables from the global scope to local scope. This helps with CMP compatibility, but there is a performance cost due to the variables getting set on each connection instead of once when the iRule is initialized. Also in version two, arrays were scrapped in favor of the session table in hopes of CMP compatibility. Unfortunately, the session commands were not fully CMP compliant until version 10, so this approach is safer than arrays, but not as optimal on pre-v10 systems.
1: when CLIENT_ACCEPTED {
2: set maxquery 2
3: set holdtime 10
4: }
5: when CLIENT_DATA {
6: set srcip [IP::client_addr]
7: set c [clock second]
8: if {[ session lookup uie "b$c$srcip" ] != ""} {
9: UDP::drop
10: return
11: }
12: set f [session lookup uie "u$c$srcip"]
13: if { $f != "" } {
14: incr f
15: if { $f > $maxquery } {
16: for { set i 2} { $i < [expr $holdtime + 2 ]} {incr i} {
17: session add uie "b$c$srcip" b $i
18: incr c
19: }
20: UDP::drop
21: return
22: } else {
23: session add uie "u$c$srcip" $f 2
24: }
25: } else {
26: session add uie "u$c$srcip" 1 2
27: }
28: }
The logic is only slightly different in that no arrays need to be unset, the sessions will just expire.
Version 3 – Table Command and Static Variables
BIG-IP version 10 introduced the static namespace and version 10.1 introduced the table command. The static namespace allows you to create global variables that are CMP compatible. Setting them once at initialization instead of on every connection is optimal, so this is a good change. The table command is a killer expansion of the session table functionality.
1: when RULE_INIT {
2: set static::maxquery 100
3: set static::holdtime 600
4: }
5: when CLIENT_DATA {
6: set srcip [IP::remote_addr]
7: if { [table lookup -subtable "blacklist" $srcip] != "" } {
8: drop
9: return
10: }
11: set curtime [clock second]
12: set key "count:$srcip:$curtime"
13: set count [table incr $key]
14: table lifetime $key 2
15: if { $count > $static::maxquery } {
16: table add -subtable "blacklist" $srcip "blocked" indef $static::holdtime
17: table delete $key
18: drop
19: return
20: }
21: }
With each progression, notice the iRule length decreasing? Also, the readability is improved as well. Here’s the logic:
Conclusion
Surviving change is the most important step as you upgrade your infrastructure, but don’t forget to review your configurations for potential optimizations, particularly in your profiles and iRules as it relates to your BIG-IP infrastructure.
Related Articles- v10.1 - The table Command - The Basics > DevCentral > F5 ...
- v10.1 - The table Command - The Fine Print > DevCentral > F5 ...
- v10.1 - iRules rate limiting with the table command > DevCentral ...
- DevCentral Wiki: table
- vers. 10.1 "session" table limits and monitoring - DevCentral - F5 ...
- viewing session table - DevCentral - F5 DevCentral > Forums ...