I released an article a while back on the DNS services architecture now built in to BIG-IP, as well as a solution article that showed some fancy DNS tricks utilizing the architecture to black hole malicious DNS requests. What might be lost in those articles is the difference maker the dns profile makes in using iRules to return DNS responses. I was working on a little project earlier this week and the VM I am hosting requires a single DNS response to a single question. The problem is that I don't have the particular fqdn defined in an external or internal name server. Adding the fqdn to either is problematic:

  1. Adding the FQDN to the external name server would require adding an internal view to bind, which adds risk and complexity.dns_hex
  2. Adding the FQDN to the internal name server would require adding external zones to my internal server, which adds unnecessary complexity.

So as I wasn't going down either of those roads...I had to find an alternate solution. Thankfully, I have BIG-IP VE at my disposal, and therefore, iRules. The DNS profile exposes in iRules the DNS:: namespace, and with it, native decodes for all the fields in requests/responses. The iRule, with the DNS namespace, is trivial:

when DNS_REQUEST {
  if { [IP::addr [IP::remote_addr] equals 192.168.1.0/24] && ([DNS::question name] equals "www.mytest.com") } {
    DNS::answer insert "[DNS::question name]. 111 [DNS::question class] [DNS::question type] 192.168.1.200"
    DNS::return
  } else ( discard }
}

However, after trying to save the iRule, I realized I'm not licensed for dns services on my BIG-IP VE, so that path wouldn't work. So I took a packet capture of some local dns traffic on my desktop and started mapping the fields and preparing to settle in for some serious binary scan/format work, but then remembered there were already some iRules out in the codeshare that I though might get me started. Natty76's Fast DNS 2 seemed to fit the bill. So with just a little customization, I was up and running with no issues. But notice the amount of work required (both by author and by system resources) to make this happen when compared with the above iRule.

when RULE_INIT priority 1 {
    # Domain Name = www mytest com
    set static::domain "www.mytest.com"    
    # IP address in answer section (type A)
    set static::answer_string "192.168.1.200"
}
when RULE_INIT {
    # Header generation (in hexadecimal)
    # qr(1) opcode(0000) AA(1) TC(0) RD(1) RA(1) Z(000) RCODE(0000)
    set static::header "8580"
    
    # 1 question, X answer, 0 NS, 0 Addition
    set static::answer_record [format %04x [llength $static::answer_string]]
    set static::header "${static::header}0001${static::answer_record}00000000"
    
    # generate domain binary string
    set static::domainhex ""
    foreach static::d [split $static::domain "."] {
        set static::l [string length $static::d]
        scan $static::l %d static::h
        append static::domainhex [format %02x $static::h]
        foreach static::n [split $static::d ""] {
            scan $static::n %c static::h
            append static::domainhex [format %02x $static::h]
        }
    }
    set static::domainbin [binary format H* $static::domainhex]
    append static::domainhex 00

    set static::answerhead $static::domainhex
    # Type = A
    set static::answerhead "${static::answerhead}0001"
    # Class = IN
    set static::answerhead "${static::answerhead}0001"
    # TTL = 1 day
    set static::answerhead "${static::answerhead}00015180"
    # Data length = 4
    set static::answerhead "${static::answerhead}0004"

    set static::answer ""
    foreach static::a $static::answer_string {
        scan $static::a "%d.%d.%d.%d" a b c d
        append static::answer "${static::answerhead}[format %02x%02x%02x%02x $a $b $c $d]"
    }
}
when CLIENT_DATA {
  if { [IP::addr [IP::client_addr] equals 192.168.1.0/22] } {
    
    binary scan [UDP::payload] H4@12A*@12H* id dname question
    set dname [string tolower [getfield $dname \x00 1 ] ]
    
    switch -glob $dname \
      $static::domainbin {
        #log local0. "match"
        set hex ${id}${static::header}${question}${static::answer}
        set payload [binary format H* $hex ]
        # to drop only a packet and keep UDP connection, use UDP::drop
        drop
        UDP::respond $payload
      } \
      default {
        #log local0. "does not match"
      }
  } else { discard }
}

No native decode means you have to do all the decoding work of the protocol yourself. I don't get to share "from the trenches" as much as I used to, but this was too good a demonstration to pass up.