Decoding the IPv4 address from the persistence cookie

I was working with a colleague on how best to manage connections to multiple sites when DNS persistence is lacking. That article will come, but as we discussed solutions, digging into the persistence cookie that the BIG-IP inserts led to some fun with decimal->hexadecimal->decimal conversions. Encoding details are available in knowledge article K6917

In this brief article, I'll show two approaches to tackling the IPv4 address conversion (there are more, feel free to discover and share!) and then share an iRule I made to performance test each solution on its own, both directly in the event and then through the use of a proc should you want to use that functionality elsewhere.

First approach - the expr and substr commands

This approach is already out in the DevCentral codeshare courtesty of DevCentral and F5 alum Deb Allen. That iRule overall needs some love (hello global variables!) but the approach to the persistence cookie is intriguing. From my logs, here's an example of the breakdown in a lab environment.

Cookie Contents: 1701187756.20480.0000
EncodedIP=1701187756, EncodedPort=20480
HexIP=656610ac, HexPort=5000
Decoded Pool Member: 172.16.102.101:80

And now, the appropriate code.

scan [HTTP::cookie "SitePersistTest"] {%[^\.]} EIP
set HIP [format %08x $EIP]
set ip4 [expr 0x[substr $HIP 0 2]]
set ip3 [expr 0x[substr $HIP 2 2]]
set ip2 [expr 0x[substr $HIP 4 2]]
set ip1 [expr 0x[substr $HIP 6 2]]
set addr $ip1.$ip2.$ip3.$ip4

The scan (used here only to grab the encoded IP from the cookie value) would have more there if we were also pulling out the port, but since we're only concerned with the IP address, I left it out. Deb's iRule linked above has the additional code. You could also achieve the same thing with getfield and splitting on the the period (or an lindex/split, but we don't need to rabbit-hole on approaches 1a, 1b, 1c...)

Now that we have the encoded IP, which is in decimal form, we need to convert that to hex. That is accomplished with the format command and as shown includes padding. The next four lines use the combination of the substr (custom iRule command) and expr commands to set each of the dotted decimal IP address octets. Because the hex string is reversed by bytes of two, we use substr to take the first two bytes and then expr to convert those two bytes to decimal and store that in the fourth octet, then the second two bytes to the third octet and so on. Finally, we set the IPv4 address by concatenating all the ipN values in octet order. Done-zo!

Second approach - the scan command

We start with the same scan command as the first approach just to get the encoded IP from the cookie value. Otherwise, the code is pretty short.

scan [HTTP::cookie "SitePersistTest"] {%[^\.]} EIP
scan [format %08x $EIP] %2x%2x%2x%2x ip4 ip3 ip2 ip1
set addr $ip1.$ip2.$ip3.$ip4

The second scan command is where the action is here. It takes the encoded IP and formats it just like the last approach with padding, and then converts each two byte hex string to a decimal in the variables listed at the end of the scan statement. Just like the first approach, that formatted hex string is in reverse order, so we store the values in descending octets instead of ascending. And again we set the IP address the same as in the first approach.

Considering performance

Both of these solutions work, but always when I get something working, I try to think about running a script once against data on my hard drive versus running code on potentially thousands of requests per second. It changes the outlook. So after proving functionality, I looked at perfomance. In this case I wanted to strip everything down to just the mechanics of what was required and set as few variables as possible and have as few ancillary tasks as possible. Here's the rule testing each approach.

proc decode_ip_scan { encoded_ip } {
    scan [format %08x $encoded_ip] %2x%2x%2x%2x ip4 ip3 ip2 ip1
}

proc decode_ip_expr { encoded_ip } {
    set HIP [format %08x $encoded_ip]
    set ip4 [expr 0x[substr $HIP 0 2]]
    set ip3 [expr 0x[substr $HIP 2 2]]
    set ip2 [expr 0x[substr $HIP 4 2]]
    set ip1 [expr 0x[substr $HIP 6 2]]
}
when RULE_INIT {
    # IPv4 address 172.16.102.101 as encoded in persist cookie
    set static::encoded_ip 1701187756
    # Select tests
    # 1 - scan
    # 2 - expr/substr
    # 3 - scan in proc
    # 4 - expr/substr in proc
    set static::test 1
}
when HTTP_REQUEST {
    switch $static::test {
        1 {
            scan [format %08x $static::encoded_ip] %2x%2x%2x%2x ip4 ip3 ip2 ip1
        }
        2 {
            set HIP [format %08x $static::encoded_ip]
            set ip4 [expr 0x[substr $HIP 0 2]]
            set ip3 [expr 0x[substr $HIP 2 2]]
            set ip2 [expr 0x[substr $HIP 4 2]]
            set ip1 [expr 0x[substr $HIP 6 2]]        
        }
        3 {
            call decode_ip_scan $static::encoded_ip
        }
        4 {
            call decode_ip_expr $static::encoded_ip
        }
    }
}

 Each approach is handled in the iRule two ways: the first directly in the event, and the second by moving the logic to a procedure and calling it from the event. All unnecessary variable setting has been removed. Here's the comparison in average CPU cycles from 200,000 requests for each test with the apache bench traffic generation tool.

ab -n 200000 -c 5 http://172.16.101.101/

 

Test description Average CPU Cycles
scan approach 19.1K
expr/substr approach 30.1K
scan approach - in proc 21.1K
expr/substr approach - in proc 32.5K

You can see that the scan approach is way faster than the expr/substr approach, and that doing it directly is marginally faster than in the procedure. Note that this performance testing is a general and imprecise optimization using iRule timing. If you want to take a look at the bytecode operations, that can be done with the rule tracing functionality (part1, part2, part3). Having done that previously on other projects, I can say that with the scan approach, all the operations occur in the Tcl virtual machine. With the expr/substr approach, the operations have to bounce back and forth between the Tcl virtual machine and TMM since substr is a custom iRules command. Not only that, but the second approach takes 14 operations to only 6 in the first approach.

Conclusion

Much like any other programming language that's been around for a long time (looking at you, Perl...), there are many ways to solve a problem in Tcl. What other solutions have you seen for decoding the persistence cookie IPv4 address, and are they faster than the scan approach? Happy coding out there, community! 

Updated Nov 16, 2023
Version 2.0

Was this article helpful?