SNAT pool persistence
Problem this snippet solves:
This example shows how select the same SNAT address from the SNAT pool for a given client IP address over multiple connections without tracking the selection in memory. The crc32 hash of the client IP address is used to select a SNAT address. See the iRule comments for further details.
Note: If this iRule is utilized on a virtual server that uses OneConnect, set the OneConnect profile's source mask to 255.255.255.255 to ensure the right SNAT pool IP address is used.
To work around an issue where the SNAT pick may be lost (ID374067), apply SNAT for each request. For instance, if using HTTP and OneConnect, you could change CLIENT_ACCEPTED to HTTP_REQUEST. Contact F5 Support references ID374067 for additional information.
Code :
# For v10 and higher, use the static namespace to store the SNAT addresses. Just change "set static::snatpool_name my_snat_pool" in the RULE_INIT event to the name of the SNAT pool for this virtual server. when RULE_INIT { # The only configuration needed is to set the name of the SNAT pool as $static::snatpool_name # Configure the name of the SNAT pool here set static::snatpool_name "my_snat_pool" # Hide the members command from the iRule parser (BZ381099 comment 7) set static::members_cmd "members -list $static::snatpool_name" # Clear any pre-existing array of the same name unset -nocomplain static::snat_ips # Initialize a counter for the number of SNAT pool members set static::i 0 # Loop through the SNAT pool members and add them to an array for faster access # If the SNAT pool is modified, the RULE_INIT code needs to be re-run to re-read the SNAT pool # Make a simple change like adding a space to a comment to force a re-run of RULE_INIT. foreach static::snat_ip [eval $static::members_cmd] { set static::snat_ips($static::i) [lindex $static::snat_ip 0] incr static::i } # Save the number of SNAT IPs to avoid getting the count on every connection set static::array_size [array size static::snat_ips] log local0. "Loaded $static::array_size SNAT IPs from $static::snatpool_name: [array get static::snat_ips]" # Clear the variables we will not use anymore unset static::snatpool_name static::members_cmd static::i static::snat_ip } when HTTP_REQUEST { # Use HTTP_REQUEST instead of CLIENT_ACCEPTED to avoid issue from BZ374067/SOL14098 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $static::snat_ips([expr {[crc32 [IP::client_addr]] % $static::array_size}]) } # Another variation using the source port of the client to determine the SNAT (Tested on v10, v11) - Bhattman when RULE_INIT { # The only configuration needed is to set the name of the SNAT pool as $static::snatpool_name # Configure the name of the SNAT pool here set static::snatpool_name "my_snat_pool" # Hide the members command from the iRule parser (BZ381099 comment 7) set static::members_cmd "members -list $static::snatpool_name" # Clear any pre-existing array of the same name unset -nocomplain static::snat_ips # Initialize a counter for the number of SNAT pool members set static::i 0 # Loop through the SNAT pool members and add them to an array for faster access # If the SNAT pool is modified, the RULE_INIT code needs to be re-run to re-read the SNAT pool # Make a simple change like adding a space to a comment to force a re-run of RULE_INIT. foreach static::snat_ip [eval $static::members_cmd] { set static::snat_ips($static::i) [lindex $static::snat_ip 0] incr static::i } # Save the number of SNAT IPs to avoid getting the count on every connection set static::array_size [array size static::snat_ips] log local0. "Loaded $static::array_size SNAT IPs from $static::snatpool_name: [array get static::snat_ips]" # Clear the variables we will not use anymore unset static::snatpool_name static::members_cmd static::i static::snat_ip } when HTTP_REQUEST { # Use HTTP_REQUEST instead of CLIENT_ACCEPTED to avoid issue from BZ374067/SOL14098 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $static::snat_ips([expr {[crc32 [IP::client_addr][TCP::remote_port]] % $static::array_size}]) } # For v9, use a local array to store the SNAT addresses: when CLIENT_ACCEPTED { # Use a local array to configure SNAT addresses. # These addresses need to be defined in a SNAT pool to ensure TMM sends gratuitous ARPs during a failover. # In this example, we use 10 addresses of any subnet. You will probably want to change these to be in the same subnet. # Any number of addresses can be used. set snat_ips(0) 1.1.1.1 set snat_ips(1) 2.2.2.2 set snat_ips(2) 3.3.3.3 set snat_ips(3) 4.4.4.4 set snat_ips(4) 5.5.5.5 set snat_ips(5) 5.5.5.5 set snat_ips(6) 6.6.6.6 set snat_ips(7) 7.7.7.7 set snat_ips(8) 8.8.8.8 set snat_ips(9) 9.9.9.9 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $snat_ips([expr {[crc32 [IP::client_addr]] % [array size snat_ips]}]) } # For v9, use a local array to store the SNAT addresses using client's source port - Bhattman when CLIENT_ACCEPTED { # Use a local array to configure SNAT addresses. # These addresses need to be defined in a SNAT pool to ensure TMM sends gratuitous ARPs during a failover. # In this example, we use 10 addresses of any subnet. You will probably want to change these to be in the same subnet. # Any number of addresses can be used. set snat_ips(0) 1.1.1.1 set snat_ips(1) 2.2.2.2 set snat_ips(2) 3.3.3.3 set snat_ips(3) 4.4.4.4 set snat_ips(4) 5.5.5.5 set snat_ips(5) 5.5.5.5 set snat_ips(6) 6.6.6.6 set snat_ips(7) 7.7.7.7 set snat_ips(8) 8.8.8.8 set snat_ips(9) 9.9.9.9 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $snat_ips([expr {[crc32 [IP::client_addr][TCP::remote_port]] % [array size snat_ips]}]) } # Here is a simple test iRule that shows the distribution of the SNAT addresses selected: when RULE_INIT { #========================================================================================= # Logic Test only in RULE_INIT # Use a local array to configure SNAT addresses. # These addresses need to be defined in a SNAT pool to ensure TMM sends gratuitous ARPs during a failover. set snat_ips(0) 1.1.1.1 set snat_ips(1) 2.2.2.2 set snat_ips(2) 3.3.3.3 set snat_ips(3) 4.4.4.4 set snat_ips(4) 5.5.5.5 set snat_ips(5) 5.5.5.5 set snat_ips(6) 6.6.6.6 set snat_ips(7) 7.7.7.7 set snat_ips(8) 8.8.8.8 set snat_ips(9) 9.9.9.9 # Test the distribution of SNAT addresses by tracking the number of hits for each SNAT pool member. # Initialize one variable per SNAT array member for {set j 0} {$j < [array size snat_ips]} {incr j}{ set [set j] 0 } # Loop through a subnet of host addresses for {set i 0} {$i < 256} {incr i}{ # Set a test client IP set ip 10.11.12.$i # Calculate the crc32 checksum of the client IP set hash [crc32 $ip] #log local0. "crc32: $hash" # Use the crc32 hash of the client IP to select a SNAT IP from the array set ip_number [expr {[crc32 $ip] % [array size snat_ips]}] #log local0. "\$ip: $ip, \$snat_ips($ip_number): $snat_ips($ip_number)" # Track which SNAT array member was selected incr $ip_number #log local0. "$ip_number, [set $ip_number]" } log local0. "Results for distribution across the SNAT array members:" for {set j 0} {$j < [array size snat_ips]} {incr j}{ log local0. "$j: [set $j]" } } # Sample log output:: Results for distribution across the SNAT array members: : 0: 27 : 1: 28 : 2: 26 : 3: 29 : 4: 27 : 5: 23 : 6: 20 : 7: 20 : 8: 28 : 9: 28