Supporting FQDN Generic-Host Servers in F5 DNS — An iCall + iApp Solution

The Problem

If you’ve ever tried to use F5 BIG-IP DNS (formerly BIG-IP GTM) to monitor and load balance traffic to a backend service behind AWS load balancers you’ve probably run into this problem: GTM generic-host server objects require IP addresses, but your backend service only has a DNS name.

With AWS Application Load Balancers (ALBs) and Network Load Balancers (NLBs), AWS doesn’t give you a static IP. You get a DNS name, and behind that DNS name is a pool of IP addresses that AWS reserves the right to change. The same is true for some other cloud-native services.

In GTM, a “generic-host” is a server object type for backend servers that don’t run the iQuery agent; essentially anything that isn’t a BIG-IP. You monitor them via ICMP, TCP, or custom health monitors. But to create the server object, GTM needs at least one IP address in the devices block and one IP address in the virtual-servers block. If those IPs change underneath you, GTM’s health monitoring breaks, and your pool members effectively go dark until someone manually updates the configuration.

So the question is: how do you keep a GTM generic-host server object in sync when you don’t control the IPs, only the FQDN?

F5 iCall scripts, of course.

TL;DR

I wrote an iCall script that periodically resolves an FQDN and automatically creates or updates a GTM generic-host server object, including both the devices block and the virtual-servers block, to reflect the current A records. I also wrapped it in an iApp template so you can manage multiple FQDNs from the BIG-IP GUI without ever touching the CLI.

What is an iCall Script?

For those not familiar, iCall scripts are to the F5 BIG-IP management plane what iRules are to the data plane. They’re Tcl scripts that run in response to a trigger.  A trigger can be an event, a log message, a condition, or a time interval. In this case, I’m using a periodic iCall handler to fire the script on a configurable interval (every 30 seconds by default). The script then resolves the FQDN, compares the results to what GTM currently has configured, and updates as needed.

The iCall Script

The script accepts four parameters via the iCall event context: a debug flag, the FQDN to resolve, the GTM datacenter to use on initial create, and the TCP port for the virtual-server destinations.

foreach {debug fqdn datacenter port} $tgt { break }

From there, it does three things in sequence: resolve, compare, and update.

Step 1: Resolve

if {[catch {exec /bin/nslookup $fqdn} rslt] &&<br>  ![regexp {FIPS mode or MD5} $rslt]} {<br>   ...<br>   return -code error $e<br>}<br>set new_ips [list]<br>foreach line [split $rslt "\n"] {<br>  if {[regexp {^Address:[ ]+([0-9.]+)$} $line junk ip]} {<br>    lappend new_ips $ip<br>  }<br>}

The script uses nslookup to resolve the FQDN and parses the output to build a list of A records.

Step 2: Port Normalization

This one caught me by surprise. TMSH normalizes well-known port numbers to their service name equivalents when it stores them. So if you configure port 443, TMSH stores it as https. If you configure port 80, TMSH stores it as http. This matters because on the next run, when the script reads back the current virtual-server configuration to compare it against the new DNS results, it would see https in the stored config but 443 in the new config and conclude (incorrectly) that something had changed, triggering a modify on every single run.

array set _wkp {80 http 443 https 8080 http-alt 21 ftp 22 ssh 23 telnet 25 smtp} set port_svc [expr {[info exists _wkp($port)] ? $_wkp($port) : $port}]

The fix is a simple lookup table. Before comparing or writing, the script converts the configured port number to the service name TMSH would use, so the comparison is apples-to-apples.

Step 3: Compare and Update

The script then checks whether the GTM server object already exists. If it doesn’t, it creates it. If it does, it reads back the current device IPs and virtual-server IPs, sorts both lists, and compares:

