How can I find the authoritative DNS server for a domain using dnspython?
Question
As part of a tool I'm writing I want to have a diagnostic that will tell the user whether they have configured their domain's DNS correctly for a particular service. I want to query the authoritative DNS server for their domain so that I can bypass any cached results.
Solution
Here's my attempt at this. It uses the system's standard DNS server for looking up the root server for the top-level domain and for resolving the names of the various DNS servers along the chain, which I think is appropriate because those names would presumably change very infrequently.
import dns
import dns.name
import dns.query
import dns.resolver
def get_authoritative_nameserver(domain, log=lambda msg: None):
n = dns.name.from_text(domain)
depth = 2
default = dns.resolver.get_default_resolver()
nameserver = default.nameservers[0]
last = False
while not last:
s = n.split(depth)
last = s[0].to_unicode() == u'@'
sub = s[1]
log('Looking up %s on %s' % (sub, nameserver))
query = dns.message.make_query(sub, dns.rdatatype.NS)
response = dns.query.udp(query, nameserver)
rcode = response.rcode()
if rcode != dns.rcode.NOERROR:
if rcode == dns.rcode.NXDOMAIN:
raise Exception('%s does not exist.' % sub)
else:
raise Exception('Error %s' % dns.rcode.to_text(rcode))
rrset = None
if len(response.authority) > 0:
rrset = response.authority[0]
else:
rrset = response.answer[0]
rr = rrset[0]
if rr.rdtype == dns.rdatatype.SOA:
log('Same server is authoritative for %s' % sub)
else:
authority = rr.target
log('%s is authoritative for %s' % (authority, sub))
nameserver = default.query(authority).rrset[0].to_text()
depth += 1
return nameserver
import sys
def log(msg):
print msg
print get_authoritative_nameserver(sys.argv[1], log)
Here's some sample output:
Looking up com. on 192.168.255.10
l.gtld-servers.net. is authoritative for com.
Looking up stackoverflow.com. on 192.41.162.30
ns1.p19.dynect.net. is authoritative for stackoverflow.com.
Looking up meta.stackoverflow.com. on 208.78.70.19
Same server is authoritative for meta.stackoverflow.com.
208.78.70.19
OTHER TIPS
I came across Jon Colverson's answer, and it helped me understand the dnspython module and how to process the results (I guess all DNS modules have the same twisty maze of class structure ...) I needed the TTL and the glue records, so I created my own adaptation. I am posting it here in case somebody would find it useful; I do not intend to compete with Jon Colverson's excellent answer, just fill in some additional blanks. The basic improvement is the use of name server information from the additional section of the answer, where available. I suppose a server could put something else than glue records in the additional section, so perhaps this should still be enhanced to properly correlate the information from the additional section with the information in the answer section. I also fetch and print all the name servers, not just the first one.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dns.query
import dns.resolver
from dns.exception import DNSException
def query_authoritative_ns (domain, log=lambda msg: None):
default = dns.resolver.get_default_resolver()
ns = default.nameservers[0]
n = domain.split('.')
for i in xrange(len(n), 0, -1):
sub = '.'.join(n[i-1:])
log('Looking up %s on %s' % (sub, ns))
query = dns.message.make_query(sub, dns.rdatatype.NS)
response = dns.query.udp(query, ns)
rcode = response.rcode()
if rcode != dns.rcode.NOERROR:
if rcode == dns.rcode.NXDOMAIN:
raise Exception('%s does not exist.' % (sub))
else:
raise Exception('Error %s' % (dns.rcode.to_text(rcode)))
if len(response.authority) > 0:
rrsets = response.authority
elif len(response.additional) > 0:
rrsets = [response.additional]
else:
rrsets = response.answer
# Handle all RRsets, not just the first one
for rrset in rrsets:
for rr in rrset:
if rr.rdtype == dns.rdatatype.SOA:
log('Same server is authoritative for %s' % (sub))
elif rr.rdtype == dns.rdatatype.A:
ns = rr.items[0].address
log('Glue record for %s: %s' % (rr.name, ns))
elif rr.rdtype == dns.rdatatype.NS:
authority = rr.target
ns = default.query(authority).rrset[0].to_text()
log('%s [%s] is authoritative for %s; ttl %i' %
(authority, ns, sub, rrset.ttl))
result = rrset
else:
# IPv6 glue records etc
#log('Ignoring %s' % (rr))
pass
return result
import sys
def log (msg):
sys.stderr.write(msg + u'\n')
for s in sys.argv[1:]:
print query_authoritative_ns (s, log)
The other examples are fine but overly complex if you need just the nameservers. Example from http://c0deman.wordpress.com/2014/06/17/find-nameservers-of-domain-name-python/ :
import dns.resolver
domain = 'google.com'
answers = dns.resolver.query(domain,'NS')
for server in answers:
print server
Im pretty sure this would do it.
import dns.resolver
domain = 'co.uk'
response = dns.resolver.query(domain, 'SOA')
if response.rrset is not None:
print response.rrset
You could of course cleanup the response
import dns.resolver
import re
domain = 'co.uk'
response = dns.resolver.query(domain, 'SOA')
if response.rrset is not None:
pattern= r'(%s)\.\s(\d{1,})\s(\w+)\sSOA\s(.*?)\.\s(.*?)\.\s(\d{1,})\s(\d{1,})\s(\d{1,})\s(\d{1,})\s(\d{1,})' % domain
match = re.match(pattern, str(response.rrset))
m_name, ttl, class_, ns, email, serial, refresh, retry, expiry, minim = match.groups()
output ='''
Main Name In Zone: {a},
Cache TTL: {b},
Class: {c},
Authoritive NS: {d},
Email Address: {e},
Last Change: {f},
Retry In Secs: {g},
Expiry: {h},
Slave Cache In Sec: {i}
'''.format(a = m_name, b = ttl, c = class_, d = ns, e = str(email).replace('\\', ''), f = serial, g = retry, h = expiry, i = minim)
print output
This produces
Main Name In Zone: co.uk,
Cache TTL: 600,
Class: IN,
Authoritive NS: dns1.nic.uk,
Email Address: hostmaster.nominet.org.uk,
Last Change: 1305857394,
Retry In Secs: 300,
Expiry: 2419200,
Slave Cache In Sec: 10800