BIG IP f5

iRule redirect to webpage based on f5 session count

f5

Recently a request came in from a customer who wanted to implement an iRule on their f5 device that sends users to a waiting room page if a session threshold is breached. I’ve worked with the f5’s in anger before and think they are great (along with VPX and StingRays too), but this wasn’t something I had done before so I was keen to get involved to see how this worked. The customer is a well known shop on Oxford Street and when they have busy periods like Christmas Comes Early or the Boxing Day Sales, their website gets hammered. I imagine the same happens on their website as when you see on the news all those people piling in to the shop when the shop open their doors when the sales start.

I looked on Dev Central to find who else had done this and sure enough there were a few different examples. To count sessions, you can use the table feature available in firmware 10.1 +. This is really good and opens the door to endless possibilities. Here’s a quick look at what it does..

when CLIENT_ACCEPTED {
    set tbl "connlimit:[IP::client_addr]"
    set key "[TCP::client_port]"

    if { [table keys -subtable $tbl -count] > 1000 } {
        event CLIENT_CLOSED disable
        reject

    } else {
        table set -subtable $tbl $key "ignored" 180
        set timer [after 60000 -periodic { table lookup -subtable $tbl $key }]
    }
}

when CLIENT_CLOSED {

    after cancel $timer
    table delete -subtable $tbl $key
}

This isn’t the iRule I used, but all this is doing is setting 2 variables – tbl and key – when a CLIENT_ACCETPED event is triggered, i.e. someone is connecting to your VS, the if statement then handles whether the connection is closed or the connection details are added to the f5 subtable with a time stamp set. I’ve just summarised that far too quickly, however, if you are keen to get involved with how this works, check out dev central and the examples there.

The next request from the customer was how they can set the thresholds – so for example, how many sessions are allowed before users are then redirected to a holding page. For this, I used a data group. A data group is a dictionary like vault that holds key value pairs. You can add a data group easily in the UI, so rather than changin values directly in the iRule itself, operational guys can change the value in the data group and the value then propagates itself to the iRule. Here is the iRule code that looks up the data group value.

set static::maxActiveClients "[class search -all -value datagroup_variables starts_with "max_active_clients"]"

I have a max_active_clients in the datagroup_variables data group which has a configured value which is then set to the static::maxActiveClients in the iRule. So if the value is changed in the data group, the variable value in the iRule is changed. It is important to have the variable in the iRule in an event that gets triggered regularly, like a HTTP_ACCEPT rather than an RULE_INIT event, otherwise you change the data group and the event doesn’t trigger and the value doesn’t change in the iRule.

Here is a example iRule close to what I used – the main difference is that this example does not use data groups and has logging turned on pretty much everywhere.


#timing on

# Count the number of HTTP sessions on a VS, and limit them. Use the table feature of 10.1 to track the number of
# current sessions
#
# This is a simple version of limiting the number of sessions to a website.
#
#  Notes
#   1. Cookies are opaque values. Keys into the session table only
#   2. No checksum is included in the cookie value
#   3. No additional checking is performed.
#   4. The cookie isn't set secure (So will work on HTTP, but you may want to change for an HTTPS only site)
#   5. There is no concept of 'LOGOUT' of a session... You can add this under HTTP_REQUEST...
#

when RULE_INIT {

   log local0. "Running RULE_INIT"
   # All of these parameters SHOULD be set from a class... So you can vary them and only require ONE iRule for all...

   # Max session count
   set static::max_active_clients 5

   # Prefix for session cookie (VS name will be appended)
   set static::sessionCookiePrefix "session"

   # Session (Idle) timeout in seconds
   set static::sessionTimeout 600

   log local0. "rule session_limit initialized: max: $static::max_active_clients cookieprefix: $static::sessionCookiePrefix timeout: $static::sessionTimeout"
}

when HTTP_REQUEST {
   set subtableName "sessionLimit-[virtual name]"
   set sessionCookieName "$static::sessionCookiePrefix-[virtual name]"
   set need_cookie 0

   ;# test cookie presence
   if {[HTTP::cookie exists $sessionCookieName]} {
      set client_id [HTTP::cookie $sessionCookieName]
         # Check the session still exists
         set sessiondata [table lookup -subtable $subtableName $client_id]
         if { $sessiondata != "" } {
         # We have a valid session... The lookup has reset the timeer on it so just finish processing
         # Optional processing in here to check or validate the client session via IP etc if required...
         log local0. "Valid session $client_id - continuing"
         return
      }
   }

   # No valid session... So do we have a free 'slot'?
   log local0. "No session. Checking for free slot (Max     $static::max_active_clients)"
   set sessionCount [table keys -subtable $subtableName -count]
   log local0. "No session. Checking for free slot (Current $sessionCount)"

   if {$sessionCount < $static::max_active_clients} {
      # Yes we have a free slot... Allocate it and note that we need to set the cookie on the client
      set need_cookie 1
      set client_id [format "%08d" [expr { int(100000000 * rand()) }]]
      set sessionValue [IP::client_addr]

      table add -subtable $subtableName $client_id $sessionValue $static::sessionTimeout
      log local0. "New Session ($client_id) added value $sessionValue Timeout $static::sessionTimeout"
   } else {
      HTTP::redirect "http://sorry.domain.com/"
   }
}

when HTTP_RESPONSE {
   ;# insert cookie if needed
   if {$need_cookie == 1} {
      HTTP::cookie insert name $sessionCookieName value $client_id path "/"
   }
}