F5 LTM – Cookie Persistence between HTTP and HTTPS

BACKGROUND

In order to to maintain persistence between services (such as HTTP and HTTPS) on a single Virtual Server two persistence methods are available ; Cookie Hashing and Source IP.

In order to perform “true” Cookie (insert) persistence across services an iRule is required.

Note : Though cookie persistence (insert) can be performed within the cookie persistence profile, this does not allow you to perform persistence across services when the pool members are on different backend ports (i.e HTTP->80 / HTTPS->81). 

SOLUTION

To maintain persistence across services an iRule is first created. This iRule is then assigned to a Universal Persistence profile which is then assigned to each of the Virtual Servers.

Cookie Types

Before we look at how the iRule(s) work it is worth mentioning the two main types of cookies. They are :

  • Persistent cookie – A persistent cookie has a date expiration. These cookies display when the cookie will expire under the Expires column (within the browser).
  • Session cookie – A session cookie does not have a date expiration set. These cookies display “session” under the Expires column (within the browser).

Note : The other types of cookies can be viewed here.

Overview

The iRule operates by creating a unique cookie which is provided to the client within the HTTP RESPONSE. This cookie value (UIE key) is also added to a universal persistance record on the F5 which is later referenced for any further HTTP REQUESTS.

Below is an example of a universal persistence record,

root@f5ltm(Active)(tmos)# show ltm persistence persist-records all-properties
Sys::Persistent Connections
universal - 172.16.100.10:80 - 192.168.1.31:80
-----------------------------------------------------------
  TMM           0
  Mode          universal
  Key           8abc87e0260ca1eddb577d37bf4018f5_1359498668
  Age (sec.)    6
  Virtual Name  VS-172.16.100.10-80
  Virtual Addr  172.16.100.10:80
  Node Addr     192.168.1.31:80
  Pool Name     POOL-172.16.100.10-80-A
  Client Addr   172.16.1.1

Note : The Age within the persistence record is an idle timeout. Meaning when a persistence look up is performed the Age is reset back to 0.

iRule Options

Because of the differences in how persistence should be performed there are 2 iRule options available here ; strict and standard.

Strict

# Name        : Virtual Server Cookie Persistence (strict)
# Purpose     : Cookie Persistence across Virtual Server services
# Methodology : Create cookie containing "unique" hash and creation time. 
#               This cookie is then used to persist connections.

when HTTP_REQUEST {
	   	set cookie_timeout 900

		if { [HTTP::cookie exists "b1P"] } {
			# calculate current/elapsed time 
			set now [clock seconds]
                        set cookie_epoch [getfield [HTTP::cookie "b1P"] "_" 2]
			set timer [expr {$now - $cookie_epoch}]
            		
			if { $timer > $cookie_timeout } {
				# if cookie timeout is exceeded create new cookie within response
				set cookie [HTTP::cookie value "b1P"]
                                set cookie_check 1
                                #_debug_# log local0. "[virtual name] [IP::client_addr] TM_EXCEED $cookie 1"
			} else {
				# if cookie is present and timeout valid then persist
				persist uie [HTTP::cookie value "b1P"]
                                set cookie_check 0
                                #_debug_# log local0. "[virtual name] [IP::client_addr] TM_OK [HTTP::cookie value "b1P"] 1"
			}					
		} else {
            		# if there is no cookie present add one within the response
			set cookie_check 2
			}
}
when HTTP_RESPONSE {
		set cookie_expiry 900

		# if cookie timeout is exceeded within request delete persistance entry
		if { $cookie_check eq 1 } {
                        persist delete uie $cookie
                        #_debug_# log local0. "[virtual name] [IP::client_addr] REMOVE $cookie 1"
        }   		
		# if cookie timeout exceeded or there is no cookie then create new cookie and add to reponse
		if { ($cookie_check eq 1) or ($cookie_check eq 2)} {
			# create hash for cookie
			set now [clock seconds]
		        set token "[IP::server_addr][IP::client_addr][TCP::client_port][expr { int(1000000 * rand()) }]"
                        binary scan [md5 $token] H* md5var junk
                       
			# insert cookie and add expiry
			HTTP::cookie insert name "b1P" path / value $md5var\_$now
			HTTP::cookie expires b1P $cookie_expiry
			#_debug_# log local0. "[virtual name] [IP::client_addr] INSERT [HTTP::cookie "b1P"] 1or2"
                        persist add uie [HTTP::cookie "b1P"]
		}
}

To ensure that a client session is only persistent for a fixed time period, the strict version is used. This adds an expiry time to the cookie. Unfortunately because not all browsers honour this expiry value, at the point the cookie is created a timestamp (using the EPOCH format) is also appended to the cookie value. This is then used to ensure that if the client sends a cookie that exceeds the timeout the connection is rebalanced.
This option works well for scenarios when session draining is required.

