DNS NXDOMAIN Handler - No More Non-Existent Domain
Problem this snippet solves:
With this rules, BIGIP can convert a “non-exist domain” DNS query result to a "normal" response to client. That will help if client type the wrong domain name (ie. if a client types "wwww.f5.com" for www.f5.com, when the request send to a load balanced DNS cache server, it will return an error message of non-exist domain (NXDOMAIN), then the plugin of browser will redirect it to an search engine or other site. After apply this rules to the BIGIP that load balances the DNS cache servers, BIGIP will replace the Cache DNS’s error message and turn it to a specified IP address like "wwww.f5.com=200.100.4.10" in the sample rule. That the 200.100.4.10 may be portal of the service provider. Or a friendly error page that indicate the type error.
Note: Rewriting non-existent domain DNS responses can introduce serious security issues for any domain which is resolved in such a manner. It may also break SPAM blacklisting. For more information, see http://www.wired.com/threatlevel/2008/04/isps-error-page
The following iRule requires BIG-IP version 11 with a DNS profile attached to the virtual server.
when DNS_RESPONSE { if { [DNS::ptype] == "NXDOMAIN" } { DNS::answer insert "[DNS::question name]. 60 [DNS::question class] [DNS::question type] 200.100.4.10" } }
Code :
# The following iRule can be used if you are running a BIG-IP version prior to v11. when RULE_INIT { set ::too_many_dns_parts 10 set ::header_without_id [binary format S5 {0x8180 0x0001 0x0001 0x0000 0x0000}] #predefined fixed header # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | ID | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # |QR| Opcode |AA|TC|RD|RA| Z | RCODE | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | QDCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | ANCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | NSCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | ARCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # #opcode = QUERY, rcode = NOERROR #header flags: response, auth. answer, want recursion, recursion avail. #questions = 1, answers = 1, authority records = 0, additional = 0 set ::answerpart [binary format S6c4 {0xC00C 0x0001 0x0001 0x0000 0x0D1B 0x0004} {200 100 4 10}] #predefined Fixed Answer section #Name: same as qestion #Type: Host address #Class: INET #Time to live: 55 minutes, 55seconds #Data length: 4 #Addr: 200.100.4.10, you can modify your own IP address here #Data Structure # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Name | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Answer Type | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Class | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Time to live part 1 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Time to live part 2 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | IP Address part 1 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | IP Address part 2 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ } when CLIENT_ACCEPTED { #check QID, if query ID is 0, that means it's a illegal request. #qid 0 will consume server a lot of resource. binary scan [ string range [UDP::payload] 0 1 ] S sflags set qid [expr $sflags & 0x00ff] #log "the request ID is $qid" if {$qid == 00 }{ STATS::incr dns_request_stat dns_request } } when SERVER_DATA { #check rcode binary scan [ string range [UDP::payload] 2 3 ] S sflags set rcode [expr $sflags & 0x000f] if {$rcode == 3 }{ # skip the DNS header, jump to the QNAME part of first QUESTION # byte contains the first part length binary scan [string range [UDP::payload] 12 13 ] c foo # make the byte an unsigned integer set byte [expr $foo & 0xff] # initialize our posisition in the QNAME parsing and the text QNAME set offset 12 # $i is a sanity check so this logic won't spin on invalid QNAMEs set i 0 ############# /extract QNAME from QUESTION header ############# while {$byte > 0 && $i < 10} { # grab a part and put it in our text QNAME section set offset [expr $offset + $byte + 1] # grab the length of the next part, and make it an unsigned integer set byte [string range [UDP::payload] $offset [expr $offset + 1]] binary scan $byte c foo set byte [expr $foo & 0xff] incr i } # increment offset past the final part so it points at the QTYPE field incr offset ############# extract QTYPE from QUESTION header ############# # grab the next 2 bytes that represent the QTYPE binary scan [string range [UDP::payload] $offset [expr $offset + 2]] S qtype # see if the QTYPE is 0x0001 (TYPE_AAAA), if it's a A query, then replace the content if {$qtype == 0x0001} { #Pack the respond packet #first a2 is id #second a* is predefined header without id #third a* is question part extract from return packet #fourth a* is predefined answer section #if we use UDP::respond here, we can let a LTM response DNS request just by rules #UDP::respond [binary format a2a*a*a* [string range [UDP::payload] 0 1] $::header_without_id [string range [UDP::payload] 12 [expr $offset+3]] $::answerpart] #UDP::drop UDP::payload replace 0 0 [binary format a2a*a*a* [string range [UDP::payload] 0 1] $::header_without_id [string range [UDP::payload] 12 [expr $offset+3]] $::answerpart] } } } # iRule Source when RULE_INIT { set ::my_address {200 100 4 10} #Addr: 200.100.4.10, you can modify your own IP address here set ::header_without_id [binary format S5 {0x8180 0x0001 0x0001 0x0000 0x0000}] #predefined fixed header # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | ID | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # |QR| Opcode |AA|TC|RD|RA| Z | RCODE | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | QDCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | ANCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | NSCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | ARCOUNT | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # #opcode = QUERY, rcode = NOERROR #header flags: response, auth. answer, want recursion, recursion avail. #questions = 1, answers = 1, authority records = 0, additional = 0 set ::answerpart [binary format S6c4 {0xC00C 0x0001 0x0001 0x0000 0x0D1B 0x0004} $::my_address] #predefined Fixed Answer section #Name: same as qestion #Type: Host address #Class: INET #Time to live: 55 minutes, 55seconds #Data length: 4 #IPv4 Addr: xxx.xxx.xxx.xxx #Data Structure # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Name | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Answer Type | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Class | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Time to live part 1 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | Time to live part 2 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | IP Address part 1 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | IP Address part 2 | # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ } when SERVER_DATA { #check rcode binary scan [UDP::payload] @2S sflags set rcode [expr $sflags & 0x000f] #rcode = 3 Name Error (domain name referenced in the query does not exist. if {$rcode == 3 }{ # skip the DNS header, jump to the QNAME part of first QUESTION # byte contains the first part length binary scan [UDP::payload] @12c foo # make the byte an unsigned integer set byte [expr ($foo+0x100)%0x100] # initialize our posisition in the QNAME parsing and the text QNAME set offset 12 # $i is a sanity check so this logic won't spin on invalid QNAMEs set i 0 ############# /extract QNAME from QUESTION header ############# while {$byte > 0 && $i < 10} { # grab a part and put it in our text QNAME section set offset [expr $offset + $byte + 1] # grab the length of the next part, and make it an unsigned integer binary scan [UDP::payload] @${offset}c foo set byte [expr ($foo+0x100)%0x100 ] incr i } # increment offset past the final part so it points at the QTYPE field incr offset ############# extract QTYPE from QUESTION header ############# # grab the next 2 bytes that represent the QTYPE binary scan [UDP::payload] @${offset}S qtype # see if the QTYPE is 0x0001 (TYPE_AAAA), if it's a A query, then replace the content if {$qtype == 0x0001} { #Pack the respond packet #keep original query id #replace header with predefined header without id (answer part and remove NS/AR part #replace the rest of packet after query section with predefined answer section UDP::payload replace 2 10 $::header_without_id incr offset 4 UDP::payload replace $offset [expr [UDP::payload length] - $offset ] $::answerpart } } }