Forum Discussion

David_Remington's avatar
Feb 28, 2008

Interesting Class Behavior

So I am writing an iRule that essentially needs to parse an order list and exit processing on the first match.

 

 

However I have discovered that the order of the individual elements of the class memory structure is not identical to the order the elements are listed in the class file.

 

 

Is this the expected behavior? I've confirmed on 9.3.1 and 9.4.3.

 

 

Here is an example class:

 

class cl_snatacl {

 

type string

 

filename "/config/snatacl.class"

 

mode read

 

}

 

classfile contents:

 

"1 tcp 216.17.29.0/24 any 172.20.0.0/16 80",

 

"2 tcp 12.214.195.170/32 any any any",

 

"3 tcp any any any any",

 

 

Here is a the meat of the iRule that does the parsing:

 


rule selective_lb.v0.1-tcp {
    This irule reads a class file in the format of
    "rulenum proto srcip srcport dstip dstport",
    In the acl, 'any' may be used to match any value.
    Port support is only included for tcp and udp,
    however other protocols should list 'any' in the
    port fields.
    The rule also requires a class file listing the
    assigned IP protocol numbers.
    This rule does not support non-IP traffic.
    Connections which match the acl are load-balanced
    and snat'd using snat automap and persisted.
   when CLIENT_ACCEPTED {
       debug levels: 0 - silent, 1 - success/fail, 2 - verbose
      set debug 1
      if {$debug > 1} {log local0. "IP protocol - [IP::protocol]"}
      if { [IP::protocol] < 140 } {
         set protoname [string tolower [findclass [IP::protocol] $::cl_ipproto " "]]
         if { $debug > 1} {log local0.notice "IP protocol name - $protoname"}
      } else {
         set protoname unassigned
      }
      foreach {rule proto srcip srcport dstip dstport} [split $::cl_snatacl " "] {
         set rule [string trimleft $rule "{"]
         set dstport [string trimright $dstport "}"]
         set acl "$rule $proto $srcip:$srcport->$dstip:$dstport"
         if { $debug > 1} { log local0.notice "ACL : $rule $proto $srcip $srcport $dstip $dstport" }
            if { $proto == $protoname or $proto == "any"} {
               switch $protoname {
                  tcp {
                     set thisconn "$protoname [IP::client_addr]:[TCP::client_port]->[IP::local_addr]:[TCP::local_port]"
                     if { $debug > 1} {log local0.notice "Comparing ACL: $acl to Connection: $thisconn" }
                     if { $srcip == "any"} { set srcip [IP::client_addr] }
                     if { $srcport == "any"} { set srcport [TCP::client_port] }
                     if { $dstip == "any"} { set dstip [IP::local_addr] }
                     if { $dstport == "any"} { set dstport [TCP::local_port] }
                     if { $debug > 1} {log local0. "ACL after any substitutions: $rule $proto $srcip:$srcport->$dstip:$dstp
                     if { [IP::addr [IP::client_addr] equals $srcip] and $srcport == [TCP::client_port] and [IP::addr [IP::local_addr] equals $dstip] and $dstport == [TCP::local_port] } {
                        if { $debug > 0 } {log local0.info "Connection $thisconn matched ACL $acl."}
                        persist uie "$proto.$srcip.$dstip" 300
                        snat automap
                        pool lb_links
                        break
                     } else {
                        if { $debug > 0} {log local0.notice "Connection $thisconn did not match ACL $acl"}
                     }
                  }
                  default {
                     set thisconn "$protoname [IP::client_addr]->[IP::local_addr]"
                     if { $debug > 1} {log local0.notice "Comparing ACL: $acl to Connection: $thisconn" }
                     if { $srcip == "any"} { set srcip [IP::client_addr] }
                     if { $dstip == "any"} { set dstip [IP::local_addr] }
                     if { $debug > 1} {log local0. "ACL after any substitutions: $rule $proto $srcip->$dstip"}
                     if { [IP::addr [IP::client_addr] equals $srcip] and [IP::addr [IP::local_addr] equals $dstip] } {
                        if { $debug > 0 } {log local0.info "Connection $thisconn matched ACL $acl."}
                        persist uie "$proto.$srcip.$dstip" 300
                        snat automap
                        pool lb_links
                        break
                     } else {
                        if { $debug > 0} {log local0.notice "Connection $thisconn did not match ACL $acl"}
                     }
                  }
               }
            }
         }
      }
}

 

 

Here is the logging output of a connection attempt:

 

Feb 28 13:17:08 tmm tmm[19965]: Rule selective_lb.v0.1-tcp : Connection tcp 12.214.195.170:33618->172.20.1.111:80 did not match ACL 1 tcp 216.17.29.0/24:any->172.20.0.0/16:80

 

Feb 28 13:17:08 tmm tmm[19965]: Rule selective_lb.v0.1-tcp : Connection tcp 12.214.195.170:33618->172.20.1.111:80 matched ACL 3 tcp any:any->any:any.

 

 

I caused this behavior by putting in the three elements and doing a 'b load', then going back and editing element '2' and loading again. That apparently causes the order in memory to be 1 3 2, which is bad.

 

 

As you can see, the second element in the class should have been the one matched and element 3 should not have even been processed.

 

 

Do I need to load the class into a list ordered by rule number in RULE_INIT or is there some option on the classfile I can specify that tells the LTM to use the class in the exact order in the file?

 

 

(note this rule is not pretty atm, nor complete, for those like me who troll devcentral for code snippets.. I'm in the "make it work" phase of the project!)

 

 

Thanks!

4 Replies

  • Added info. I added a log statement to dump the output of [split $::cl_snatacl " "] and this is what it shows:

     

     

    Feb 28 14:02:10 tmm tmm[19965]: Rule selective_lb.v0.1-tcp : class contents: \{1 tcp 216.17.29.0/24 any 172.20.0.0/16 80\} \{3 tcp any any any any\} \{2 tcp 12.214.195.170/32 any any any\}

     

     

    As a side question, any way to get rid of the pesky {}'s? I'm stripping them with trimleft/trimright but there is probably a better way I'm sure.
  • Thanks for the comment. I assume that lsort is going to want my ACL numbers to be 001 002 003 .... 999, right?

     

     

    On an aside, how expensive is lsort? I have a second scenario where I need a class file in a proper order but the class file has 6000+ elements.

     

     

  • You can specify how you want the sort done, so you wouldn't necessarily need to pad the numbers:

    
    % set my_list {"1 aa" "2 bb" "10 jj"}
    "1 aa" "2 bb" "10 jj"
    % lsort $my_list
    {1 aa} {10 jj} {2 bb}
    % lsort -dictionary $my_list
    {1 aa} {2 bb} {10 jj}

    Check the lsort man page (Click here) for more details on the options.

    As for how much of a performance hit you'll see with lsort, I'm not sure. If you'll have a lot of entries and high load, I'd suggest testing with 'timing' enabled to see. I'd be curious to see what you find.

    Aaron