Within this iRule there are 2 variables that define the timeout. These are :

  • cookie_timeout – This value represents (in seconds) the amount of time between the cookie create time (appended EPOCH) and the current time.
  • cookie_expiry – This value represents (in seconds) the amount of time in seconds that will be added to the cookie expiry timeout. i.e should this value be set to 900 secs and the cookie create on the 15th April 2012 15:00 then the expiry time will be set to 15th April 2012 15:15.

Note : Both timeouts (by default) are set to 15minutes.

Standard

# Name        : Virtual Server Cookie Persistence (standard)
# Purpose     : Cookie Persistence across Virtual Server services
# Methodology : Create cookie containing "unique" hash. This cookie is then used to persist connections.

when HTTP_REQUEST {
	   if { [HTTP::cookie exists "b1P"] } {
                        # if cookie is present then persist 
			persist uie [HTTP::cookie value "b1P"]
                        set cookie_check 0 
                        #_debug_# log local0. "[virtual name] [IP::client_addr] PRESENT [HTTP::cookie value "b1P"] 0"	
			} else {
				set cookie_check 1
			}
}
when HTTP_RESPONSE { 	
                # if there is no cookie in request then add cookie to response
		if { $cookie_check eq 1 } {
			# create hash for cookie
			set now [clock seconds]
			set token "[IP::server_addr][IP::client_addr][TCP::client_port][expr { int(1000000 * rand()) }]"
                        binary scan [md5 $token] H* md5var junk
                        # insert cookie
                        HTTP::cookie insert name "b1P" path / value $md5var\_$now
			#_debug_# log local0. "[virtual name] [IP::client_addr] INSERT [HTTP::cookie "b1P"] 1"
                        persist add uie [HTTP::cookie "b1P"]
		}
}

With this option no expiry time is added to the cookie and the EPOCH timestamp is not referenced. The only value that mandates how long the session is persisted for is the Age value within the universal persistence record. However (as previously mentioned) as this is an idle timeout it can result in the client maintaining persistence to a single server for extremely long periods (i.e until the client stops sending traffic for the length of the universal persistence timeout).
This option is recommended if the application session also has a idle timeout. This ensures that the application session times out before the persistence record in turn preventing the situation where an existing session is being sent to a (newly balanced) backend server that has no knowledge of the session.

Note : Based on this you will need to ensure the idle timeout of the persistence record is longer then the idle timeout of the application.

iRule Notes

  • For the iRule jedi`s out there, you may of noticed that in the iRule (standard) there is no logic that covers the event of a cookie being sent, but there being no corresponding persistence entry. In this scenario at the point the F5 performs a ‘persist lookup’ and no UIE entry is found then the traffic will be rebalanced and a new persistence entry created.
  • Though the EPOCH value is not used within the standard option, this value is still appended to the cookie to assist in any troubleshooting that may be required.

DEPLOYMENT

To deploy either of the 2 persistence options follow the steps below:

1. Create an iRule using on of the above options.
2. Create universal persistence profile using the following settings

ltm persistence universal PERSIST-COOKIE-HTTP_S { 
 defaults-from universal 
 match-across-pools disabled 
 match-across-services enabled 
 match-across-virtuals disabled 
 mirror disabled 
 override-connection-limit disabled 
 rule <IRULE NAME> 
 timeout <???> 
}

3. Assign the persistence profile to your Virtual Servers (i.e port 80 and 443).
4. Enable OneConnect on all Virtual Servers that the new persistence profile has been applied to.

TROUBLESHOOTING

Should persistence still fail or present problems there are a number of ways that you can troubleshoot. Typically I use a method of obtaining a string within the webpage to determine which server I am connected to. I then use a combination of curl (using the cookie options) and grep to determine if basic persistence is working.

However in circumstances where the issue centres around timeout issues with your persistence session then you can :

  1. Enable debugging within the iRule and remove the #_debug_# syntax for each of the log lines. These logs can then be viewed within /var/log/ltm.
  2. To also log the node that the traffic is sent to
    • add the $node variable to the log line
    • add the following syntax above each log line — set node [persist lookup uie [HTTP::cookie value “b1P”] node]
  3. Add an entry to each log line for any other session cookie values that are being used. So that you can match up the life of the persistence cookie to that of your application session cookie.
Rick Donato

Want to become an F5 Loadbalancers expert?

Here is our hand-picked selection of the best courses you can find online:
F5 BIG-IP 101 Certification Exam – Complete Course
F5 BIG-IP 201 Certification Exam – Complete Course
and our recommended certification practice exams:
AlphaPrep Practice Tests - Free Trial