tags:

views:

92

answers:

1

I have implemented a simple DNS server. It simply responds with a TXT record. I am hosting the script as the NS server for example.com. The NS server is x.y.z.k. It works fine when I issue something like:

dig demo.example.com @x.y.z.k

; <<>> DiG 9.3.6-P1-RedHat-9.3.6-4.P1.el5_4.1 <<>> demo.example.com @x.y.z.k ;; global options: printcmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10028 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 2

;; QUESTION SECTION: ;demo.example.com. IN A

;; ANSWER SECTION: demo.example.com. 1000 IN TXT "test message"

but it does not work when I ask the very same question from one of the public name servers (For example Sun's DNS at 4.2.2.1):

dig demo.example.com @4.2.2.1 (I get the same thing when I issue dig demo.example.com @4.2.2.1 TXT)

; <<>> DiG 9.3.6-P1-RedHat-9.3.6-4.P1.el5_4.1 <<>> demo.example.com ;; global options: printcmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 10905 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION: ;demo.example.com. IN A

;; Query time: 5837 msec ;; SERVER: 172.16.1.1#53(172.16.1.1) ;; WHEN: Tue Jul 20 04:01:27 2010 ;; MSG SIZE rcvd: 75

Do you guys have any idea what is wrong? Interestingly if I change the response type to something like CNAME, instead of TXT, it works fine.

def dns_respond(resp, q, data_type):
    rrset = dns.rrset.from_text(q.name, 1000,
           dns.rdataclass.IN, dns.rdatatype.TXT, 'test message')
    resp.answer.append(rrset)
    return resp

def dns_ok(resp, q, data = None, msg = ''):
    return dns_respond(resp = resp, q = q, data_type = 'OK')

def dns_error(resp, q):
    return dns_respond(resp = resp, q = q, data_type = 'Error')

def requestHandler(address, message):
    resp = None
    message_id = ord(message[0]) * 256 + ord(message[1])
    logging.debug('msg id = ' + str(message_id))
    if message_id in serving_ids:
        # the request is already taken, drop this message
        logging.debug('I am already serving this request.')
        return
    serving_ids.append(message_id)
    msg = dns.message.from_wire(message)
    op = msg.opcode()
    if op == 0:
        # standard and inverse query
        qs = msg.question
        if len(qs) > 0:
            q = qs[0]
            logging.debug('request is ' + str(q))
            if q.rdtype == dns.rdatatype.A:
                resp = std_qry(msg)
            else:
                # not implemented
                #resp = std_qry(msg)
                resp = make_response(qry=msg, RCODE=4)   
    else:
        # not implemented
        resp = make_response(qry=msg, RCODE=4)   

    if resp:
        s.sendto(resp.to_wire(), address)

def std_qry(msg):
    qs = msg.question
    logging.debug(str(len(qs)) + ' questions.')

    answers = []
    nxdomain = False
    for q in qs:
        resp = make_response(qry=msg)
        logging.critical('Sending...')
        return dns_ok(resp, q)

def make_response(qry=None, id=None, RCODE=0):
    if qry is None and id is None:
        raise Exception, 'bad use of make_response'
    if qry is None:
        resp = dns.message.Message(id)
        # QR = 1
        resp.flags |= dns.flags.QR
        if RCODE != 1:
            raise Exception, 'bad use of make_response'
    else:
        resp = dns.message.make_response(qry)
    #resp.flags |= dns.flags.AA
    resp.flags |= dns.flags.RA
    resp.set_rcode(RCODE)
    return resp
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', 53))
logging.debug('binded to UDP port 53.')
serving_ids = []

while True:
    logging.debug('waiting requests.')
    message, address = s.recvfrom(1024)
    logging.debug('serving a request.')
    requestHandler(address, message)
A: 

I can see a few real bugs that'll stop this working:

  1. You're returning a TXT record to an A record query - this is probably the show stopper.
  2. You should be returning the AA flag (authoritative answer) instead of RA
  3. Your headers say there are two answers in the authority and additional sections, but there aren't
  4. You're not removing served IDs from the list once they've actually been served

In addition there are a few protocol-related issues that need fixing:

  1. You need to be able to return NS records and SOA records when asked, or your delegation may not work reliably
  2. Query matching should use the whole (source IP, source port, queryID) tuple into account, not just (queryID)
  3. If the question name is in the right domain, but not the right subdomain you should return NXDOMAIN (rcode = 3)
  4. If the question name matches the right subdomain but the wrong record type you should return NOERROR (rcode = 0) instead of NOTIMPL
  5. If the question name isn't a part of the right domain at all you should return REFUSED (rcode = 5)
Alnitak