dnspython Resolver

The socket module of the Python standard library provides basic functions for resolving hostnames (gethostbyname and gethostbyname_ex) as implemented by the C library. The resolver of the dnspython allows to bypass the C library and query DNS servers directly.

pip install dnspython

Constants

Before we start querying DNS servers it is useful to know about some constants and convenience functions dnspython provides. The dns.rdataclass module defines the DNS data clasess out of which only Internet IN is relevant (or has anyone ever seen CHAOS or HESIOD implemented???).


In [4]:
import dns.rdataclass
dns.rdataclass.IN


Out[4]:
1

The constants of the dns.rdatatype module are a lot more important as they list the types of records commonly served by DNS servers.


In [5]:
import dns.rdatatype
dns.rdatatype.A, dns.rdatatype.MX, dns.rdatatype.PTR, dns.rdatatype.SOA, dns.rdatatype.TXT


Out[5]:
(1, 15, 12, 6, 16)

Using the default resolvers

On Unix systems the file /etc/resolv.conf may list (on Linux up to three) nameservers that the C library functions shall query for resolving host names that are not found in /etc/hosts or other local databases like LDAP or NIS as defined in /etc/nsswitch.conf.

Unless one requires exact control which DNS server(s) to query, the dns.resolver.query() function (actually a wrapper for the more versatile dns.resolver.Resolver.query() method) is the right choice. It will simply use the servers and other settings configured in /etc/resolv.conf. The function dns.resolver.get_default_resolver() returns the details. Check the resolv.conf(5) manual page for more information.


In [6]:
import dns.resolver
dns.resolver.get_default_resolver().nameservers


Out[6]:
['10.1.2.1', '8.8.8.8', '8.8.4.4']

In [7]:
dns.resolver.get_default_resolver().domain


Out[7]:
<DNS name juenemann.local.>

In [8]:
dns.resolver.get_default_resolver().search


Out[8]:
[<DNS name juenemann.local.>, <DNS name juenemann.net.>]

In [9]:
dns.resolver.get_default_resolver().timeout


Out[9]:
2.0

Querying A records

The dns.resolver.query() function accepts a number of arguments but in most cases only the first one (qname) or two (rdtype) are required. Note how the returned value for an A query (the default) contains much more information than just the IP address.


In [10]:
answers = dns.resolver.query('www.google.com')
answers


Out[10]:
<dns.resolver.Answer at 0x7f6e7c526250>

In [11]:
answers.canonical_name.to_text(), answers.canonical_name.to_unicode()


Out[11]:
('www.google.com.', u'www.google.com.')

In [12]:
answers.expiration


Out[12]:
1492550783.903062

In [13]:
answers.qname


Out[13]:
<DNS name www.google.com.>

In [14]:
answers.qname.to_text(), answers.qname.to_unicode()


Out[14]:
('www.google.com.', u'www.google.com.')

In [15]:
answers.rdclass == dns.rdataclass.IN


Out[15]:
True

In [16]:
answers.rdtype == dns.rdatatype.A


Out[16]:
True

In [17]:
answers.response


Out[17]:
<DNS message, ID 37526>

In [18]:
answers.response.edns


Out[18]:
-1

In [19]:
answers.response.time


Out[19]:
0.023239850997924805

In [20]:
answers.response.flags       # https://tools.ietf.org/html/rfc1035 4.1.1. Header section format


Out[20]:
33152

In [21]:
answers.response.flags & 0b1000000000000000          # 0=query, 1=response


Out[21]:
32768

In [22]:
answers.response.flags & 0b0000010000000000          # 1=authoratative


Out[22]:
0

In [23]:
answers.response.flags & 0b0000001000000000          # 1=truncated


Out[23]:
0

In [24]:
answers.response.flags & 0b0000000100000000          # 1=recursive desired (copied into response)


Out[24]:
256

In [25]:
answers.response.flags & 0b0000000010000000          # 1=recursion available


Out[25]:
128

In [26]:
answers.response.rcode()                             # errors?


Out[26]:
0

In [27]:
answers.rrset


Out[27]:
<DNS www.google.com. IN A RRset>

In [28]:
len(answers.rrset)


Out[28]:
15

In [29]:
list(answers.rrset)


