Technical Articles
F5 SMEs share good practice.
cancel
Showing results for 
Search instead for 
Did you mean: 
Joe_Pruitt
Cirrostratus
Cirrostratus

One of the often overlooked features of iRules is the ability to use dynamic variables in assignments.  This can make for some interesting iRules, but it can also get you into trouble at runtime when your variables are not defined in the current configuration.  This tech tip will illustrate how to take input from an HTTP request and use that as the criteria for which pool of servers the connection is sent to, all while maintaining runtime error checking using the TCL catch command. Other articles in the series: 

The "pool" command
The pool command is used to assign a connection to a specified pool name (and optionally a specific pool member).  The syntax for the pool command is:
pool <pool_name>

Specifies the pool to which you want to send the traffic.

pool <pool_name> ?member <addr> ?<port>??

Specifies a pool member to which you want to directly send the traffic.

Most typically, we see iRules written something like this (using hard-coded pool names).

when HTTP_REQUEST {
  switch -glob [HTTP::uri] {
    "*.gif" -
    "*.jpg" -
    "*.png" {
      pool images_pool
    }
    "*.asp" {
      pool ms_app_pool
    }
    "*.jsp" {
      pool java_app_pool
    }
  }
}


When this iRule is created, the pool commands are syntax checked and validated and since the pool name arguments are literals, the save logic will validate whether the given pools exist in the configuration.  If they do not exist, a save error will occur and you will have to configure the pools before you save your iRule.  In this case, no runtime exception will occur (unless you remove your pools out from under your iRule, that is).

But, let's say you want to build a more dynamic solution that can expand over time.  For this example, let's say that you want to allow your client to determine which pool of servers it is sent to.  This could be the server specifying a pool name in an HTTP cookie, or simply appending it as a GET parameter on the URI.  We'll use the later scenario for this example.

For this example, we'll define the following GET parameters that can control pool direction.

pool=name

So an example URI could be

http://somedomain.com/somefile.ext?param1=val1&pool=pool_name&pararm2=val2...


We will interrogate the URI's GET parameters in search of the "pool" parameter, extract the pool_name value and use that variable as the argument in the pool command.  For newer versions of BIG-IP, one could use the URI::query command to extract values, but this implementation should work all the way back to BIG-IP v9.0.

when HTTP_REQUEST {
  set namevals [split [HTTP::query] "&"]
  set pool_name ""
  for {set i 0} {$i < [llength $namevals]} {incr i} {
    set params [split [lindex $namevals $i] "="]
    set name [lindex $params 0]
    set val [lindex $params 1]
    switch $name {
      "pool" {
         set pool_name $val
      }
    }
  }
  if { "" ne $pool_name } {
    pool $pool_name
  }
}

What's wrong with this implementation?  Nothing if your configuration has the given value in the $pool_name variable.  What happens if that value doesn't exist in the configuration?  The answer is that you will get a runtime error and the given connection will be broken. This is not an ideal solution and the simple use of the catch command can avoid runtime connection termination and allow the request to continue on through to a default pool of servers.

The syntax for the catch command is as follows:

catch script ?varName?

The catch command may be used to prevent errors from aborting command interpretation. The catch command calls the Tcl interpreter recursively to execute script, and always returns without raising an error, regardless of any errors that might occur while executing script.

If script raises an error, catch will return a non-zero integer value corresponding to the exceptional return code returned by evaluation of script. Tcl defines the normal return code from script evaluation to be zero (0), or TCL_OK. Tcl also defines four exceptional return codes: 1 (TCL_ERROR), 2 (TCL_RETURN), 3 (TCL_BREAK), and 4 (TCL_CONTINUE). Errors during evaluation of a script are indicated by a return code of TCL_ERROR. The other exceptional return codes are returned by the return, break, and continue commands and in other special situations as documented. Tcl packages can define new commands that return other integer values as return codes as well, and scripts that make use of the return -code command can also have return codes other than the five defined by Tcl.

If the varName argument is given, then the variable it names is set to the result of the script evaluation. When the return code from the script is 1 (TCL_ERROR), the value stored in varName is an error message. When the return code from the script is 0 (TCL_OK), the value stored in resultVarName is the value returned from script.

Solution

The following iRule is the same as above, except that it makes use of exception handling in the dynamic pool assignment.

when HTTP_REQUEST {
  set namevals [split [HTTP::query] "&"]
  set pool_name ""
  for {set i 0} {$i < [llength $namevals]} {incr i} {
    set params [split [lindex $namevals $i] "="]
    set name [lindex $params 0]
    set val [lindex $params 1]
    switch $name {
      "pool" {
         set pool_name $val
      }
    }
  }
  if { "" ne $pool_name } {
    if { [catch { pool $pool_name } ] } {
      log local0. "ERROR: Attempting to assign traffic to non-existant pool $pool_name"
      pool default_pool
    }
  }
}

Now, the control of which pool the connection is directed to is completely in the hands of the request URI.  And in those rare situations where your app logic changes before the network configuration does, the connection will fallback to a default pool of servers, while logging the issue to the system log for later examination.
Conclusion
The ability to use dynamic variables greatly enhances the flexibility of what you can do with iRule commands.  The catch command should be used around any iRule command that makes use of dynamic variables to catch any runtime errors that occur.  In fact, if you so desire, you and nest catch commands for a multi-layered exception handling solution, but I'll leave that one for a later article.

