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.
I ended up here because I needed to get an accurate return of ns in python ignoring the cache and starting from random servers.
I got a lot of help from your answers to build what is for me, the most useful version.
It allows me to know directly if a site belongs to a domain parking or not (e.g. freespt.com => ['ns1.bodis.com.', 'ns2.bodis.com.'])
I hope my code will be useful to you!
import random
import sys
import dns.resolver
import dns.name
import dns.message
import dns.query
import dns.flags
import re
NAMESERVERS = {
'1.1.1.1': 'Cloudflare DNS',
'1.0.0.1': 'Cloudflare DNS',
'8.8.8.8': 'Google DNS',
'8.8.4.4': 'Google DNS',
'9.9.9.9': 'Quad9',
'149.112.112.112': 'Quad9',
'208.67.222.222': 'OpenDNS',
'208.67.220.220': 'OpenDNS',
'185.228.168.9': 'CleanBrowsing',
'185.228.169.9': 'CleanBrowsing',
'94.140.14.14': 'AdGuard DNS',
'94.140.15.15': 'AdGuard DNS',
}
def rand_nameserver():
"""
Choosing randomly one of the nameservers
"""
return random.choice(list(NAMESERVERS.keys()))
def query_authoritative_ns(domain):
"""
Dig recursively leaf to root and retrieve ns with cache bypassing.
That will NOT only give you the nameserver for the top-level domain !
It'll try sub-domains if they have NS records.
It'll tell you what the authoritative DNS server is for www.example.org and will not raise a dns.resolver.NoAnswer exception.
(quite similary to dig +trace)
"""
trace = '### Querying authoritative ns for {d}:\n'.format(d=domain)
result = None
resolver = dns.resolver.Resolver(configure=False)
name_server = rand_nameserver()
# Setting-up random nameservers
resolver.nameservers = [name_server]
# Defining Timeout and lifetime
# To not taking long time to skip to the next one records when it failed.
resolver.timeout = 5
resolver.lifetime = 5
trace += '[NS records] [{d}] Used nameserver for DNS NS query is {fns} ({ns})'.format(
d=domain,
fns=NAMESERVERS.get(name_server),
ns=name_server
)
ns = resolver.nameservers[0]
# branches
n = domain.split('.')
for i in range(len(n), 0, -1):
sub = '.'.join(n[i - 1:])
trace += '\n[NS records] Looking up %s on %s' % (sub, ns)
query = dns.message.make_query(sub, dns.rdatatype.NS)
try:
response = dns.query.udp(query, ns, port=53, timeout=5)
except Exception as e:
trace += '\n[NS records] [domain={d}] [subdomain={s}] Receive exception : {e}'.format(d=domain, s=sub, e=e)
continue
rcode = response.rcode()
if rcode != dns.rcode.NOERROR:
if rcode == dns.rcode.NXDOMAIN:
trace += '\n[NS records] {sub} does not exist.'.format(sub=sub)
else:
trace += '\n[NS records] Error {err}'.format(err=dns.rcode.to_text(rcode))
break
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:
try:
trace += '\n[NS records] Same server is authoritative for {sub}'.format(sub=sub)
except KeyError:
# Here, for '1337x.unblocked.team' it returns:
# "unblocked.team. 300 IN SOA ns1.koaladns.com. admin.unblocked.team. 2021070507 86400 10800 604800 300"
return (rrset.to_text().split(' ')[4::11], trace)
elif rr.rdtype == dns.rdatatype.A:
try:
ns = rr.items[0].address
trace += '\n[NS records] Glue record for {sub}: {gl}'.format(sub=rr.name, gl=ns)
except KeyError:
# {<DNS IN A rdata: 103.224.212.63>: None}
# Glue Record with no data
trace += '\n[NS records] Glue record for {sub} contains None data !'.format(sub=rr.name)
# [<DNS ns18.above.com. IN A RRset: [<103.224.212.63>]>, <DNS ns17.above.com. IN A RRset: [<103.224.182.63>]>]
# ['ns17.above.com.','3600','IN','A','103.224.182.63','ns18.above.com.','3600','IN','A','103.224.212.63']
# And you extract correct ns by jumping arround the list
return (' '.join([e.to_text() for e in rrset]).split(' ')[0::5], trace)
elif rr.rdtype == dns.rdatatype.NS:
authority = rr.target
try:
if sys.version_info.major == 3:
ns = resolver.resolve(authority).rrset[0].to_text()
else:
ns = resolver.query(authority).rrset[0].to_text()
trace += '\n[NS records] {ns} ({ns_ip}) is authoritative for {sub}; ttl {ttl}'.format(ns=authority, ns_ip=ns, sub=sub, ttl=rrset.ttl)
except dns.resolver.NoAnswer:
trace += '\n[NS records] Got no answer querying {ns}'.format(ns=authority)
continue
except dns.resolver.NoNameservers:
trace += '\n[NS records] All nameservers failed to answer the query {ns}. Retrying with another nameserver.'.format(ns=authority)
return query_authoritative_ns(domain)
except dns.resolver.NXDOMAIN:
# 1337x.full-hd-torrent.net
trace += '\n[NS records] {ns} does not exist'.format(ns=authority)
continue
except (dns.resolver.Timeout, dns.exception.Timeout):
trace += '\n[NS records] [{d}] Timeout while querying {ns}, retrying with another nameserver.'.format(d=domain, ns=authority)
return query_authoritative_ns(domain)
result = rrset.to_text()
else:
# IPv6 glue records etc
pass
if result is not None:
# Here, rrset can look like:
# <DNS fp5u7c.top. IN NS RRset: [<justin.ns.cloudflare.com.>, <dora.ns.cloudflare.com.>]>
# <DNS bypassed.works.prx2.unblocksites.co. IN NS RRset: [<ns2.parklogic.com.>, <ns1.parklogic.com.>]>
return (re.split(r'NS |\n', result)[1::2], trace)
return ([], trace)
domain = 'freespt.com'
ns_querying = query_authoritative_ns(domain)
trace = '### Results for ns query_authoritative_ns("{d}"): {ns}\n{trace}'.format(
d=domain,
ns=ns_querying[0],
trace=ns_querying[1]
)
print(trace)
This produces
### Results for ns query_authoritative_ns("freespt.com"): ['ns1.bodis.com.', 'ns2.bodis.com.']
### Querying authoritative ns for freespt.com:
[NS records] [freespt.com] Used nameserver for DNS NS query is Quad9 (149.112.112.112)
[NS records] Looking up com on 149.112.112.112
[NS records] m.gtld-servers.net. (192.55.83.30) is authoritative for com; ttl 41016
[NS records] a.gtld-servers.net. (192.5.6.30) is authoritative for com; ttl 41016
[NS records] b.gtld-servers.net. (192.33.14.30) is authoritative for com; ttl 41016
[NS records] j.gtld-servers.net. (192.48.79.30) is authoritative for com; ttl 41016
[NS records] i.gtld-servers.net. (192.43.172.30) is authoritative for com; ttl 41016
[NS records] c.gtld-servers.net. (192.26.92.30) is authoritative for com; ttl 41016
[NS records] f.gtld-servers.net. (192.35.51.30) is authoritative for com; ttl 41016
[NS records] l.gtld-servers.net. (192.41.162.30) is authoritative for com; ttl 41016
[NS records] g.gtld-servers.net. (192.42.93.30) is authoritative for com; ttl 41016
[NS records] h.gtld-servers.net. (192.54.112.30) is authoritative for com; ttl 41016
[NS records] d.gtld-servers.net. (192.31.80.30) is authoritative for com; ttl 41016
[NS records] e.gtld-servers.net. (192.12.94.30) is authoritative for com; ttl 41016
[NS records] k.gtld-servers.net. (192.52.178.30) is authoritative for com; ttl 41016
[NS records] Looking up freespt.com on 192.52.178.30
[NS records] ns1.bodis.com. (199.59.242.141) is authoritative for freespt.com; ttl 172800
[NS records] ns2.bodis.com. (199.59.242.142) is authoritative for freespt.com; ttl 172800
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With