Out[29]:
[<DNS IN A rdata: 150.101.161.237>,
 <DNS IN A rdata: 150.101.161.215>,
 <DNS IN A rdata: 150.101.161.221>,
 <DNS IN A rdata: 150.101.161.219>,
 <DNS IN A rdata: 150.101.161.249>,
 <DNS IN A rdata: 150.101.161.234>,
 <DNS IN A rdata: 150.101.161.226>,
 <DNS IN A rdata: 150.101.161.251>,
 <DNS IN A rdata: 150.101.161.245>,
 <DNS IN A rdata: 150.101.161.222>,
 <DNS IN A rdata: 150.101.161.211>,
 <DNS IN A rdata: 150.101.161.236>,
 <DNS IN A rdata: 150.101.161.230>,
 <DNS IN A rdata: 150.101.161.241>,
 <DNS IN A rdata: 150.101.161.207>]

In [30]:
answers.rrset[0].address


Out[30]:
u'150.101.161.237'

In [31]:
answers.rrset[0].rdclass == dns.rdataclass.IN


Out[31]:
True

In [32]:
answers.rrset[0].rdclass == dns.rdatatype.A


Out[32]:
True

Querying other record types

Of course one query other records than A type. The second argument to dns.resolver.query accepts the contants defined in dns.rdatatype or simply a string value. The attributes of the returned records are specific ot the queried type, e.g. MX records have a preference attribute.

Example: Mail Exchanger (MX) records


In [33]:
answers = dns.resolver.query('google.com', 'MX')
len(answers.rrset)


Out[33]:
5

In [34]:
answers.rrset[0]


Out[34]:
<DNS IN MX rdata: 50 alt4.aspmx.l.google.com.>

In [35]:
answers.rrset[0].exchange.to_text()


Out[35]:
'alt4.aspmx.l.google.com.'

In [36]:
answers.rrset[0].preference


Out[36]:
50

In [37]:
answers.rrset[0].rdtype == dns.rdatatype.MX


Out[37]:
True

Example: Start of Authority (SOA) record


In [38]:
answers = dns.resolver.query('google.com', 'SOA')
len(answers.rrset)


Out[38]:
1

In [39]:
answers.rrset[0]


Out[39]:
<DNS IN SOA rdata: ns2.google.com. dns-admin.google.com. 153472704 900 900 1800 60>

In [40]:
answers.rrset[0].mname.to_text()


Out[40]:
'ns2.google.com.'

In [41]:
answers.rrset[0].serial


Out[41]:
153472704

In [42]:
answers.rrset[0].refresh


Out[42]:
900

Example: PTR record


In [44]:
answers = dns.resolver.query('4.4.8.8.in-addr.arpa', 'PTR')
len(answers)


Out[44]:
1

In [45]:
answers.rrset[0]


Out[45]:
<DNS IN PTR rdata: google-public-dns-b.google.com.>

In [51]:
answers.rrset[0].to_text()


Out[51]:
'google-public-dns-b.google.com.'

Example SRV record


In [54]:
answers = dns.resolver.query('_http._tcp.juenemann.net', 'SRV')
len(answers)


Out[54]:
1

In [55]:
answers.rrset[0]


Out[55]:
<DNS IN SRV rdata: 10 50 80 www.juenemann.net.>

In [56]:
answers.rrset[0].to_text()


Out[56]:
'10 50 80 www.juenemann.net.'

In [62]:
answers.rrset[0].target.to_text()


Out[62]:
'www.juenemann.net.'

In [58]:
answers.rrset[0].priority, answers.rrset[0].weight, answers.rrset[0].port


Out[58]:
(10, 50, 80)

Querying specific DNS server(s)

There are cases where one does not want to use alternative settings to those configured in /etc/resolv.conf. For this purpose one has to create and customise an instance of the dns.resolver.Resolver class.

Example: Querying the OpenDNS servers


In [ ]:


In [ ]:
resolver = dns.resolver.Resolver()
resolver.nameservers = ['208.67.222.222', '208.67.220.220']
resolver.nameservers

In [ ]:
answers = resolver.query('google.com', 'NS')
list(answers.rrset)

Example: Disallow recursion

In this example the query flags are manipulated to disallow recursion. This will fail if the queried DNS servers are not authorative for the domain.


In [ ]:
resolver = dns.resolver.Resolver()
resolver.nameservers = ['208.67.222.222', '208.67.220.220']
resolver.nameservers

In [ ]:
resolver.set_flags(0b0000000000000000)    # Clear all flags, including 'recursive desired'
resolver.flags

In [ ]:
try:
    answers = resolver.query('www.google.com', 'A')
except Exception as e:
    print e

In [ ]:
answers = resolver.query('www.opendns.com', 'A') 
list(answers.rrset)