if {$cur_sorted eq $new_sorted && $cur_vs_sorted eq $new_sorted && $cur_vs_port eq $port_svc} {<br>  # Nothing changed, skip modify<br>  return -code ok<br>}

Only if there’s an actual difference does it issue a tmsh::modify. This prevents a perpetual update loop; the script is idempotent when DNS results are stable.

When an update IS needed, it rebuilds the entire virtual-servers block from scratch, with one virtual-server entry per resolved IP, named sequentially:

append vs_block "${svr}-[format {%02d} $vs_seq] { destination ${ip}:${port_svc} } "

So for an AWS ALB that resolves to three IPs, you’d end up with virtual-server entries named my-alb-us-east-1-elb-amazonaws-com-01, -02, and -03. If the ALB later drops to two IPs (or adds a fourth), the script handles it on the next poll interval.

One More Implementation Note

There’s an annoying quirk with embedding iCall script definitions in TMSH configuration. TMSH’s parser counts every bare { and } character when loading config, regardless of Tcl quoting context. This means you cannot use regexp patterns that contain literal curly braces inside an iCall script definition block — an unbalanced brace will cause the config to fail to load. The script uses [split] and [string trim] tokenization instead of regexp-based parsing anywhere that brace-containing patterns would otherwise be natural.

The iApp

Managing individual iCall handlers from the CLI works fine if you have one or two FQDNs to track. But if you have a fleet of backend services, which is common in cloud-hybrid environments, you want something you can manage from the GUI without opening a terminal.

The iApp template (dns-fqdn-generic-host.iapp) wraps the iCall script and handlers in a proper GUI interface. Deploy the iApp, fill in the table, and each row creates one periodic iCall handler pointing at the dns_fqdn_generic_host script. If you redeploy the iApp and remove a row, the corresponding handler is automatically cleaned up. No orphaned config.

The GUI table exposes five fields per FQDN entry:

Field Description
FQDN The fully-qualified domain name to resolve
Destination Port TCP port for GTM virtual-server destinations (default 443)
GTM Datacenter Dropdown populated from existing GTM datacenter objects
Poll Interval (sec) How often to check DNS (minimum 5, default 30)
Debug Logging Yes/No — logs resolved IPs and change decisions to local0.debug

The iApp also takes care of one additional detail: if the dns_fqdn_generic_host iCall script doesn’t already exist on the device, it creates it automatically on first deploy. The script is created with app-service none, which means it’s independent of the iApp service instance; so you won’t lose the script if you delete the iApp.

Because the iApp implementation runs in a Tcl 8.4 context (the compat-tcl8.4 iApp environment), the script avoids Tcl 8.5+ features like dict. All key-value access is done via array set, which is worth knowing if you need to modify the iApp implementation for your own environment.

Deployment

  1. Import the dns-fqdn-generic-host.iapp template via iApps > Templates > Import.
  2. Create a new Application Service from the template.
  3. Fill in the table with one row per FQDN you want to track.
  4. Click Finished.

That’s it. The iApp creates the iCall script (if needed) and one periodic handler per row. Within your first poll interval, the GTM generic-host server objects will be created or updated to reflect current DNS.

Things to Note

  • Health monitoring is your responsibility. This solution keeps the IP addresses in the GTM server object current. It does not configure health monitors for you. Make sure you have appropriate monitors attached to the GTM pool members so GTM can actually determine whether a given IP is healthy.
  • Poll interval vs. DNS TTL. If your FQDN has a very short TTL (common with AWS load balancers), set your poll interval accordingly. A 30-second interval is a reasonable default for most scenarios, but you can go lower (minimum 5 seconds) if needed.
  • GTM server naming. Dots in the FQDN are replaced with dashes to generate the GTM server object name. For a typical AWS ALB name like my-alb-1234567890.us-east-1.elb.amazonaws.com, the resulting GTM server object will have a long name. This is cosmetic but worth being aware of if you have naming conventions you’re trying to follow.
  • This runs on the management plane. iCall scripts run on the BIG-IP control plane, not the data plane. The DNS resolution and TMSH calls happen on the management CPU and do not affect data plane performance.
  • Tested on TMOS 21.1.x but should work on any supported older software versions.

The iCall script and iApp template will be available on the DevCentral GitHub repository. As always, if you have questions, improvements, or feedback, drop a comment below or DM me.

2 Likes