Intro
F5’s BigIP load balancers have an API accessible via iRules which are written in their bastardized version of the TCL language.
I wanted to map all incoming source IPs to a unique source IP belonging to the load balancer (source NAT or snat) to avoid session stealing issues encountered in GUIxt.
First iteration
In my first approach, which was more proof-of-concept, I endeavored to preserve the original 4th octet of the scanner’s IP address (scanners are the users of GUIxt which itself is just a gateway to an SAP load balancer). I have three unused class C subnets available to me on the load balancer. So I took the third octet and did a modulo 3 operation to effectively randomly spread out the IPs in hopes of avoiding overlaps.
rule snat-test2 { # see https://devcentral.f5.com/questions/snat-selected-source-addresses-on-a-vs # and https://devcentral.f5.com/questions/load-balance-on-source-ip-address # spread things out by taking modulus of 3rd octet # - DrJ 2/11/16 when CLIENT_ACCEPTED { # maybe IP::client_addr set snat_Subnet_base "141" set ip3 [lindex [split [IP::client_addr] "."] 2] set ip4 [lindex [split [IP::client_addr] "."] 3] set offset [expr $ip3 % 3] set snat_Subnet [expr $snat_Subnet_base + $offset] set newip "10.112.$snat_Subnet.$ip4" # log local0. "Client IP: [IP::client_addr], ip4: $ip4, ip3: $ip3, offset: $offset, newip: $newip" snat $newip } } |
It worked for awhile but eventually there were overlaps anyway and session stealing was reported.
The next act steps it up
So then I decided to cycle through all roughly 765 addresses available to me on the LB and maintain a mapping table. Maintaining variable state is tricky on the LB, as is working with arrays, syntax, version differences, … In fact the whole environment is pretty backwards, awkward, poorly documented and unpleasant. So you feel quite a sense of accomplishment when you actually get working code!
rule snat-GUIxt { # see https://devcentral.f5.com/questions/snat-selected-source-addresses-on-a-vs # and https://devcentral.f5.com/questions/load-balance-on-source-ip-address # spread things out by taking modulus of 3rd octet # - DrJ 2/22/16 when CLIENT_ACCEPTED { # DrJ 2/16 # use ~ 750 addresses available to us in the SNAT pool # initialization. uncomment after first run ##set ::counter 0 set clientip [IP::client_addr] # can we find it in our array? set indx [array get ::iparray $clientip] set ip [lindex $indx 0] if {$ip == ""} { # add new IP to array incr ::counter # IPs = # IPs per subnet * # subnets = 255 * 3 set IPs 765 set serial [expr $::counter % $IPs] set subnetOffset [expr $serial / 255] set ip4 [expr $serial % 255 ] log local0. "Matched blank ip. clientip: $clientip, counter: $::counter, serial: $serial, ip4: $ip4 , subnetOffset: $subnetOffset" set ::iparray($clientip) $ip4 set ::subnetarray($clientip) $subnetOffset } else { # already seen IP set ip4 [lindex $indx 1] set sindx [array get ::subnetarray $clientip] set subnetOffset [lindex $sindx 1] # log local0. "Matched seen ip. counter: $::counter, ip4: $ip4 , subnetOffset: $subnetOffset" } set thrdOctet [expr 141 + $subnetOffset] set snat_Subnet "10.112.$thrdOctet" set newip "$snat_Subnet.$ip4" # log local0. "Client IP: [IP::client_addr], indx: $indx, ip4: $ip4, counter, $::counter, ip3: $thrdOctet, newip: $newip" snat $newip # one-time re-set when updating the code... # Re-set procedure: uncomment, run, commnt out, run again... Plus set ::counter at the top #unset ::iparray #unset ::subnetarray } } |
Criticism of this approach
Even though there are far fewer users than my 765 addresses, they get their addresses dynamically from many different subnets. So soon the iRule will have encountered 765 unique addresses and be forced to re-use its IPs from the beginning. At that point session stealing is likely to occur all over again! I’ve just delayed the onset.
What I would really need to do is to look for the opportunity to clear out the global arrays and the global counter when it is near its maximum value and the time is favorable, like 1 AM Sunday. But this environment makes such things so hard to program…
A word about the snat pool
I used tmsh to create a snat pool. It looks like this:
snatpool SNAT-GUIxt { members { 10.112.141.0 10.112.141.1 10.112.141.2 10.112.141.3 10.112.141.4 10.112.141.5 10.112.141.6 10.112.141.7 10.112.141.8 10.112.141.9 10.112.141.10 10.112.141.11 10.112.141.12 10.112.141.13 10.112.141.14 10.112.141.15 10.112.141.16 ... |
Conclusion
A couple real-world iRules were presented, one significantly more sophisticated than the other. They show how awkward the language is. But it is also powerful and allows to execute some otherwise out-there ideas.
References and related
This article discusses trouble-shooting a virtual server on the load balancer