cancel
Showing results for 
Search instead for 
Did you mean: 
hooleylist
Cirrostratus
Cirrostratus

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
Comments
KaiDuerkop
Nimbostratus
Nimbostratus

Hey, we're using this irule to persist ~200.000 Client Connections, it works very good.

 

We have one problem with this iRule. After a BIG-IP Failover the new active BIG-IP has a problem to load the array_size variable.

 

err tmm4[11616]: 01220001:3: TCL error: /Common/snat_client - can't read "static::array_size": no such variable while executing "expr {[crc32 [IP::client_addr]] % $static::array_size}"
Sam_O_197776
Nimbostratus
Nimbostratus

Having the same issue that Kai reports above. It appears the RULE_INIT doesn't re-execute on failover and that the static array_size (or the array itself for that matter) are copied between the active and standby.

 

Just adding or removing a blank line from RULE_INIT and clicking Update re-runs the init and corrects the issue but it's a pain.

 

wonsoo_41223
Historic F5 Account

It looks the iRule execution (RULE_INIT) is earlier than loading SNAT pool, so "members -list $snatpool_name" command execution is failed, and static::snat_ips array not be existed. Try to use this code in HTTP_REQUEST. It checks existence of variable/array and set the data again if the variables are not existed. This setting logic will be run for each tmm process.

 

when HTTP_REQUEST { if { ![info exists static::array_size] or ![array exists static::snat_ips] } { unset -nocomplain static::snat_ips set snatpool_name "/Common/my_snat_pool" set members_cmd "members -list $snatpool_name" set count 0 foreach snat_ip [eval $members_cmd] { set static::snat_ips($count) [lindex $snat_ip 0] incr count } set static::array_size [array size static::snat_ips] } snat $static::snat_ips([expr {[crc32 [IP::client_addr]] % $static::array_size}]) }
KroNiX
Nimbostratus
Nimbostratus

Hello! You need to change member_cmd to members_cmd at "foreach snat_ip [eval $member_cmd] {"

 

ltwagnon
Legacy Employee
Legacy Employee

@KroNiX...change has been made. Thanks!

 

JRahm
Community Manager
Community Manager

I updated that snippet in @wonsoo's comment. Good find, @KroNiX

 

jmugica_136094
Historic F5 Account

"members -list" command is not available on rule init event. This irule fails to load during boot up, and therefore it will probably cause runtime failures during execution

 

https://clouddocs.f5.com/api/irules/members.html:

 

"Note:

 

When assigning a snatpool to static variable and using "members -list" to reference it in RULE_INIT, failures will be observed at startup but won't show up in a reload afterwards. Expected behavior is to fail it in any case as "members -list" is not designed to reference a snatpool name."

 

Emmanuel_L_1791
Nimbostratus
Nimbostratus

To mitigate initialization issue a solution could be to use a datagroup instead of Array/snatpool (tested on v11.5.4) :

 

when RULE_INIT { The only configuration needed is to set the name of the Datagroup(type string) that lists SNAT addresses as $static::snatpool_name_DG Configure the name of the Datagroup set static::snatpool_name_DG "my_snat_pool_DG" Datagroup size set static::snatpool_size [class size "static::snatpool_name_DG"] } when CLIENT_ACCEPTED { 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 [class element -name [expr {[crc32 [IP::client_addr]] % $static::snatpool_size}] $static::snatpool_name_DG] }

Datagroup exemple (keep the same list as snatpool to be declared as failover object):

 

ltm data-group internal my_snatpool_DG { records { 10.0.0.1 { } 10.0.0.2 { } 10.0.0.3 { } } type string }
Dustin_Plomondo
Historic F5 Account

The incorrect SNAT address issue described in the description of this article is addressed in version 12.0 and 11.5.3-HF2, and more information is available in AskF5 article https://support.f5.com/csp/article/K14098 (ID374067).

 

Ejes
Nimbostratus
Nimbostratus

Is there an Idle Timeout value of the SNAT Persist??

I want to know how long it holds.

Version history
Last update:
‎18-Mar-2015 15:40
Updated by:
Contributors