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.

Overview

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 www.f5.com, Nat provided the example shown below (updated to static namespace.)

    # Name = www f5 com
    set static::answer "0377777702663503636f6d00"
    # Address = 65.197.145.23
    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.

#!/usr/bin/python

__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: ')
else:
    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 fqdn2hex.py test.devcentral.local 172.16.31.100

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

172.16.31.100: ac101f64

C:\PycharmProjects\scripts>python fqdn2hex.py this.is.fun.com 192.168.50.250

this.is.fun.com, 04746869730269730366756e03636f6d00, \x04this\x02is\x03fun\x03com

192.168.50.250: 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 this.is.fun.com
    set static::nmb "04746869730269730366756e03636f6d00"
    # Type = A(2)IN(2)TTL(4)Len(2)
    set static::ans "00010001000151800004"
    # test.devcentral.local ip = 172.16.31.100
    set static::ipa "ac101f64"
    # this.is.fun.com ip = 192.168.50.250
    set static::ipb "0a32019d"
}
when CLIENT_DATA {
    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} ]
            drop
            UDP::respond $payload
        }
        "\x04this\x02is\x03fun\x03com" {
            set payload [binary format H* ${id}${static::hdr}${question}${static::nmb}${static::ans}${static::ipb} ]
            drop
            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:

C:\PycharmProjects\scripts>nslookup
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
Addresses:  172.16.31.100
          172.16.31.100

Looks like all is well!

Conclusion

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
No CommentsBe the first to comment