exchange-snpool-persist
Problem this snippet solves:
Exchange 2010 recently introduced a brand-new topology: the CAS (Client Access Service) array. Before, all mail users of an Exchange system would reference one or more CAS servers and then connect directly to their assigned Mailbox server. Now, with Exchange 2010, users attach to the CAS server and the server retrieves the user's email from the most appropriate Mailbox server. This change in topology was necessary to implement certain high-availability mailbox recovery schemes.
It does, however, introduce one particularly interesting wrinkle. The CAS array a group of CAS servers bound together by a common namespace must be prepared to accept incoming RPC connections. When put behind a loadbalancer, this becomes a bit tricky to distribute: RPC negotiates connections across multiple incoming TCP ports from the client.
The excellent F5 resource on configuring the loadbalancer for Exchange 2010 CAS (http://www.f5.com/pdf/deployment-guides/f5-exchange-2010-dg.pdf)) rightly points out that the CAS array is perfectly happy cooperating with a loadbalancer via RPC. Unfortunately, it does not adequately describe how to set up a very large instance of a CAS array with a SNAT pool.
First, it's necessary to modify the instructions for creating the RPC persistence profile. It is not uncommon for an Exchange CAS server to have a lengthy timeout for RPC lengthier than you might realize. In the "exch-rpc-persist" profile, set the timeout to 7200. This matches the Exchange timeout on RPC name registration. In addition, make sure to check the box next to "Match Across Services" this is important. RPC will set up related connections to an incoming request, and we must persist all related connections to the same CAS pool member.
Next, a large CAS array must use special considerations when operating with a SNAT pool be it a manual SNAT pool or an Automap with multiple Self-IPs. Exchange CAS is perfectly happy negotiating connections through SNAT (certainly simplifies setup that you don't need to route through the F5), but the SNAT pool IP assignment MUST NOT CHANGE while the connection is in progress. Unfortunately, the F5 tends to use a round-robin assignment to dole out SNAT masquerading IPs, which will lead to RPC faults. Why? It's probably intended as a security mechanism to keep someone from watching your RPC port mapping and responding to the negotiated port from a different IP before you do.
So the idea is to assign a SNAT pool entry that is persistent per incoming IP address -- call it "SNAT persistence." The code below will do this.
Code :
when RULE_INIT { # Set your SNAT pool members. # # The list should contain all the same IP addresses as your SNAT pool configuration does. # Note that this does not exempt you from applying a SNAT pool at the VS level; you must # still do that. This must be done in RULE_INIT until such time as there is a way to get # a list of SNAT pool members via an iRule function. set static::snpool { 10.x.x.1 10.x.x.2 10.x.x.3 10.x.x.4 } # If using OPTION 2, uncomment the following to seed the FNV hash. set static::fnv_hash 0x811c9dc5 set static::fnv_prime 0x01000193 } when CLIENT_ACCEPTED { #----------- # OPTION 1 # Convert the incoming IP to Hex and set the snat pool member based on the modulo of the full IP. # # This option is fine and quite quick for most purposes, but depending on how your organization # assigns IP addresses via DHCP may not lead to a high degree of randomness. This could cause more # incoming connections to prefer a particular SNAT pool address. Uncomment the following lines to # enable it. #set octets [split [IP::remote_addr] .] #if { [llength $octets] != 4 } { # set octets [lrange [concat $octets 0 0 0] 0 3] #} #binary scan [binary format c4 $octets] H8 packed_address #set packed_address [format 0x%x [expr $packed_address & 0xffffffff]] #---------- # OPTION 2 # Generate an FNV 64-bit hash of the remote IP address on a connected client. # # This option requires more CPU time at the F5 but will inject a good degree of randomness # in SNAT selection. for { set fnv_i 0 } { $fnv_i < [string length [IP::remote_addr]] } { incr fnv_i } { binary scan [IP::remote_addr] @${fnv_i}H2 fnv_str_i set fnv_hash [expr $static::fnv_hash ^ 0x$fnv_str_i] set fnv_hash [expr $fnv_hash * $static:fnv_prime] } set packed_address [format 0x%x [expr $fnv_hash & 0xffffffff]] # Select a SNAT based on the modulo of the FNV hash or hex-converted IP address according # to the size of the SNAT pool configured above. snat [lindex $static::snpool [eval { expr $packed_address % [llength $static::snpool] }]] #log local0. "Selected [lindex $::snpool [eval {expr $packed_address % [llength $::snpool] }]] from pool of size [llength $::snpool] due to findng modulo [eval {expr $packed_address % [llength $::snpool]}] from [IP::remote_addr]" }