iRules 101 - #12 - The Session Command

One of the things that makes iRules so incredibly powerful is the fact that it is a true scripting language, or at least based on one. The fact that they give you the tools that TCL brings to the table - regular expressions, string functions, even things as simple as storing, manipulating and recalling variable data - sets iRules apart from the rest of the crowd. It also makes it possible to do some pretty impressive things with connection data and massaging/directing it the way you want it. Other articles in the series: 

Sometimes, though, a simple variable won't do. You've likely heard of global variables in one of the earlier 101 series and read the warning there, and are looking for another option. So here you are, you have some data you need to store, which needs to persist across multiple connections. You need it to be efficient and fast, and you don't want to have to do a whole lot of complex management of a data structure. One of the many ways that you can store and access information in your iRule fits all of these things perfectly, little known as it may be. For this scenario I'd recommend the usage of the session command.

There are three main permutations of the session command that you'll be using when storing and referencing data within the session table. These are:

  • session add:
    Stores user's data under the specified key for the specified persistence mode
  • session lookup:
    Returns user data previously stored using session add
  • session delete:
    Removes user data previously stored using session add

A simple example of adding some information to the session table would look like:

when CLIENTSSL_CLIENTCERT {
  set ssl_cert [SSL::cert 0]
  session add ssl $ssl_cert 90
}

By using the session add command, you can manually place a specific piece of data into the LTM's session table. You can then look it up later, by unique key, with the session lookup command and use the data in a different section of your iRule, or in another connection all together. This can be helpful in different situations where data needs to be passed between iRules or events that it might not normally be when using a simple variable. Such as mining SSL data from the connection events, as below:

when CLIENTSSL_CLIENTCERT {
  # Set results in the session so they are available to other events
  session add ssl [SSL::sessionid] [list [X509::issuer] [X509::subject] [X509::version]] 180
}

when HTTP_REQUEST {
  # Retrieve certificate information from the session
  set sslList [session lookup ssl [SSL::sessionid]]
  set issuer [lindex sslList 0]
  set subject [lindex sslList 1]
  set version [lindex sslList 2]
}

Because the session table is optimized and designed to handle every connection that comes into the LTM, it's very efficient and can handle quite a large number of items. Also note that, as above, you can pass structured information such as TCL Lists into the session table and they will remain intact. Keep in mind, though, that there is currently no way to count the number of entries in the table with a certain key, so you'll have to build all of your own processing logic for now, where necessary.

It's also important to note that there is more than one session table. If you look at the above example, you'll see that before we listed any key or data to be stored, we used the command session add ssl. Note the "ssl" portion of this command. This is a reference to which session table the data will be stored in. For our purposes here there are effectively two session tables: ssl, and uie. Be sure you're accessing the same one in your session lookup section as you are in your session add section, or you'll never find the data you're after.

This is pretty easy to keep straight, once you see it. It looks like:

session add uie    ... session lookup uie  
Or:
session add ssl    ... session lookup ssl  

You can find complete documentation on the session command here, in the iRules, as well as some great examples that depict some more advanced iRules making use of the session command to great success. Check out Codeshare for more examples.

