GTM iRule with NAT

Contributed by: Andrey M.

Description

This iRule was inspired by numerous questions in Q&A forum, by work of Hamish Marson at https://clouddocs.f5.com/api/irules/GTM-Translation.html and my own need to provide different IP address in GTM response based on LDNS IP. It can be used in place where WIP pool members are behind NAT and GTM should respond with NAT address if request is coming from external LDNS and with untranslated address of a pool member if request is coming from internal LDNS.

iRule Source

when LB_SELECTED {
   # Capture IP address chosen by WIP load balancing
   set wipHost [LB::server addr]

   # Chose datagroup name based on LDNS IP
   set natDatagroup [class match -value [IP::client_addr] equals "/Common/LDNS_datagroup" ]

   # Return originally chosen IP address if no NAT desired
   # Return 0.0.0.0 if request is from unknown LDNS or NAT datagroup doesn't exists
   if { $natDatagroup eq "nonat" } {
      return
   } elseif { ! [class exists $natDatagroup] } {
      host "0.0.0.0"
      return
   }

   # determine NAT address to return
   set natHost [class match -value "0.0.0.0" equals $natDatagroup]
   if { $natHost eq "" } {
      if { $wipHost ne "" } {
         set natHost [class match -value $wipHost equals $natDatagroup]
      }
      if { $natHost eq "" } {
         set natHost [class match -value "255.255.255.255" equals $natDatagroup]
      }
   }

   # Return 0.0.0.0 if couldn't determine NAT
   if { $natHost ne "" } {
      host $natHost
   } else {
      host "0.0.0.0"
   }
}

when LB_FAILED {
   # Chose datagroup name based on LDNS IP
   set natDatagroup [class match -value [IP::client_addr] equals "/Common/LDNS_datagroup" ]

   # Return to DNS if no NAT desired
   # Return 0.0.0.0 if request is from unknown LDNS or NAT datagroup doesn't exists
   if { $natDatagroup eq "nonat" } {
      return
   } elseif { ! [class exists $natDatagroup] } {
      host "0.0.0.0"
      return
   }

   # return override or catch all IP if configured
   set natHost [class match -value "0.0.0.0" equals $natDatagroup]
   if { $natHost eq "" } {
      set natHost [class match -value "255.255.255.255" equals $natDatagroup]
   }

   # Return 0.0.0.0 if couldn't determine NAT
   if { $natHost ne "" } {
      host $natHost
   } else {
      host "0.0.0.0"
   }
}

How it works

This is GTM iRule. It has to be added to the DNS->GSLB section and applied to the intended WIPs.
Two data groups control IP address in DNS response:
LDNS_datagroup - choses address translation per LDNS source
type: IP
format:
:= “nonat”|, where
- host or network IP address of LDNS
“nonat” - keyword to return original IP address of a pool member while handling query from LDNS specified by

- name of data group with address translation rules to use while handling query from LDNS specified by
. Data group name has to be prefixed with partition name. Even if you don’t use partitions prefix it with “/Common”
Example:
ltm data-group internal LDNS_datagroup {
    records {
        0.0.0.0 {
            data /Common/Internet_NAT
        }
        192.168.0.0/16 {
            data /Common/PartnerVPN_NAT
        }
        10.0.0.0/8 {
            data nonat
        }
    }
    type ip
}

This data group forces iRule to:

  • use /Common/Internet_NAT data group for address translation if request is coming from LDNS in Public IP space (including 172.16.0.0/12 which isn’t actually Public)
  • use /Common/PartnerVPN_NAT data group for address translation if request is coming from LDNS in 192.168.0.0/16 network. Which could be partner’s LDNS while connecting via VPN with address translation.
  • use original (non-translated) pool member IP address if request is coming from internal LDNS part of 10.0.0.0/8 network Note: “0.0.0.0” entry has to be entered as Network type with Address 0.0.0.0 and Mask 0.0.0.0

<NAT data group> - describes address translation rules
type: IP
format:
:= , where
- original IP address of a pool member as a host IP /32
- translated IP address of a pool member as a host IP /32
Note: Two special addresses can be used in <Address> 0.0.0.0/32 - to create “override” rule. <Host IP> in this data group record will be always returned and rest of data group entries will be ignored. Can be used to always return specific NAT address regardless of load balancing logic and pool member health status. 255.255.255.255/32 - to create “catch all” rule. <Host IP> in this data group record will be returned if none of other records matched pool member IP.
Example:
Lets say private VIPs have following NAT applied before being exposed to Internet
10.10.10.11 -> 100.100.10.11
10.10.20.12 -> 100.100.10.12
ltm data-group internal Internet_NAT {
    records {
        10.10.10.11/32 {
            data 100.100.10.11
        }
        10.10.20.12/32 {
            data 100.100.10.12
        }
        255.255.255.255/32 {
            data 100.100.10.12
        }
    }
    type ip
}
This data group makes iRule return 100.100.10.11 when WIP load balancing algorithm chooses 10.10.10.11 pool member. Return 100.100.10.12 when 10.10.20.12 is chosen. And return 100.100.10.12 if any other pool member is chosen and not specifically reflected in this data group.
Some pros for this iRule:
  • all control is performed via data groups - no need to make changes to iRule code. To accommodate different address translation logic per LDNS operator have to update data group only thus eliminating potential errors and breaking iRule code. Additional script can be used to update data group based on current NAT rules in the upstream router.
  • checks were added to prevent leaking untranslated addresses to external LDNS. iRule will return 0.0.0.0 address in any of following cases: - pool member was chosen by load balancing algorithm, its IP address is missing in NAT data group and “catch all” rule isn’t set - WIP load balancing has failed and “Return to DNS” was triggered - LDNS_datagroup references NAT data group which doesn’t exist - DNS request is coming from LDNS not listed in LDNS_datagroup Note: 0.0.0.0 IP in DNS response may trigger some intrusion detection systems. Use “catch all” IP or replace “0.0.0.0” with valid IP.
  • this is GTM iRule. It doesn’t rely on LTM events and doesn’t require active LTM license. It is also associated with individual WIPs vs. LTM iRule being associated with global listener object. Arguably this is safer approach. Especially if address translation support is required only for couple WIPs. Note: if LTM license isn’t activated data groups should be created using tmsh.
  • this iRule preserves WIP pool load balancing logic and replaces IP address only after pool member is chosen by load balancing algorithm or when load balancing fails.
  • “override” IP can be configured to mitigate temporary issues or for troubleshooting
  • “catch all” IP can be configured similar to “fallback IP” in the pool
  • no need to set up separate VIP with Private IP and another VIP with Public (NAT) IP for the same LTM pool

One con for this iRule:
  • it doesn’t handle multiple IP addresses in DNS response in case of “Return to DNS” event. Only one “catch all” or “override” IP will be returned if configured.

iRule was tested on GTM 11.5.2. Should work with all GTM 11.x versions.

The BIG-IP API Reference documentation contains community-contributed content. F5 does not monitor or control community code contributions. We make no guarantees or warranties regarding the available code, and it may contain errors, defects, bugs, inaccuracies, or security vulnerabilities. Your access to and use of any code available in the BIG-IP API reference guides is solely at your own risk.