Forum Discussion

William_Benett1's avatar
William_Benett1
Icon for Nimbostratus rankNimbostratus
Mar 28, 2007

TCL capabilities in iRules

So i'm working on an iRule that will require me to do bitwise math in order to determine if the port number is odd or even. I believe I looked up the operator (&) but the iRuler gives me a syntax error when I attempt to save the iRule and upload it to my BIG-IP.

 

 

Can I use that operator? Is there a better method to determine if a number is odd or even (given the restrictions of iRules)?

8 Replies

  • The bitwise AND operator "&" is supported. Could you post the segment of your iRule that is causing the problem? Likely it is something else in your assigment. You might need to wrap the math in an "expr" command.

    http://tmml.sourceforge.net/doc/tcl/expr.html

    Click here

    If you could post your iRule, I'll take a look...

    This should work:

    when HTTP_REQUEST {
      set port_is_odd [expr [TCP::local_port] & 1]
      if { $port_is_odd } {
        log local0. "Port [TCP::local_port] is odd"
      } else {
        log local0. "Port [TCP::local_port] is even"
      }
    }

    -Joe
  • I haven't personally purchased any books on TCL for a reference, but relied on the online docs and TCL client distributions. Here are some links of interest.

     

     

    1. The official TCL SourceForge Documentation - Click here

     

    2. ActiveTCL - Windows TCL client - Click here

     

     

    If anyone else has recommendations on TCL primers, I'd be interested in hearing about what you find useful.

     

     

    Oh, and when reviewing the online TCL docs, keep in mind that we have disabled several of the builtin TCL commands in iRules for optimization and performance reasons. The list is available here: Click here

     

     

    -Joe
  • Okay, so sides stepping the TCL knowledge stuff, I don't think I have a method to do what I want to do with my iRule.

     
    when RULE_INIT  {
    set ::currentserver 1
    }
    when CLIENT_ACCEPTED  {
    switch -glob [TCP::local_port] {
    49* { if {[TCP::local_port] > 49151 && 49250 > [TCP::local_port]} {
    if { [expr [TCP::local_port] & 1] } {
     odd port section:
    TCP::collect
    } else {
    even port section: use src based persistence only
    if {$::currentserver eq 1} {
    if {[LB::status node 10.10.4.150] eq "down"} {
    set ::currentserver 0
    node 10.10.4.154
    persist source_addr 300
    } else {
    set ::currentserver 0
    node 10.10.4.150
    persist source_addr 300
    }
    } else {
    if {[LB::status node 10.10.4.154] eq "down"} {
    set ::currentserver 1
    node 10.10.4.150
    persist source_addr 300
    } else {
    set ::currentserver 1
    node 10.10.4.154
    persist source_addr 300
    }
    }
    }
    }
    }
    default { discard }
    }
    }
    when LB_FAILED {
    if {$::currentserver eq 1} {
    if {[LB::status node 10.10.4.150] eq "down"} {
    set ::currentserver 0
    set ::lbserver [session lookup source_addr [IP::client_addr]]
    log local0. "server $::lbserver"
    node 10.10.4.154
    TCP::release
    } else {
    set ::currentserver 0
    node 10.10.4.150
    set ::lbserver [session lookup source_addr [IP::client_addr]]
    log local0. "server $::lbserver"
    TCP::release
    }
    }
    if {$::currentserver eq 0} {
    if {[LB::status node 10.10.4.154] eq "down"} {
    set ::currentserver 1
    node 10.10.4.150
    set ::lbserver [session lookup source_addr [IP::client_addr]]
    log local0. "server $::lbserver"
    TCP::release
    } else {
    set ::currentserver 1
    node 10.10.4.154
    set ::lbserver [session lookup source_addr [IP::client_addr]]
    log local0. "server $::lbserver"
    TCP::release
    }
    }
    }

    The idea behind the irule is this: the application I'm load balancing for listens on around 100 ports. The client makes a connection on an even numbered port, then it connects on the next highest port. After that it sends some data on the 1st connection, but never on the second connection. I can't think of any good reason to design an application like this, but I digress. I have to send the 2nd connection to the same server as 1st connection. The issue is that I can't use simple source address based persistence because I might get 20 different connections (on different ports) from the same source IP address. Doing so would mean that load would be uneven, because one IP address could connect 20 times, while another connected 4 times and one server would have 20 connections and the other server 4. I should add, this iRule runs on a wildcard virtual server, so it isn't bound to a particular port.

    The way i'm trying to make this work is by detecting the 1st connection (since I know it will be on an even numbered port) and using simple source based persistence for that connection. Then i'm hoping to delay load balancing selection on the 2nd connection (which I can detect because it is always odd and +1 to the 1st connection's dest port) so that I can lookup the persistence table entry for the 1st connection and then load balance the 2nd connection to the same server.

    It seems as though I cannot perform persistence table lookups from LB_FAILED or NAME_RESOLVED. Trying to do this in LB_FAILED is something of a lark. I figured that if i could perform a dummy DNS lookup (using NAME::lookup) I could make my decision when the DNS query failed.

    There was a thread that I found on this board that indicated that I couldn't set persistence while in the CLIENT_ACCEPTED event. If that's the case, is there a good event I can try and do this in? Thanks in advance.
  • spark_86682's avatar
    spark_86682
    Historic F5 Account
    You seem to have some round-robin login in the rule too. If I understand, the heart of the matter is:

    1. Client connects to port N (where N is even)

    2. This connection should be round-robin (or otherwise) load-balanced

    3. The client (from the same IP) connects to port N+1 (which is odd)

    4. This connection must go to the same server as in step 2.

    If this is correct, then how about something like this:

    
      when RULE_INIT {
        set ::minport 49151
        set ::maxport 49250
      }
      when LB_SELECTED {
        set lport [TCP::local_port]
        if { $lport > $::minport && $lport < $::maxport } {
          if { ! [expr { $lport & 1 }] } {
            
             even number port; 1st connection
            
             Persist on remoteIP:localport.
             Also, set up another persist entry for
             the odd number port connection coming next
            
            persist add uie "[IP::remote_addr]:$lport" 300
            set lport [expr $lport + 1]
            persist add uie "[IP::remote_addr]:$lport" 300
          }
        }
      }
      when CLIENT_ACCEPTED {
        set lport [TCP::local_port]
        if { $lport > $::minport && $lport < $::maxport } {
          if { [expr { $lport & 1}] } {
            
             odd number port; 2nd connection
            
             Persist on remoteIP:localport.
             Persist entry should already be there.
            
            if { [string length [persist lookup uie "[IP::remote_addr]:$lport"]] ==
    0 } {
              log "ERROR! Connection from [IP::remote_addr]:[TCP::remote_port] to [IP::local_addr]:[TCP::local_port] had no persist record!"
              reject
            } else {
              persist uie "[IP::remote_addr]:$lport"
            }
          }
        }
      }

    Is that clear? The first connection comes in, gets load balanced, and creates two persistence entries, one of which is for the yet-unsent odd number port. When that second connection comes in, the persist entry is ready and waiting in CLIENT_ACCEPTED. Also, this way multiple connections from the same source IP but on different (sets of) ports will get load balanced separately. Plus you can just use the built-in load balancing algorithm. Easy!

    Now, the persistence entry for the even number port will get expired independently of the odd number one. So if traffic flows over the first and not the second, the second persistence entry might get deleted while the first still exists. So another connection on the odd number port will get independently load balanced. If this is a problem, then there's some Deep Magical way of linking the two, but I'm not familiar with it.
  • First, thank you. The idea of prepping the persistence for the 2nd connection while we're doing the first is badass. I believe the problem i'm really running into is that was trying to avoid creating 100 different pools (one for each possible port), however i'm going to have to give up on that idea. I believe the interpreter is correctly telling me that I cannot set persistence for this connection because I'm not telling it to actually load balance anything.

     

     

    To that end, i'm going to create pools for each of the ports I need. Then I can take advantage of your other suggestion of letting the system handle the round robin function. The persistence expiration on the 2nd connection isn't a huge concern. This thing is supposed to stay connected (sending data, TCP keepalives, etc), and if the 2nd connection drops the 1st one is disconnected by the server. Basically, i'll never have more than 1 connection on port 49152.
  • So i've been working with this and I can't seem to get information out of the persistence table. If I try and do a lookup during CLIENT_ACCEPTED it throws me a "Prerequisite operation not in progress" if I try and do it during LB_SELECTED I get nothing returned on my lookup. I can perform a lookup right after I set the persistence in LB_SELECTED, so i'm not sure why I can't do it again when the odd port connection comes in.

    
    when CLIENT_ACCEPTED  {
    switch -glob [TCP::local_port] {
    49* { if {[TCP::local_port] > 49151 && 49250 > [TCP::local_port]} {
    set lport [TCP::local_port]
    if { ! [expr {$lport & 1}]} {
    pool pool_$lport
    } else {
    set evenport [ expr $lport -1]
    set current [LB::server addr]
    set remaddr [IP::client_addr]
    set ourpool "pool_$evenport"
    set hoohaa [persist lookup source_addr [IP::client_addr]]
    log local0. "current: $current evenport: $evenport pers entry: $hoohaa remote_addr: $remaddr"
    }
    }
    }
    default { discard }
    }
    }
    when LB_SELECTED {
    if {[TCP::local_port] > 49151 && 49250 > [TCP::local_port]} {
    set lport [TCP::local_port]
    if { ! [expr {$lport &1}]} {
     even
    set remaddr [IP::client_addr] 
    persist add source_addr [IP::client_addr] 300
    set output [persist lookup source_addr [IP::client_addr] node ]
    log local0.  "pers lookup: $output remip: [IP::remote_addr]"
    } else {
     odd
    set evenport [ expr $lport - 1 ]
    set current [LB::server addr]
    set remaddr [IP::client_addr]
    set ourpool "pool_$evenport"
    log local0. "pool: $ourpool remaddr: $remaddr"
    set hoohaa [persist lookup source_addr [IP::client_addr] ]
    log local0. "current: $current  evenport: $evenport pers entry: $hoohaa remote_addr: $remaddr"
    if {[$entry != $current]} {
    LB::reselect pool pool_$lport
    }
    }
    }
    }
    
  • Heh, so I couldn't get the information out of the persistence table, as I described above. I do realize looking at this that I didn't quite follow through on Spark's idea. That said, I found a way to make it work. I initialized an array in RULE_INIT and then I store the load balancing decisions in that array, with the key being the client IP:socket pair. It works currently. If anyone is interested in this, let me know and I'll post a copy of it.
  • Ah oops? I didn't think about clearing out the array, so I'll need to add in some code for the CLIENT_CLOSED event and I should be in good shape.

     

     

    I did end up note creating pools and instead writing code to do simple round-robin load balancing. I don't think it's optimal, but the BIG-IP will probably service 50-100 connections a day. The connections only go down if there is network congestion or maintenance. This is definitely a weird application. Once I test everything i'll post it on Monday or Tuesday.