A question that comes up often when a member of the DC Team is out and about giving a talk is "How can I optimize my iRules?". Well, this series, "iRules Optimization 101" is intended to walk you through some of the ways in which you can do just that. Other articles in the series:

Just like the popular iRule Security 101 series, this series will start off with some simple tips and related examples, and then work up to more complex optimization techniques to help you keep your code lean and mean. There is an excellent document called "How To Write Fast Rules" that is located here which gives an overview of several things you can do to optimize your code. This is a great starting point and we're going to cover many of the same topics in this series, with more detail and examples, as well as branching out some from the current doc.

For this first installation we're going to start out with a comparison of "if" chains vs. "if/elseif" chains and some examples of "switch" usage. I know this is pretty basic stuff, but it's important to put the building blocks in place before moving to more advanced concepts.

Section One: if vs. if/elseif

One of the most basic logical operators that we offer is the "if" operator. This simple comparison is often used multiple times throughout a single rule and it's not rare to see strings of if comparisons together, such as:

 
when CLIENT_ACCEPTED { 
  if {[IP::client_addr] equals "192.168.5.42" } { 
    pool pool1 
  } 

  if {[IP::client_addr] equals "192.168.5.43" } { 
    pool pool2 
  } 
  
  if {[IP::client_addr] equals "192.168.5.44" } { 
    pool pool3 
  } 
... 
} 

While this is technically correct, and will function as desired, it is not by any means efficient. The logic here is checking each IP address in turn to determine which pool to send the client to. The issue is, even after a match has been made, the code is going to continue processing and checking every other IP address, even though we know we've already found the correct pool.

Instead, we implement a slightly altered logic set, using if and elseif. We can also make use of a simple "else" clause to finish this string of if/elseif checks, thereby defining a default action to perform if none of the above statements match.

 
when CLIENT_ACCEPTED { 
  if {[IP::client_addr] equals "192.168.5.42" } { 
    pool pool1 
  } elseif {[IP::client_addr] equals "192.168.5.43" } { 
    pool pool2 
  } elseif {[IP::client_addr] equals "192.168.5.44" } { 
    pool pool3 
  } else {
    pool default_pool
  }
} 

In this manner we can ensure that once we've found the matching IP address we no longer continue to process every if statement. This means we don't have to evaluate the logical statement in each if, which can save us valuable CPU time.

Section Two: switch

An even more efficient manner of dealing with multiple if statements is to get rid of them all together! No, I don't mean remove the logic from your rule, I just mean use switch instead. The switch command is more efficient, in general, than using a string of if or even if/elseif statements. It's also flexible in the matching that you can do. The above example can be simplified by making use of the switch command to look like:

 
when CLIENT_ACCEPTED { 
  switch [IP::client_addr] { 
    192.168.5.42 { pool pool1 } 
    192.168.5.43 { pool pool2 } 
    192.168.5.44 { pool pool3 } 
    default { pool default_pool }
  } 
... 
} 

As you can see this continues to simplify the code, as well as optimize it. Notice the "default" statement in the switch command replaces the functionality of the last "else" statement in the "if/elseif" chain. If none of the first logical statements match, the default action is performed. The real power of switch becomes more obvious when you start getting into some of the other things you can do with the command like glob style matching and nested comparisons. Something slightly more advanced that uses both of these might look like:

when CLIENT_ACCEPTED { 
  switch -glob [IP::client_addr] { 
    "192.168.4.24" -
    "192.168.4.25" - 
    "192.168.5.*" { 
      append myPool "pool" [getfield [IP::client_addr] "." 4]
      pool $myPool
    } 
    "192.168.6.4*" { 
      if { [active_members backupPool] > 2 } {
        pool backupPool
      }
    } 
    default { 
      log local0. "[IP::client_addr] fell through, sending to defaultPool"
      pool defaultPool
    }
  } 
} 

As you can see, there is a lot of potential with this for complex logic structures. I'll leave learning about the rest of the tricks up to you, though. ;) You can read more about switch here

Get the Flash Player to see this player.