Pinhole/Pinpoint DNS

Resident superman and F5er Joel Moses dropped a solution in the wiki several weeks back utilizing an iRule to intercept dns requests and respond with different answers than the name servers would provide. As he states in the wiki entry, one of the use cases for this is to mimic the static host functionality in the BIG-IP Access Policy Manager. This is useful for platforms like iOS that don't support the modification of their internal host files, as well as preventing the need for OSX users from needing to manage their host files manually. I had a need for exactly this use case in my development environment, but a couple things led me to a slightly different approach. One, I don't have the DNS Features license on my BIG-IP Edge Gateway device. Two, the solution Joel crafted is far more robust than I needed. Granted, the DNS services module is far more efficient than my solution as it is completely built-in to TMOS, but for the very limited traffic and clientbase I'll have for this temporary solution, an iRule with binary manipulation makes sense.


You can front any dns solution with an iRule like this, but in this case, I created a udp virtual server on port 53 and attached the iRule and the pool of dns services for those requests not matched in the iRule. Then in my APM network access configuration, I make the dns server the virtual I just created.

The iRule

If you're only going to respond from the iRule with one IP address, F5er Nat (natty76 in the community) posted a pre-CMP version in the wiki quite a while back. This formed the foundation of the iRule I used, just changing the names, ips, and updating for CMP by using the static namespace instead of global variables. The mad science in this iRule is getting the fqdn and the ip in the right format for binary conversion. For, Nat provided the example shown below (updated to static namespace.)

    # Name = www f5 com
    set static::answer "0377777702663503636f6d00"
    # Address =
    set static::answer "${static::answer}41c59117"

Unpacking that a little, the characters and numbers are converted to hex. The IP is simple, just a by-octet conversion from decimal to hexadecimal. For the name, the dns field format requires the character number in advance of the characters themselves, so in hex, www is represented:

len characters - 03 77 77 77 and with spaces removed, 03777777.

This is repeated for each part of the fqdn, and when it is finished, the string needs to be padded with a double zero. If you don't like to make your eyes bleed converting ascii to hex, I wrote a python script to do this for you. Just supply an fqdn and an IP for that name, and it'll output the strings you need for the iRule.


__author__ = 'rahm'

def fqdn2hex(fqdn):
    fqdn_split = fqdn.split('.')
    hexstring = ''
    matchstring = ''
    for x in fqdn_split:
        hexstring += hex(len(x))[2:].zfill(2)
        hexstring += x.encode('hex')
        matchstring += '\\x' + str(len(x)).zfill(2)
        matchstring += x

    hexstring += '00'
    return hexstring, matchstring

def ip2hex(ip):
    ip = ip.split('.')
    ip = ''.join((hex(int(i))[2:].zfill(2) for i in ip))
    return ip

import sys

if len(sys.argv) != 3:
    fqdn = raw_input('Enter the FQDN you want to convert to a hex string for DNS response: ')
    ip = raw_input('Enter the IP you want to convert to a hex string for DNS response: ')
    fqdn = sys.argv[1]
    ip = sys.argv[2]

h_fqdn, m_fqdn = fqdn2hex(fqdn)
h_ip = ip2hex(ip)

print "\n\n%s, %s, %s" % (fqdn, h_fqdn, m_fqdn)
print "\n%s: %s" % (ip, h_ip)

This script when run results in the following output:

C:\PycharmProjects\scripts>python test.devcentral.local

test.devcentral.local, 04746573740a64657663656e7472616c056c6f63616c00, \x04test\x10devcentral\x05local ac101f64

C:\PycharmProjects\scripts>python, 04746869730269730366756e03636f6d00, \x04this\x02is\x03fun\x03com c0a832fa

Now that the data I need to build the iRule is properly formatted, here's the iRule:

when RULE_INIT  {
    # Header generation (in hexadecimal)
    # qr(1) opcode(0000) AA(1) TC(0) RD(1) RA(1) Z(000) RCODE(0000) Question(0001) Answer(0001) No DNS(0000) No Addition(0000)
    set static::hdr "85800001000100000000"
    # name_a test.devcentral.local
    set static::nma "04746573740a64657663656e7472616c056c6f63616c00"
    # name_b
    set static::nmb "04746869730269730366756e03636f6d00"
    # Type = A(2)IN(2)TTL(4)Len(2)
    set static::ans "00010001000151800004"
    # test.devcentral.local ip =
    set static::ipa "ac101f64"
    # ip =
    set static::ipb "0a32019d"
    binary scan [UDP::payload] H4@12A*@12H* id dname question
    set dname [string tolower [getfield $dname \x00 1 ] ]
    switch -glob $dname {
        "\x04test\x10devcentral\x05local" {
            set payload [binary format H* ${id}${static::hdr}${question}${static::nma}${static::ans}${static::ipa} ]
            UDP::respond $payload
        "\x04this\x02is\x03fun\x03com" {
            set payload [binary format H* ${id}${static::hdr}${question}${static::nmb}${static::ans}${static::ipb} ]
            UDP::respond $payload
        default {
            #log local0. "does not match"

To add new names to the ones I've already included, I would add another static::nm_ and static::ip_ variable with the output from the script for those values, and then duplicate one of the switch conditions, updating that condition with the appropriate match string from the script, then update the variables to the newly created ones. After getting everything setup, it was time to test:

DNS request timed out.
    timeout was 2 seconds.
Default Server:  UnKnown
Address:  x.x.x.x

> server x.x.x.x
Default Server:  [x.x.x.x]
Address:  x.x.x.x

> test.devcentral.local
Server:  [x.x.x.x]
Address:  x.x.x.x

Name:    test.devcentral.local

Looks like all is well!


Whether you go with a more robust and production-ready solution like Joel's, or meet a quick temporary need with a solution like this, it's great to have the Swiss-army knife toolset that BIG-IP and iRules provide.

Technorati Tags: iRules,dns,pinhole dns,python
Published Jan 02, 2014
Version 1.0

Was this article helpful?

No CommentsBe the first to comment