Exchange Persistence Duality and iRules

Whether you're in the IT industry or just using IT in whatever industry you're in, chances are good that most people are using email in the workplace to some extent. Those of us in the IT field rely on it as a mission critical tool that we use to support our business. A larger and larger part of this email use is becoming a mix of internal and external usage. An environment that supports both local clients as well as people logging in remotely to not only check their email and calendar, but to work remotely while remaining effective is a must.

The good news is there are robust solutions available these days to allow for this kind of deployment in order to meet the needs of today's workforce. Microsoft's Exchange server is definitely one such solution, and one that F5 is able to support and optimize quite well. We believe so strongly in how much we can help in these deployments that we have a deployment guide (PDF) that will lead you through some of the ins and outs of how to use our gear with Exchange for the best results. One of the things that we recommend in that guide is to turn on Universal Persistence when trying to enable Outlook Anywhere. That's all well and good until you try to send your Outlook Web Access traffic through the same Virtual Server and realize that now you need cookie persistence, but only for that traffic. That's a problem.

As luck would have it another feature of the LTM can be used to enable both persistence types, according to traffic, and circumvent the inherent issue in the above scenario. So you've got two kinds of traffic, with two different persistence needs running across the same Virtual...how do you sort it all out? iRules, of course. The below iRule is a great example of how the LTM's flexibility, thanks to iRules, is changing the realm of the possible among businesses today. Less time is spent in Dev cycles, arguing with vendors or trying to re-architect things, more time is spent with the application up and running the way it's supposed to.

In this particular example the iRule looks to see if an Authorization header is present, indicating the need for Universal Persistence, per the guidance of the deployment guide. If it's found, it simply sets up persistence and away you go. If it's not found, however, the iRule goes to work looking for a previously set persistence cookie, or setting up a new one as needed, and ensuring the traffic is being sent to the right place so that OWA functionality will behave as expected, and you don't have to worry about trying to separate traffic out into two separate virtual servers.

There's also some bonus resiliency built in, but that's just the icing on the cake, as you've already solved your core problem of trying to get both types of traffic playing nice across a single virtual. Many thanks go to G. Craff who submitted this awesome example to the DevCentral iRules Codeshare. This is an awesome look at some of the things that iRules can do to make your life easier, and your business run that much smoother.

when RULE_INIT {
  # see SOL6917 for cookie encoding details: https://tech.f5.com/home/solutions/sol6917.html        
  set ::debug 0
} 

when HTTP_REQUEST {
  set reselect_retries 0
  set persistCookieNeeded 0
  if { [HTTP::header exists "Authorization"] } {
    # If we detect this header, do Universal persistence as specified in the deployment document
    persist uie [HTTP::header "Authorization"]
    if {$::debug != 0}{log local0. "Outlook client detected at [IP::client_addr]:[TCP::client_port]"}
  }
  else
  {
    # Implement cookie persistence manually. Will only work with the default pool.
    if { [HTTP::cookie exists "BIGipServer[LB::server pool]"] }
    {
      if { [catch {
        scan [HTTP::cookie "BIGipServer[LB::server pool]"] "%d.%d.%d" myIpE myPortE unused
        if {$::debug != 0}{log local0. "myIpD=$myIpE   myPortE=$myPortE  unused=$unused"}
        # calculate IP
        set myIpH [format %08x $myIpE]
        if {$::debug != 0}{log local0. "myIpH=$myIpH"}
        set myIpD "[expr 0x[substr $myIpH 6 2]].[expr 0x[substr $myIpH 4 2]].[expr 0x[substr $myIpH 2 2]].[expr 0x[substr $myIpH 0 2]]"
        if {$::debug != 0}{log local0. "myIpD=$myIpD"}
        # calculate port
        set myPortH [format %x $myPortE]
        if {$::debug != 0}{log local0. "myPortH=$myPortH"}
        set myPortD [string trimleft [expr 0x[substr $myPortH 2 2]][expr 0x[substr $myPortH 0 2]] 0]
        if {$::debug != 0}{log local0. "myPortD=$myPortD"}
    
        # Send the request to the indicated pool member, cheating to use only port 80
        pool [LB::server pool] member $myIpD $myPortD
        } ] }
      { 
        set persistCookieNeeded 1
        if {$::debug != 0}{log local0. "We could not decipher a cookie with name BIGipServer[LB::server pool]"}
      }
    }
    else
    {
      set persistCookieNeeded 1
      if {$::debug != 0}{log local0. "We do not detect a cookie with name BIGipServer[LB::server pool]"}
    }
  } 
}

when LB_FAILED {
  if { ( $reselect_retries == 0 ) and ( [active_members [LB::server pool]] > 0 ) }
  {
    LB::detach
    LB::reselect [LB::server pool]
    set reselect_retries 1
    if { not ( [HTTP::header exists "Authorization"] ) } {set persistCookieNeeded 1}
    if {$::debug != 0}{log local0. "[LB::server pool]/[LB::server addr]:[LB::server port] down, reselecting"}
  }
}

when HTTP_RESPONSE {
  if { $persistCookieNeeded }
  {
    # calculate IP section
    scan [LB::server addr] "%d.%d.%d.%d" poolIP1 poolIP2 poolIP3 poolIP4 
    set poolIPE [expr $poolIP4 * 16777216 + $poolIP3 * 65536 + $poolIP2 * 256 + $poolIP1 ]
    if {$::debug != 0}{log local0. "poolIPE=$poolIPE"}
    # calculate port section
    set poolPortE [expr 0x[substr [format %04x [LB::server port]] 2 2][substr [format %04x [LB::server port]] 0 2]]
    if {$::debug != 0}{log local0. "poolPortE=$poolPortE"}

    # Insert cookie
    HTTP::cookie insert name "BIGipServer[LB::server pool]" value "$poolIPE.$poolPortE.0000"
    if {$::debug != 0}{log local0. "BIGipServer[LB::server pool] value is $poolIPE.$poolPortE.0000"}
  }
}
Published Nov 12, 2008
Version 1.0

Was this article helpful?

No CommentsBe the first to comment