Comments
Andy_Herrman_22
Nimbostratus
Nimbostratus
What's the performance like for catch? My understanding is that exception handling in most languages has some extra overhead, and it's sometimes better to avoid raising an error than trying to catch it.

 

 

Would it be more efficient to manually verify the pool name before calling the pool command, or is catch fast enough in TCL that it doesn't matter?
Joe_Pruitt
Cirrostratus
Cirrostratus
Nothing comes without a cost, but from what I understand the overhead of using a catch command is very minimal.

 

 

How would you verify the pool name outside of this method? Obviously that would be a better solution but I don't know of a method that will do it. Let me know if you do B-).

 

 

I guess it's time to run some performance tests against wrapping functioning calls with a catch command to see what the impact is. The issue in this case of raising an error will mean that the connection will be terminated. Maybe that's a good thing, maybe it's not. I'd be more inclined to issue a HTTP::respond with an error message than abruptly halting the connection. But that's left for you all to determine.

 

 

-Joe
Nicolas_Menant
F5 Employee
F5 Employee
I did a quick performance test on the catch command with a basic iRule:

 

 

when HTTP_REQUEST timing on {

 

set pool_name "titi"

 

catch [pool $pool_name]

 

}

 

 

I did some tests once with a pool that exists and another round of testing with a non existent pool.

 

 

On a BIGIP 6800, after 21000 resquests:

 

+-> HTTP_REQUEST 21825 total 0 fail 0 abort

 

| Cycles (min, avg, max) = (3965, 4088, 43949)

 

 

It represents: 0,000020506%of CPU per requests

 

 

So the impact is minimal and the add value is important. Definitely a command to use!
Joe_Pruitt
Cirrostratus
Cirrostratus
Thanks for taking the time to test out the performance impacts!

 

 

-Joe
Seattle2k
F5 Employee
F5 Employee

Just want to provide another example. I was recently troubleshooting the following error, generated by the iRule below.

bigip2 err tmm[100]: 01220001:3: TCL error: /cp/responsiblescholars.com-redirect- <HTTP_REQUEST> - ERR_VAL (line 1) invoked from within "HTTP::path"

Existing iRule:

when HTTP_REQUEST {
  if { [string tolower [HTTP::path]] starts_with "/mypath"}
  {
	HTTP::redirect "http://sub.mdomain.com/Mypath"
  } else {
	HTTP::redirect "http://sub.mydomain.com/somewhere"
  }
}

 

Wanted to know more about the incoming HTTP request, without using tcpdump. I wrapped the iRule code block with catch:

Example syntax:

catch { <script> } catch_err
if { $catch_err ne "" } {
	log local0. "Catch err: $catch_err"
	log local0. "HTTP Request: [HTTP::request]"
}

 

Resulting code:

when HTTP_REQUEST {
catch{
  if { [string tolower [HTTP::path]] starts_with "/mypath"}
  {
	HTTP::redirect "http://sub.mdomain.com/Mypath"
  } else {
	HTTP::redirect "http://sub.mydomain.com/somewhere"
  } catch_err
  if { $catch_error ne "" } {
	log local0. "Catch_err: $catch_err"
	log local0. "HTTP Request: [HTTP::request]"
	drop
  }
}

 

The next time the error was triggered, the iRule logged the following, revealing the invalid HTTP Request:

<HTTP_REQUEST>: Catch_err: ERR_VAL (line1) invoked from within "HTTP::path"

<HTTP_REQUEST>: HTTP Request: D

Seattle2k
F5 Employee
F5 Employee

I noticed my example contained a syntax error. The correct code is shown below:

when HTTP_REQUEST {
catch {
  if { [string tolower [HTTP::path]] starts_with "/mypath"} {
      HTTP::redirect "http://sub.mdomain.com/Mypath"
  } else {
	HTTP::redirect "http://sub.mydomain.com/somewhere"
  }
} catch_err
if { $catch_error ne "" } {
  log local0. "Catch_err: $catch_err"
  log local0. "Virtual Server: [virtual]"
  drop
}
}

 

joeldavism
Altostratus
Altostratus

@Seattle2k What was the error captured in the log statement?

log local0. "Catch_err: $catch_err

 Btw, I am getting many such ERR_VAL errors after upgrading F5 VE to 16.1.3.1

01220001:3: TCL error: /Common/IRule_Name <HTTP_REQUEST> - ERR_VAL (line 114)     invoked from within "HTTP::path"

Were you able to idenlty to cause for such errors?

Seattle2k
F5 Employee
F5 Employee

@joeldavism 

The error that i was troubleshooting was "bigip2 err tmm[100]: 01220001:3: TCL error: /cp/responsiblescholars.com-redirect- <HTTP_REQUEST> - ERR_VAL (line 1) invoked from within "HTTP::path""

With the 'catch' command, I was able to see the malformed/truncated HTTP request from the client: HTTP Request: D
"D" is not a valid HTTP Request Method

I think there might've been better checking/validation introduced into 16.1.3, but I've not dug in to confirm.

Version history
Last update:
‎06-Dec-2007 22:01
Updated by:
Contributors