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: }
   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:



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