Querying DNS-SEC validators

This basic example shows how to query validating resolver and evaluate answer.

Resolving step by step

For DNS queries, we need to initialize ldns resolver (covered in previous example).

# Create resolver
resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf")
resolver.set_dnssec(True)

# Custom resolver
if argc > 2:
   # Clear previous nameservers
   ns = resolver.pop_nameserver()
   while ns != None:
      ns = resolver.pop_nameserver()
   ip = ldns.ldns_rdf.new_frm_str(sys.argv[2], ldns.LDNS_RDF_TYPE_A)
   resolver.push_nameserver(ip)

Note the second line resolver.set_dnssec(), which enables DNSSEC OK bit in queries in order to get meaningful results.

As we have resolver initialized, we can start querying for domain names :

# Resolve DNS name
pkt = resolver.query(name, ldns.LDNS_RR_TYPE_A, ldns.LDNS_RR_CLASS_IN)
if pkt and pkt.answer():

Now we evaluate result, where two flags are crucial :

  • Return code
  • AD flag (authenticated)

When return code is SERVFAIL, it means that validating resolver marked requested name as bogus (or bad configuration).

AD flag is set if domain name is authenticated (secure) or false if it’s insecure.

Complete source code

#!/usr/bin/python
# -*- coding: utf-8 -*-
import ldns
import sys

debug = True

# Check args
argc = len(sys.argv)
name = "www.nic.cz"
if argc < 2:
   print "Usage:", sys.argv[0], "domain [resolver_addr]"
   sys.exit(1)
else:
   name = sys.argv[1]

# Create resolver
resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf")
resolver.set_dnssec(True)

# Custom resolver
if argc > 2:
   # Clear previous nameservers
   ns = resolver.pop_nameserver()
   while ns != None:
      ns = resolver.pop_nameserver()
   ip = ldns.ldns_rdf.new_frm_str(sys.argv[2], ldns.LDNS_RDF_TYPE_A)
   resolver.push_nameserver(ip)

# Resolve DNS name
pkt = resolver.query(name, ldns.LDNS_RR_TYPE_A, ldns.LDNS_RR_CLASS_IN)
if pkt and pkt.answer():

   # Debug
   if debug:
      print "NS returned:", pkt.get_rcode(), "(AA: %d AD: %d)" % ( pkt.ad(), pkt.ad() )

   # SERVFAIL indicated bogus name
   if pkt.get_rcode() is ldns.LDNS_RCODE_SERVFAIL:
      print name, "is bogus"

   # Check AD (Authenticated) bit
   if pkt.get_rcode() is ldns.LDNS_RCODE_NOERROR:
      if pkt.ad(): print name, "is secure"
      else:        print name, "is insecure"

Testing

In order to get meaningful results, you have to enter IP address of validating resolver or setup your own (see howto).

Execute ./example2.py with options domain name and resolver IP, example:

user@localhost# ./example2.py www.dnssec.cz 127.0.0.1 # Secure (Configured Unbound running on localhost)
user@localhost# ./example2.py www.rhybar.cz 127.0.0.1 # Bogus

Howto setup Unbound as validating resolver

Install Unbound according to instructions. Modify following options in unbound.conf (located in /etc or /usr/local/etc)/

Uncomment module-config and set validator before iterator.

module-config: "validator iterator"

Download DLV keys and update path in unbound.conf:

# DLV keys
# Download from http://ftp.isc.org/www/dlv/dlv.isc.org.key
dlv-anchor-file: "/usr/local/etc/unbound/dlv.isc.org.key"

Update trusted keys (.cz for example):

# Trusted keys
# For current key, see www.dnssec.cz
trusted-keys-file: "/usr/local/etc/unbound/trusted.key"

Now you should have well configured Unbound, so run it:

user@localhost# unbound -dv

Table Of Contents

Previous topic

Resolving the MX records

Next topic

High-level functions