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

**URL:** <https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638>\
**Category:** Community CodeShare\
**Tags:** irules, big-ip, data-group\
**Created:** [May 21, 2018, 9:22am UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638 "2018-05-21T09:22:40Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Lee\_Sutcliffe](https://avatars.discourse-cdn.com/v4/letter/l/958977/32.png) [@Lee\_Sutcliffe](https://community.f5.com/u/Lee_Sutcliffe)\
**Post date:** [May 21, 2018, 9:22am UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/1 "2018-05-21T09:22:40Z")

</div>

**Problem this snippet solves:**

Following on from the code share [Ephemeral Node FQDN Resolution with Route Domains - DNS Caching iRule](https://devcentral.f5.com/codeshare/ephemeral-node-fqdn-resolution-with-route-domains-dns-caching-irule-1118) 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

```tcl
dns_pool

```

Add the lookup datagroup,

```tcl
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.

```tcl
$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

```tcl
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 :**

```auto
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

---

<div class="post-metadata">

**Author:** ![stan\_piron](https://avatars.discourse-cdn.com/v4/letter/s/7bcc69/32.png) [@stan\_piron](https://community.f5.com/u/stan_piron)\
**Post date:** [October 3, 2018, 12:50pm UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/2 "2018-10-03T12:50:11Z")

</div>

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.

```tcl
when HTTP_REQUEST priority 400 {

```

---

<div class="post-metadata">

**Author:** ![Lee\_Sutcliffe](https://avatars.discourse-cdn.com/v4/letter/l/958977/32.png) [@Lee\_Sutcliffe](https://community.f5.com/u/Lee_Sutcliffe)\
**Post date:** [October 3, 2018, 1:21pm UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/3 "2018-10-03T13:21:46Z")

</div>

Thanks Stan.. it’s a copy and paste error from another version when I put it together for DC

---

<div class="post-metadata">

**Author:** ![thundat00th\_206](https://avatars.discourse-cdn.com/v4/letter/t/a87d85/32.png) [@thundat00th\_206](https://community.f5.com/u/thundat00th_206)\
**Post date:** [November 29, 2018, 9:02pm UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/4 "2018-11-29T21:02:30Z")

</div>

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

```tcl
set nodeIpRd $dnsResolvIp$nodeRd

```

Instead of

```tcl
set nodeIpRd $dnsResolvIp $nodeRd

```

---

<div class="post-metadata">

**Author:** ![wlopez](https://avatars.discourse-cdn.com/v4/letter/w/c77e96/32.png) [@wlopez](https://community.f5.com/u/wlopez)\
**Post date:** [February 7, 2020, 9:03pm UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/5 "2020-02-07T21:03:44Z")

</div>

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](http://app1.my-domain.com)

%0|NODE-RD %2|TTL 300|"&nbsp;&nbsp;&nbsp;&nbsp;while executing “if {[class match -value [HTTP::uri] starts\_with $dnsDG]} {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set dnsValues [split [string trim [class match -value [HTTP::uri] starts\_with $…”

---

<div class="post-metadata">

**Author:** ![Vladimir\_Shishk](https://avatars.discourse-cdn.com/v4/letter/v/4af34b/32.png) [@Vladimir\_Shishk](https://community.f5.com/u/Vladimir_Shishk)\
**Post date:** [September 7, 2022, 11:26am UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/6 "2022-09-07T11:26:24Z")

</div>

This iRule doesn’t work in 15.1.1

```auto
node $nodeIpRd $port

```

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

---

<div class="post-metadata">

**Author:** ![Vladimir\_Shishk](https://avatars.discourse-cdn.com/v4/letter/v/4af34b/32.png) [@Vladimir\_Shishk](https://community.f5.com/u/Vladimir_Shishk)\
**Post date:** [September 9, 2022, 10:09am UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/7 "2022-09-09T10:09:21Z")

</div>

Remark:

```auto
node $nodeIpRd $port

```

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

---

<div class="post-metadata">

**Author:** ![DanSkow](https://avatars.discourse-cdn.com/v4/letter/d/ecb155/32.png) [@DanSkow](https://community.f5.com/u/DanSkow)\
**Post date:** [November 21, 2025, 1:45pm UTC](https://community.f5.com/t/dynamic-fqdn-node-dns-resolution-based-on-uri-with-route-domains-and-caching-irule/68638/8 "2025-11-21T13:45:06Z")

</div>

Old topic, but… If the goal is just to have the F5 route to an FQDN node based on a route specified in another Route Domain, then one option is to attach this simple iRule to any VIPs using FQDN nodes defining the Route Domain Gateway the pool members should use:

```tcl
when CLIENT_ACCEPTED {
    nexthop 10.10.10.1%1
}

```

Many other options here are using some variation of “set node” in HTTP\_REQUEST to modify the node selection to have the Route Domain appended to it. The main issue I’ve observed with these methods is that using “set node” prevents any load balancing and persistence used by the VIP/pool from happening, since the load balancing decision and persistence takes place in LB\_SELECTED, which is after HTTP\_REQUEST. They also don’t take healthchecks into account, and would send traffic to a downed node.

This iRule still uses whatever load balancing method is assigned to the pool, and whatever persistence is assigned to the VIP, and it doesn’t interfere with any other HTTP\_REQUEST iRules attached to the VIP since it uses CLIENT\_ACCEPTED.

The only remaining downside of this iRule is that the healthcheck traffic will still use the default Route Domain routing table. Unfortunately this issue can’t be bypassed by using an inband monitor, because inband monitors aren’t supported on FQDN nodes.
