One of the amazing things that I found using python is the fact that it has a great community that shares incredible projects on PyPI, the official Python repository. Every time that I need a specific functionality, I search through the PyPI repository and I’m always able to discover new projects and find out amazing solutions that already exists, instead of trying to build a new solution from the ground. These days I was into DNS requests and I found exactly what I wanted, a simple way to communicate with DNS! I’ll explain how simple it is to find out DNS entries with the dnspython package.
It’s very easy to use dnspython, since the package is already provided by the most of Linux distros. On Debian, there are two packages that provides dnspython:
- python-dnspython (for python2)
- python3-dnspython (for python3)
All we need to do is to install the correct package, based on the python’s version that we’re using. Since we’re just starting to use python, it’s better to begin with python3 right away! So, we’ll install the following package on our Debian system:
So now that we have installed the dnspython package we can just proceed importing it into IDLE. But how the dnspython library is called? Let’s find out using the help funtion:
>>> help('modules dnspython') Here is a list of modules whose name or summary contains 'dnspython'. If there are any, enter a module name to get more help. dns - dnspython DNS toolkit dns.version - dnspython release version information. >>>
The library is called ‘dns’, and again if we want to search everything related to this package, we can just use the help function, this time searching the name of the library. Ok, let’s begin to work with dnspython!
>>> import dns >>>
Creating an instance of a DNS query:
There are various types of requests that we can made to find out specific DNS entries of a given domain. The request response will always return a dictionary that with a few sections which we’ll be able to work with. The first step needed to work with dnspython is to create an instance of a dns query:
>>> from dns import resolver >>> dns_request = resolver.query('marcelfox.com')
We may also inform which type of request do we want. The request above will return a type A request. We can also request different types of entries such as TXT or MX, by specifying which type do we want, for instance:
>>> dns_request = resolver.query('marcelfox.com', 'TXT') >>> dns_request = resolver.query('marcelfox.com', 'MX')
Ok now that we have created our instance, is important to check the objects of our dns_request. Let’s print out the keys stored on our dns_request dictionary:
>>> dns_request.__dict__.keys() dict_keys(['expiration', 'response', 'rrset', 'rdtype', 'rdclass', 'canonical_name', 'qname'])
There are some crazy key names on it! Let’s begin our work with a word that we all understand here, which is the response object. Let’s print out the contents of our response:
>>> print(dns_request.response) id 39489 opcode QUERY rcode NOERROR flags QR RD RA ;QUESTION marcelfox.com. IN A ;ANSWER marcelfox.com. 99 IN A 184.108.40.206 marcelfox.com. 99 IN A 220.127.116.11 ;AUTHORITY marcelfox.com. 99792 IN NS dorthy.ns.cloudflare.com. marcelfox.com. 99792 IN NS sri.ns.cloudflare.com. ;ADDITIONAL >>>
As we may observe above, the response will give us the complete answer to the query that we’ve made for our marcelfox.com domain. It returns the ‘QUESTION’ that we’ve made, giving us an ‘ANSWER’ and informing who has the ‘AUTHORITY’ over the domain and some ‘ADDITIONAL’ information, if provided by the response. Let’s understand each of those sections, as explained in RFC – 1034:
The most important field in the header is a four bit field called an opcode which separates different queries. Of the possible 16 values, one (standard query) is part of the official protocol, two (inverse query and status query) are options, one (completion) is obsolete, and the rest are unassigned. The four sections are: Question Carries the query name and other query parameters. Answer Carries RRs which directly answer the query. Authority Carries RRs which describe other authoritative servers. May optionally carry the SOA RR for the authoritative data in the answer section. Additional Carries RRs which may be helpful in using the RRs in the other sections.
As explained above, the ‘ANSWER’ Carries RRs which directly answer the query. The RRs is the standard name that represents the answer of contents in a specific entry of a DNS zone. It’s kinda difficult to understand at first but, just keep in mind that the answer is stored in the rrset key of our dictionary. So let’s print out the contents that we have stored on the rrset key:
>>> print(dns_request.rrset) marcelfox.com. 99 IN A 18.104.22.168 marcelfox.com. 99 IN A 22.214.171.124
If you noticed, the answer give us the A entry by default, if none is given. But as explained earlier, we can request other entries such as MX or TXT so let’s print out the rrset of our different types of requests:
>>> mx_request = resolver.query('marcelfox.com', 'MX') >>> print(mx_request.rrset) marcelfox.com. 300 IN MX 10 mail.marcelfox.com.
>>> txt_request = resolver.query('marcelfox.com', 'TXT') >>> print(txt_request.rrset) marcelfox.com. 300 IN TXT "v=spf1 mx a:srv.purplewitch.org -all" marcelfox.com. 300 IN TXT "ca3-fa7db9e71ee643a88184f78e8a9e804e"
We can also print just the entry value with a for loop:
>>> dns_request = resolver.query('marcelfox.com', 'TXT') >>> for rdata in dns_request: ... print(rdata) ... "v=spf1 mx a:srv.purplewitch.org -all" "ca3-fa7db9e71ee643a88184f78e8a9e804e" >>>
This post is just a scratch in the surface of dnspython range of possibilities. Of course you can do a whole lot more by comprehending it’s documentation. You can find more examples at:
More related links: