For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

Dynamic FQDN Node DNS Resolution based on URI with Route Domains and Caching iRule

Problem this snippet solves:

Following on from the code share Ephemeral Node FQDN Resolution with Route Domains - DNS Caching iRule that I posted, I have made a few modifications based on further development and comments/questions in the original share.

On an incoming HTTP request, this iRule will dynamically query a DNS server (out of the appropriate route domain) defined in a pool to determine the IP address of an FQDN ephemeral node depending on the requesting URI. The IP address will be cached in a subtable so to prevent querying on every HTTP request

It will also append the route domain to the node and replace the HTTP Host header.

Features of this iRule * Dynamically resolves FQDN node from requesting URI * Iterates through other DNS servers in a pool if response is invalid or no response * Caches response in the session table using custom TTL * Uses cached result if present (prevents DNS lookup on each HTTP_REQUEST event) * Appends route domain to the resolved node * Replaces host header for outgoing request

How to use this snippet:

Add a DNS pool with appropriate health monitors, in this example the pool is called

dns_pool

Add the lookup datagroup,

dns_lookup_dg
. This defines the parameters of the DNS query. The values in the datagroup will built up an array using the key in capitals to define the array object e.g.
$myArray(FQDN)
Modify the values as required:

FQDN: the FQDN of the node to load balance to. DNS-RD: the outbound route domain to reach the DNS servers NODE-RD: the outbound route domain to reach the node TTL: TTL value for the DNS cache in seconds

ltm data-group internal dns_lookup_dg {
    records {
        /app1 {
            data "FQDN app1.my-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8443"
        }
        /app2 {
            data "FQDN app2.my-other-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8080"
        }
        default {
            data "FQDN default.domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 443"
        }
    }
    type string
}

Code :

ltm data-group internal dns_lookup_dg {
    records {
        /app1 {
            data "FQDN app1.my-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8443"
        }
        /app2 {
            data "FQDN app2.my-other-domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 8080"
        }
        default {
            data "FQDN default.domain.com|DNS-RD %10|NODE-RD %20|TTL 300|PORT 443"
        }
    }
    type string
}


when CLIENT_ACCEPTED {
    set dnsPool "dns_pool"
    set dnsCache "dns_cache"
    set dnsDG "dns_lookup_dg"
}

when HTTP_REQUEST {
    # set datagroup values in an array
    if {[class match -value [HTTP::uri] "starts_with" $dnsDG]} {
        set dnsValues [split [string trim [class match -value [HTTP::uri] "starts_with" $dnsDG]] "| "]
    } else {
        # set to default if URI is not defined in DG
        set dnsValues [split [string trim [class match -value "default" "equals" $dnsDG]] "| "]
    }

    if {([info exists dnsValues]) && ($dnsValues ne "")} {
        if {[catch {
            array set dnsArray $dnsValues
            set fqdn $dnsArray(FQDN)
            set dnsRd $dnsArray(DNS-RD)
            set nodeRd $dnsArray(NODE-RD)
            set ttl $dnsArray(TTL)
            set port $dnsArray(PORT)
        } catchErr ]} {
            log local0. "failed to set DNS variables - error: $catchErr"
            event disable
            return
        }
    }

    # check if fqdn has been previously resolved and cached in the session table
    if {[table lookup -notouch -subtable $dnsCache $fqdn] eq ""} {
        log local0. "IP is not cached in the subtable - attempting to resolve IP using DNS"

        # initialise loop vars
        set flgResolvError 0
        if {[active_members $dnsPool] > 0} {
            foreach dnsServer [active_members -list $dnsPool] {
                set dnsResolvIp [lindex [RESOLV::lookup @[lindex $dnsServer 0]$dnsRd -a $fqdn] 0]

                # verify result of dns lookup is a valid IP address
                if {[scan $dnsResolvIp {%d.%d.%d.%d} a b c d] == 4} {
                   table set -subtable $dnsCache $fqdn $dnsResolvIp $ttl

                    # clear any error flag and break out of loop
                    set flgResolvError 0
                    set nodeIpRd $dnsResolvIp$nodeRd
                    break
                } else {
                    call ${partition}log local0."$dnsResolvIp is not a valid IP address, attempting to query other DNS server"
                    set flgResolvError 1
                }
            }
        } else {
            call ${partition}log local0."ERROR no DNS servers are availible"
            event disable
            return
        }
        #log error if query answers are invalid
        if {$flgResolvError} {
            call ${partition}log local0."ERROR unable to resolve $fqdn"
            unset -nocomplain dnsServer dnsResolvIp dnsResolvIp 
            event disable
            return
        }
    } else {
        # retrieve cached IP from subtable (verification of resolved IP compelted before being commited to table)
        set dnsResolvIp [table lookup -notouch -subtable $dnsCache $fqdn]
        set nodeIpRd $dnsResolvIp$nodeRd
        log local0. "IP found in subtable: $dnsResolvIp with TTL: [table timeout -subtable $dnsCache -remaining $fqdn]"
    }
    # rewrite outbound host header and set node IP with RD and port
    HTTP::header replace Host $fqdn
    node $nodeIpRd $port
    log local0. "setting node to $nodeIpRd $port"
}

Tested this on version:

12.1
Updated Jun 06, 2023
Version 2.0

6 Comments

  • Hi Lee,

    I think the line 16 must be removed.

    if you want to keep it, you must add a closing curly bracket before (line 15) and add a priority less than 500 on the HTTP_REQUEST event on line 7.

    when HTTP_REQUEST priority 400 {
    
  • Thanks Stan.. it's a copy and paste error from another version when I put it together for DC

     

  • I think that there is an extra space on line 63, should be:

    set nodeIpRd $dnsResolvIp$nodeRd

    Instead of

    set nodeIpRd $dnsResolvIp $nodeRd

  • wlopez's avatar
    wlopez
    Icon for Cirrocumulus rankCirrocumulus

    Did you ever get this iRule to work?

    I'm trying it on versions 12.1.2 and 14.1.2 but am getting TCL errors:

     

    TCL error: /Web/rule_FQDN_pool_member <HTTP_REQUEST> - expected boolean value but got "FQDN app1.my-domain.com

    %0|NODE-RD %2|TTL 300|"    while executing "if {[class match -value [HTTP::uri] starts_with $dnsDG]} {         set dnsValues [split [string trim [class match -value [HTTP::uri] starts_with $..."

  • This iRule doesn't work in 15.1.1

     node $nodeIpRd $port

    This node statement doesn't direct traffic to specified node.
    Don't waste you time.

  • Remark:

     node $nodeIpRd $port

    In 15.1.1 this statement works only for plain, non-ecnrypted HTTP traffic (not SSL).