Updated Oct 14, 2024
Version 2.0
  • Colin, can you speak to the session table cleanup process? For example, when a server fails, are these entries cleaned up immediately, when the next request that references that entry is received, or not until the timeout expires? Thanks --Jason
  • Colin, the wiki entry mentions several different tables (or "modes" (simple | source_addr | sticky | dest_addr | ssl | uie | hash | sip)

     

     

    What is the difference between them? i.e. how would I choose which to use?

     

     

    I did try this once, but ran into difficulties. I suspect that it's a problem with our F5 / software version.
  • steveh's avatar
    steveh
    Icon for Nimbostratus rankNimbostratus
    I've had nothing but problems trying to use 'persist lookup' and 'session lookup' commands in my irules, but i can't see the reason for the infamous "prerequisite operation not in progress" error. here's my irule in case anyone else can find the problem:

     

     

    NB: due to my delivery model, there is no default pool configured for the virtual server.

     

     

    iRule:

     

     

    when CLIENT_ACCEPTED {

     

    set add_persist 1

     

    set DEBUG 1

     

    }

     

     

    when HTTP_REQUEST {

     

    set req_uri [string tolower [URI::path [HTTP::uri] 1 1]]

     

    switch -glob $req_uri {

     

    "/" -

     

    "/?Open*" -

     

    "*.exe*" -

     

    "*.dll*" -

     

    "*.pl*|" -

     

    "*.php*;*" -

     

    "/index.html" -

     

    "/manager*" -

     

    "/status*" -

     

    "/sysProps*" -

     

    "/healthCheck*" {

     

    Unautorized Access Attempted

     

    log local0. "Unauthorized access by Host [IP::remote_addr] detected for URI: [HTTP::uri]"

     

    HTTP::respond 404 "Access ForbiddenThis security violation has been logged.

    "

     

    }

     

    }

     

    set http_request_time [clock clicks -milliseconds]

     

    set request_log_line "[HTTP::request_num] - [IP::remote_addr] - [HTTP::method] - [HTTP::version] - [HTTP::host] - \"[HTTP::uri]\" - \" [HTTP::header value Referer] \" - \"[HTTP::header User-Agent]\" - \"[HTTP::cookie value JSESSIONID]\" - [SSL::cipher name] - [SSL::cipher version] - [SSL::cipher bits]"

     

    if { [HTTP::cookie exists JSESSIONID] } {

     

    set sid [HTTP::cookie JSESSIONID]

     

    set orig_uri [session lookup uie [list $sid any virtual]]

     

    log local0.crit "lookup original URI: $orig_uri"

     

    if { $DEBUG } {

     

    log local0.crit "Got request with sid: $sid; req-URI: $req_uri; http-request: [HTTP::uri]"

     

    }

     

    if { $orig_uri equals $req_uri } {

     

    if { $DEBUG } {

     

    log local0.crit "orig: $orig_uri, curr: $req_uri; got valid result from lookup and servicing request"

     

    }

     

    persist uie $sid

     

    } else {

     

    if we get here, we have an error

     

    if { $DEBUG } {

     

    log local0.crit "Persistence entry: [HTTP::cookie JSESSIONID] has URI: $orig_uri, but I got $req_uri. Request cannot be handled!"

     

    }

     

    HTTP::respond 404 "Access ForbiddenThis security violation has been logged.

     

    "

     

    }

     

    }

     

    switch -glob [string tolower [HTTP::uri]] {

     

    "/client01*" {

     

    Maintenance page redirect

     

    HTTP::redirect http://www.foo.com/maintenance/maintenance.htm

     

    }

     

    "/sec_prod*" {

     

    use pool eapp_clu01

     

    }

     

    "/sec_stage*" {

     

    use pool stage_clu04

     

    }

     

    }

     

    }

     

     

    when HTTP_RESPONSE {

     

    HTTP::header replace Server "HaloWeb"

     

    HTTP::header replace X-Powered-By "HaloWeb Hosting Solution"

     

    if { [HTTP::cookie exists "JSESSIONID"] and $add_persist } {

     

    set persist_string "[HTTP::cookie JSESSIONID]:$req_uri"

     

    set sid [HTTP::cookie JSESSIONID]

     

    persist add uie $sid 1920

     

    session add uie $sid $req_uri 1920

     

    if { $DEBUG } {

     

    log local0.crit "Create new persistence hash: $sid, stored URI: $req_uri"

     

    }

     

    set add_persist 0

     

    }

     

    if { [HTTP::header exists "Content-Length"] } {

     

    set content_length [HTTP::header "Content-Length"]

     

    } else {

     

    set content_length 1

     

    }

     

    set http_response_time [clock clicks -milliseconds]

     

    log local0. "$request_log_line - [HTTP::status] - $content_length - [expr $http_response_time - $http_request_time] - pool [LB::server pool] - node [LB::server addr]:[LB::server port]"

     

    }

     

  • hi colin,

     

    If the timeout is not given or set as 0, what's default value of timeout will be used.

     

    And what's the max value of timeout we can set.

     

    Thanks.
  • Looks like another typo in the tutorial. I think the line:

     

    session add ssl [SSL::sessionid] [list ]X509::issuer[ ]X509::subject[ ]X509::version[] 180

     

     

    should be:

     

    session add ssl [SSL::sessionid] [list [X509::issuer] [X509::subject] ]X509::version[] 180
  • Could someone please fix the typo in the original example? (It seems like there are a lot of these scattered throughout the iRules 101 pages.) E.g. where it reads:

     

    session add ssl [SSL::sessionid] [list ]X509::issuer[ ]X509::subject[ ]X509::version[] 180

     

     

    I think it should be:

     

    session add ssl [SSL::sessionid] [list [X509::issuer] [X509::subject] [X509::version]] 180
  • I agree with Network Center Sigma, is there a way to list the contents of a session table ?