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

Was this article helpful?

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).