DNS Resolution with the RESOLVER::name_lookup Command
When 15.1 released, we deprecated the RESOLV::lookup command. It still works, but there is no commitment that it will continue to work, so now is a good time to begin migrating your code away from its use. The replacement command for the lookup itself is RESOLVER::name_lookup, but you’ll want to put RESOLVER::summarize in your toolbox as well, unless you like to flex your binary scan skills for your friends. In this quick tip, you’ll learn how to use the new commands.
The Syntax
RESOLVER::name_lookup
RESOLVER::name_lookup <net_resolver_name> <name> <type>
The command takes three arguments. The net dns-resolver should be configured via tmsh. Configuration details are in the tmsh reference, but the example configuration I used in my test box is below. I was a day-ago old when I learned about net dns-resolvers. The breadth of BIG-IP functionality continues to amaze me 18 years into my F5 experiences.
net dns-resolver r1 { forward-zones { . { nameservers { 8.8.8.8:domain { } 9.9.9.9:domain { } } } } route-domain 0 }
The second argument is name, and this is what you are querying, like an IP or FQDN.
The final argument is type, and the record types that are supported by this command are a, aaaa, txt, mx, ptr, srv, and naptr.
RESOLVER::summarize
RESOLVER::summarize <dns_message>
This single argument here should be the result of the RESOLVER::name_lookup, which as I hinted to at the beginning of the article, is not a very human readable experience as shown in the example below.
00008180000100010000000001320231380332333002353407696E2D61646472046172706100000C0001C00C000C000100005C85002B127365727665722D35342D3233302D31382D32056F7264353101720A636C6F756466726F6E74036E657400
The return of calling this command is a summary of the DNS answer section as a list, only including the record types specified in the query.
Putting it All Together
My test box is a BIG-IP Virtual Edition running in VMWare Fusion on my laptop. I have a simple web application I can connect to through my VE to my Ubuntu server. See the diagram below.
I attached an iRule to that virtual, so that when I make a request via curl, the iRule will use the defined resolver to reach out to Google’s name servers for an answer. Here’s the iRule:
when CLIENT_ACCEPTED { set fqdn espn.com set result [RESOLVER::name_lookup "/Common/r1" $fqdn a] foreach rr [RESOLVER::summarize $result] { log local0.debug $rr log local0.debug [lindex $rr 4] } } ### Results ### Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : espn.com. 60 IN A 54.230.18.5V Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : 54.230.18.5 Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : espn.com. 60 IN A 54.230.18.2V Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : 54.230.18.2 Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : espn.com. 60 IN A 54.230.18.54 Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : 54.230.18.54 Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : espn.com. 60 IN A 54.230.18.38 Nov 18 12:17:26 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : 54.230.18.38
No issues with the a record, so let’s try a ptr record:
when CLIENT_ACCEPTED { set tst_ip 54.230.18.2 set result [RESOLVER::name_lookup "/Common/r1" $tst_ip ptr] foreach rr [RESOLVER::summarize $result] { log local0.debug $rr log local0.debug [lindex $rr 4] } } ### Results ###
But when this is run, there is no log, so result must be empty. Hmm...if I change tst_ip to be a properly formatted ptr record, what happens?
when CLIENT_ACCEPTED { set tst_ip 2.18.230.54.in-addr.arpa # Format the ptr record so the RESOLVER::name_lookup will work properly for a ptr lookup # set ptr [join [call lreverse [split $tst_ip .]] .].in-addr.arpa set result [RESOLVER::name_lookup "/Common/r1" $tst_ip ptr] foreach rr [RESOLVER::summarize $result] { log local0.debug $rr log local0.debug [lindex $rr 4] } } ### Results ### Nov 18 12:27:51 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : 2.18.230.54.in-addr.arpa. 20132 IN PTR server-54-230-18-2.ord51.r.cloudfront.net0( Nov 18 12:27:51 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : server-54-230-18-2.ord51.r.cloudfront.net
Voila! Odd that this is required, but at least we have a working solution. To make it more dynamic for using IP addresses you'll capture on the fly, we'll backport the lreverse command (in Tcl 8.5+) to iRules with a proc:
proc lreverse list { set res {} set i [llength $list] while {$i} { lappend res [lindex $list [incr i -1]] } set res }
And then modify the iRule to call that proc to reverse those octets and then build out the rest of string with the join command:
proc lreverse list { set res {} set i [llength $list] while {$i} { lappend res [lindex $list [incr i -1]] } set res } when CLIENT_ACCEPTED { set tst_ip 2.18.230.54.in-addr.arpa # Format the ptr record so the RESOLVER::name_lookup will work properly for a ptr lookup set ptr [join [call lreverse [split $tst_ip .]] .].in-addr.arpa set result [RESOLVER::name_lookup "/Common/r1" $ptr ptr] foreach rr [RESOLVER::summarize $result] { log local0.debug $rr log local0.debug [lindex $rr 4] } } ### Results ### Nov 18 12:27:51 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : 2.18.230.54.in-addr.arpa. 20132 IN PTR server-54-230-18-2.ord51.r.cloudfront.net0( Nov 18 12:27:51 ltm3 debug tmm[88844]: Rule /Common/resolver_demo : server-54-230-18-2.ord51.r.cloudfront.net
Taking It Further
Now that we have a working solution, let's refine it. I'm not happy that I have to have a different experience for PTR records than the rest of them. I've also been made aware (thanks, Paul!) that whereas lreverse does exactly what we want it to do, it's not necessarily the most performant means available to achieve the same result. First, let's rewrite the ipv4 reversal proc:
# Proc to reverse the octets for ipv4 PTR records proc resolv_ptr_v4 { addr_v4 } { if { ([scan $addr_v4 {%d.%d.%d.%d} a b c d] != 4) || ([llength [split $addr_v4 .]] != 4) } { return } else { return "$d.$c.$b.$a.in-addr.arpa" } }
This version uses the scan command and sets the octets directly to variables, then returns them all in the necessary string format. The conditional adds error checking for the provided address. I found in testing by adding a fifth octet that the scan itself was not enough to verify there are four and only four octets, so I added the split/list length check for good measure. Next, let's add a proc to receive the resolver, the query type, and the question from the main iRule logic. That way, every query type has a similar user experience from the iRule logic. Passing the resolver as well makes the proc more plug-n-play.
# Proc to make resolver::name_lookup queries proc resolv_look_up { net_resolver qtype qquestion } { if { $qtype eq "ptr" } { set qquestion [call resolv_ptr_v4 $qquestion] } set result [RESOLVER::name_lookup $net_resolver $qquestion $qtype] set summary [RESOLVER::summarize $result] if { [lindex $summary 0] eq "" } { # log local0.warn "DNS $qtype lookup for $qquestion failed." return } return $summary }
The query question remains the same for all record types except the PTR record, which results in a call to the other proc to reverse those ipv4 octets. Next the resolution is performed and then summarized, then as long as there is a summary, the results are returned to the calling iRule. Pretty simple solution to provide a functional fix for PTR lookups and the "same" experience for all records. Now that we have a solution, how do we use it? Well here's an iRule sample that allows you to test all the record types and enable/disable the testing in the RULE_INIT event.
when RULE_INIT { set static::enable_test 1 } when CLIENT_ACCEPTED { if { $static::enable_test } { array set records { a f5.com aaaa f5.com txt f5.com mx f5.com ptr 52.84.127.127 srv _sip._tcp.cisco.com naptr 4.4.2.2.3.3.5.6.8.1.4.4.e164.arpa } foreach {type question} [array get records] { set answers [call resolv_look_up "/Common/r1" $type $question] foreach answer $answers { if { $type eq "naptr" } { log local0. "Query type: $type, Question: $question, Answer: [lindex $answer end-1]" } else { log local0. "Query type: $type, Question: $question, Answer: [lindex $answer end]" } } } } }
Feel free to swap out all the sample question data with your own records; I used what I could fine for the SRV and NAPTR records to test. The results of making a request to the virtual server are shown below (multiple records of same time removed for brevity).
Query type: txt, Question: f5.com, Answer: MS=ms50853128 Query type: aaaa, Question: f5.com, Answer: 2604:e180:1047::ffff:6ba2:b09a Query type: naptr, Question: 4.4.2.2.3.3.5.6.8.1.4.4.e164.arpa, Answer: !^(.*)$!tel:\1! Query type: mx, Question: f5.com, Answer: mail13.f5.com Query type: a, Question: f5.com, Answer: 107.162.162.40 Query type: ptr, Question: 52.84.127.127, Answer: server-52-84-127-127.ord53.r.cloudfront.net Query type: srv, Question: _sip._tcp.cisco.com, Answer: vcsgw104.cisco.com
The basic flow here is to iterate through the array of query type/question data and call the proc for name resolution data to be returned. Once we have that data for the particular query, which can be one or more records, we iterate through that to log. For the real world, you might want only a single record returned from the proc, or just to look at a single record that was returned from the proc, but I'll leave that modification exercise to you. The full iRule with procs and sample code from this section is available as a gist on GitHub.
Conclusion
Sometimes it takes a little detective work to figure out the nuances of iRules commands. Questions or comments? Drop a line below!
- JRahmAdmin
Thanks for reading!
- PacochaNimbostratus
Are you using DNS Forwarders, or root hints? What version of Windows Server are you using for your DNS servers?
- JRahmAdmin
as shown at the top of the article, I'm configuring a resolver as a BIG-IP object and pointing to google and quad9 public resolvers on 8.8.8.8 and 9.9.9.9.
- rob_carrCirrocumulus
Hi ,
Any ideas how to trigger something like this lookup when a query matches a specific Wide IP?
- JRahmAdmin
can you expand the ask a little Rob, not sure I follow.
- rob_carrCirrocumulus
Hi Jason,
Your rule uses CLIENT_ACCEPTED, which would seem to imply that this iRule would be attached to a DNS listener. In my organization BIG-IP DNS listeners are the first stop for all client DNS queries, so attaching an iRule to the listener would lead to hundreds of thousands of evaluations per day that aren't really needed (only a subset of queries need this functionality).
Is there any reason why the DNS_REQUEST event couldn't be used (instead of CLIENT_ACCEPTED) and then the iRule associated with a specific Wide IP?
- JRahmAdmin
oh, gotcha. I tested on a local system without the dns license, which is why I used CLIENT_ACCEPTED. I don't see why DNS_REQUEST would be problematic, the two featured commands should work in that event.
- Fabrizio66Nimbostratus
Hello, thank you for explanation, however I didn't manage to convert RESOLVER::name_lookup in my RESOLV::lookup iRule.
Below what I have:
when HTTP_REQUEST {
set client_IP [IP::client_addr]
set client_hostname [RESOLV::lookup @127.0.0.1 -ptr $client_IP]
HTTP::header replace X-Forwarded-For "$client_IP"
HTTP::header replace X-Forwarded-Host "$client_hostname"
#log local0. "Received XFF Host from $client_hostname"
HTTP::header insert IS_SSL "ssl"
#log
# }
}Result
info tmm6[28816]: Rule /Common/incas-prod-pool_based_on_client_dns <HTTP_REQUEST>: Received XFF Host from - 10.8.4.42%5
Could you help me please? I upgraded from 14.1.2.8 to 15.1.5 and this irule doesn't work anymore, even if RESOLVER::name_lookup should be still supported with 15.x and 16.x.
Thanks
Regards.
- Fabrizio66NimbostratusI meant even if RESOLV::lookup should be still supported with 15.x and 16.x. I opened an F5 case but they still haven't reply. I hope you are able to help me. Thanks