A Catch from the Codeshare: Network Translations

On the side of the road in northern Missouri just north of Mark Twain’s stomping grounds, there is a slice of hillside removed just to the side of the highway. In Arkansas, there’s a nondescript field tucked away in a state park. Short of word of mouth and this thing they call the internet, you wouldn’t be any the wiser that buried or surfaced in these two locations are an amazing variety of geodes and diamonds, respectively. In this article series I will explore recent and well-aged gems from the codeshare, highlighting inventive solutions contributed by you, the community. Please join me on this great adventure as we oscillate on the Mohs’ scale of codeshare geekery.

Network Translations

Today’s catch from the codeshare is courtesy of F5er Gregory Thiell, who shared a handy dandy iRule that masks the client bits and translates only the network. This is beneficial if you are wanting to match clientside and server side transactions at the IP layer in analysis or logging tools. It requires a data group with entries of the pre/post translation networks like so:

1.1.1.0/24 := 10.10.10.0
1.1.2.0/24 := 10.10.20.0

This will result in a client IP of 1.1.1.5 and 1.1.2.5 being translated to 10.10.10.5 and 10.10.20.5, respectively. The iRule itself utilizes a few Tcl procs to do the heavy conversion lifting between formats. Note that the first two are just mirror functions for conversion between hex and dotted decimal to represent the IP address. The third proc deals with creating a hex net mask from the CIDR length provided with the first term in each entry in the data group.

proc IPtoHex { IP } {
  binary scan [binary format c4 [split $IP .]] H8 Hex
  return $Hex
}

proc hexToIP { Hex } {
  binary scan [binary format H8 $Hex] c4 IPtmp
  foreach num $IPtmp {
    # binary scan "c" format gives signed int - the following
    # [expr]-ology converts to unsigned (from [binary] manpage)
    lappend IP [expr ($num + 0x100) % 0x100]
  }
  set IP [join $IP .]
  return $IP
}

proc CIDRtoHexNetmask { CIDR } {
  set zeros [expr 32 - $CIDR]
  set ones $CIDR
  set binaryCIDR [string repeat 1 $ones]
  append binaryCIDR [string repeat 0 $zeros]
  binary scan [binary format B32 $binaryCIDR] H8 HexNetmask
  return $HexNetmask
}

And finally, the iRule itself! The logic is pretty straight forward. The first step is to read the data group against the real (or manually set) original IP address (lines 13-16,) and if there is a match (line 18,) start the conversions (lines 21-23.) Once the network mask is calculated (lines 26-28,) and after all the IP address bitwise math is done, the network can be extracted and replaced (lines 31-38,) and the final translated IP address can be utilized with the snat command (line 41.) You can uncomment the log statements at the end of the iRule (lines 45-51) for debugging purposes.

when RULE_INIT {
  set static::dg_net_tsl "dg_network_translations"
}
 
when CLIENT_ACCEPTED {
 
  set original_ip [IP::client_addr]
  
# Debug -- To overwrite original_ip
#  set original_ip 1.1.1.5
  
# Read Data Group
  set dg_elements [class match -element $original_ip equals $static::dg_net_tsl]
  set original_network [getfield [lindex $dg_elements 0] "/" 1]
  set cidr [getfield [lindex $dg_elements 0] "/" 2]
  set translated_network [lindex $dg_elements 1]
  
  if { $dg_elements != "" } {
    
  # Hex conversion
    set hex_original_ip [call IPtoHex $original_ip]
    set hex_netmask [call CIDRtoHexNetmask $cidr]
    set hex_translated_network [call IPtoHex $translated_network]
    
  # Calculate the hostmask (= inverted netmask)
    set hex_hostmask [expr 0x$hex_netmask ^ 0xffffffff]
    binary scan [binary format I $hex_hostmask] H8 hex_hostmask
    set hostmask [call hexToIP $hex_hostmask]
    
  # Extract the host bits (bit-wise AND between original_ip and hostmask)
    set hex_host [expr 0x$hex_original_ip & 0x$hex_hostmask]
    binary scan [binary format I $hex_host] H8 hex_host
    set host [call hexToIP $hex_host]
    
  # Translate the network bits (bit-wise OR between host bits and translated_network)
    set hex_translated_ip [expr 0x$hex_host | 0x$hex_translated_network]
    binary scan [binary format I $hex_translated_ip] H8 hex_translated_ip
    set translated_ip [call hexToIP $hex_translated_ip]
    
  # SNAT
    snat $translated_ip
    
  # Debug -- Log
    
#    log local0. "Client IP (dec, hex): ($original_ip, $hex_original_ip)"
#    log local0. "Original network (dec): ($original_network)"
#    log local0. "Netmask (CIDR, hex): ($cidr, $hex_netmask)"
#    log local0. "Translated network (dec, hex): ($translated_network, $hex_translated_network)"
#    log local0. "Hostmask (dec, hex): ($hostmask, $hex_hostmask)"
#    log local0. "Host (dec, hex): ($host, $hex_host)"
#    log local0. "Translated IP (dec, hex): ($translated_ip, $hex_translated_ip)"
    
  }
}

This is not only functionally very useful, it's a great learning iRule as well for examining Tcl procecures, expressions, binary scans/formats, etc. So whether you need it in your environment or not, grab this one for your lab box and start inspecting.

Published Nov 21, 2016
Version 1.0

Was this article helpful?

2 Comments

  • Hi Jason,

    I was little bit surprised of the complexity of the provided iRule and ended up with a less complicated 20LinesOrLess edition...

    when RULE_INIT {
        set static::dg_net_tsl "dg_network_translations"
    }
    when CLIENT_ACCEPTED {
         Read Data Group
        set original_ip [getfield [IP::client_addr] "%" 1]
        set dg_elements [class match -element $original_ip equals $static::dg_net_tsl]
        if { $dg_elements ne "" } then {
            set new_ip_prefix [findstr $dg_elements " " 1]
            set cidr_mask [findstr $dg_elements "/"  1 " "]
             IP to binary conversion
            binary scan [binary format c4 [split $original_ip "."]] B* original_ip_bin
            binary scan [binary format c4 [split $new_ip_prefix "."]] B$cidr_mask new_ip_prefix_bin
             IP prefix translation and binary to IP conversion
            binary scan [binary format B* "$new_ip_prefix_bin[string range $original_ip_bin $cidr_mask end]"] cccc oct1 oct2 oct3 oct4
            set new_ip "[expr { $oct1 & 0xff } ].[expr { $oct2 & 0xff } ].[expr { $oct3 & 0xff } ].[expr { $oct4 & 0xff } ]"
             SNAT
            snat $new_ip
        }
    }
    

    The iRule now translates the client IP by utilizing a rather simple

    [string range]
    syntax on the IPs binary string representations. The binary syntax requires not only just fewer lines, but also performs much faster compared to utilizing complex math on a HEX level.

    Cheers, Kai

  • Hi Kai, that's a great optimization! Thanks for sharing.