Problem 7.5
The dig tool
The dig
command is a popular tool for querying DNS name servers. You task is to expose the dig command in Python as a function dig
.
The dig
command
Let's look at the output of the dig command to understand how it works.
$ dig amazon.com
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> amazon.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49697
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;amazon.com. IN A
;; ANSWER SECTION:
amazon.com. 128 IN A 205.251.242.103
amazon.com. 128 IN A 54.239.28.85
amazon.com. 128 IN A 52.94.236.248
;; Query time: 16 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Wed Jan 18 13:45:30 IST 2023
;; MSG SIZE rcvd: 87
The output is too verbose. We can silence everything else and show only answer using:
$ dig +noall +answer amazon.com
amazon.com. 128 IN A 205.251.242.103
amazon.com. 128 IN A 54.239.28.85
amazon.com. 128 IN A 52.94.236.248
We used the dig
command to query the name server to look up for amazon.com. By default, it queries for records of type A
, which stands for IPv4 Address.
The DNS server responded with three records matching the domain name amazon.com
. Each entry contains 5 fields, namely the domain name, TTL, class, record type and content.
See how to read dig output on wizard zines for more details.
Advanced Usage
Record Type
We can optionally pass the record type to dig
to query of other types of DNS records like MX (mail exchange), TXT (text notes), NS (name server) etc.
The following command, queries for records of type MX.
$ dig +noall +answer amazon.com mx
amazon.com. 766 IN MX 5 amazon-smtp.amazon.com.
In case of MX records, the content contains the priority and the mail server name. There could be more than one entry.
The following command queries for records of type NS.
$ dig +noall +answer amazon.com ns
amazon.com. 1626 IN NS pdns6.ultradns.co.uk.
amazon.com. 1626 IN NS ns3.p31.dynect.net.
amazon.com. 1626 IN NS ns4.p31.dynect.net.
amazon.com. 1626 IN NS ns1.p31.dynect.net.
amazon.com. 1626 IN NS ns2.p31.dynect.net.
amazon.com. 1626 IN NS pdns1.ultradns.net.
Server
By default, the dig command queries the DNS server configured in the system. However, we can explicitly pass a server to query it.
$ dig +noall +answer amazon.com @8.8.8.8
amazon.com. 128 IN A 205.251.242.103
amazon.com. 128 IN A 54.239.28.85
amazon.com. 128 IN A 52.94.236.248
The 8.8.8.8
is the Google DNS server. The output would look exactly the same in the normal times, but it is very handy to troubleshoot DNS issues with your domain by querying various known DNS server.
There are some hobby DNS servers like dns.toys that provide useful utilities over dns.
$ dig +noall +answer mumbai.weather @dns.toys
mumbai. 1 IN TXT "Mumbai (IN)" "30.40C (86.72F)" "32.10% hu." "clearsky_day" "14:30, Wed"
mumbai. 1 IN TXT "Mumbai (IN)" "27.90C (82.22F)" "42.80% hu." "clearsky_day" "16:30, Wed"
mumbai. 1 IN TXT "Mumbai (IN)" "23.00C (73.40F)" "71.40% hu." "clearsky_night" "18:30, Wed"
mumbai. 1 IN TXT "Mumbai (IN)" "20.40C (68.72F)" "90.50% hu." "clearsky_night" "20:30, Wed"
mumbai. 1 IN TXT "Mumbai (IN)" "18.80C (65.84F)" "90.40% hu." "clearsky_night" "22:30, Wed"
Summary of Usage
The dig command is used as follows:
dig options domain-name record-type @server
The options, record-type and server are optional.
The Python API
You task is to implement a Python function dig
that calls the dig command with appropriate arguments, parse the output and returns the result as a Python data structure.
The funtion takes the doman name as argument and two optional arguments, record_type
and server
.
Sample Usage:
>>> dig("amazon.com")
[
{"name": "amazon.com.", "ttl": 128, "class": "IN", "record_type": "A", "content": "205.251.242.103"},
{"name": "amazon.com.", "ttl": 128, "class": "IN", "record_type": "A", "content": "54.239.28.85"},
{"name": "amazon.com.", "ttl": 128, "class": "IN", "record_type": "A", "content": "52.94.236.248"}
]
>>> dig("amazon.com", record_type="MX")
[
{"name": "amazon.com", "ttl": 498, "class": "IN", "record_type": "MX", "content": "5 amazon-smtp.amazon.com."}
]
>>> dig("amazon.com", record_type="MX", server="8.8.8.8")
[
{"name": "amazon.com", "ttl": 498, "class": "IN", "record_type": "MX", "content": "5 amazon-smtp.amazon.com."}
]
>>> dig("mumbai.weather", server="dns.toys")
[
{"name": "mumbai.", "ttl": 1, "class": "IN", "record_type": "TXT", "content": '"Mumbai (IN)" "30.40C (86.72F)" "32.10% hu." "clearsky_day" "14:30, Wed"'},
...
]
Solution
import subprocess
def parse_record(line):
name, ttl, klass, record_type, content = line.strip().split(None, 4)
return {
"name": name,
"ttl": int(ttl),
"record_type": record_type,
"class": klass,
"content": content
}
def dig(name, record_type="A", server=None):
"""Python interface to the dig command.
"""
cmd = ["dig", '+noall', '+answer', name, record_type]
if server:
cmd.append(f"@{server}")
print(cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
return [parse_record(line) for line in p.stdout]