/commands/dns/query-dns-records-python
Query DNS records in Python with dnspython (A, MX, TXT)
- tool
- DNS
- versions
- dnspython 2.x, Python 3.9+
- os
- any
To look up DNS records from Python, install dnspython and call dns.resolver.resolve(name, rtype). It returns an Answer you iterate over, one rdata per record, and raises dns.resolver.NXDOMAIN when the name does not exist and dns.resolver.NoAnswer when the name exists without that record type. To ask a specific server, create a Resolver(configure=False) and set nameservers (and port).
Example
The block starts a tiny DNS server on 127.0.0.1 port 5213 that serves a test zone, then queries it the way you would query a real resolver:
cat > lookup.py <<'EOF'
import socket, threading
import dns.message, dns.rcode, dns.resolver, dns.rrset, dns.zone
zone = dns.zone.from_text("""
$TTL 300
@ IN SOA ns1 hostmaster 1 3600 600 86400 300
@ IN NS ns1
@ IN A 192.0.2.10
ns1 IN A 192.0.2.53
@ IN MX 10 mail
mail IN A 192.0.2.25
@ IN TXT "v=spf1 mx -all"
""", origin="example.test.", relativize=False)
def serve(sock, count):
for _ in range(count):
data, addr = sock.recvfrom(4096)
q = dns.message.from_wire(data)
r = dns.message.make_response(q)
qn = q.question[0]
node = zone.get_node(qn.name)
rds = node.get_rdataset(qn.rdclass, qn.rdtype) if node else None
if rds:
r.answer.append(dns.rrset.from_rdata_list(qn.name, rds.ttl, list(rds)))
elif node is None:
r.set_rcode(dns.rcode.NXDOMAIN)
sock.sendto(r.to_wire(), addr)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", 5213))
threading.Thread(target=serve, args=(s, 6), daemon=True).start()
res = dns.resolver.Resolver(configure=False)
res.nameservers = ["127.0.0.1"]
res.port = 5213
res.lifetime = 5
for rtype in ["A", "MX", "TXT"]:
answer = res.resolve("example.test", rtype)
for rdata in answer:
print(rtype, "ttl", answer.rrset.ttl, rdata.to_text())
mx = sorted(res.resolve("example.test", "MX"), key=lambda r: r.preference)
print("mail goes to", mx[0].exchange)
for name, rtype in [("nope.example.test", "A"), ("mail.example.test", "AAAA")]:
try:
res.resolve(name, rtype)
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
print(type(e).__name__, "-", e)
EOF
uv run -q --no-project --python 3.13 --with dnspython==2.8.0 python lookup.pyOutput
A ttl 300 192.0.2.10 MX ttl 300 10 mail.example.test. TXT ttl 300 "v=spf1 mx -all" mail goes to mail.example.test. NXDOMAIN - The DNS query name does not exist: nope.example.test. NoAnswer - The DNS response does not contain an answer to the question: mail.example.test. IN AAAA
rdata.to_text() prints the record data in zone-file format; typed attributes are also available (preference and exchange for MX, strings for TXT, address for A and AAAA).
Common calls
| Call | What it does |
|---|---|
| dns.resolver.resolve('example.com', 'A') | Uses the system configuration (resolv.conf, or the registry on Windows). |
| dns.resolver.resolve('example.com', 'AAAA') | IPv6 addresses. |
| dns.resolver.resolve_address('192.0.2.10') | Reverse (PTR) lookup; builds the in-addr.arpa name for you. |
| Resolver(configure=False) with nameservers=['1.1.1.1'] | Ignores the system configuration and asks that server. |
| res.timeout / res.lifetime | Seconds per server attempt / for the whole resolution. |
| resolve(name, 'TXT', raise_on_no_answer=False) | Returns an empty Answer instead of raising NoAnswer. |
| resolve(name, 'A', tcp=True) | Sends the query over TCP instead of UDP. |
Errors you will meet
dns.resolver.NXDOMAIN: The DNS query name does not exist— see the NXDOMAIN page for causes such as typos and missing search domains.dns.resolver.NoAnswer— the name exists but has no record of that type; for example a host with only an A record has no AAAA.dns.resolver.LifetimeTimeout— no server answered withinlifetime(default 5 seconds); check the server address, port and firewall.dns.resolver.NoNameservers— every server returned an error such as SERVFAIL or REFUSED; the DNS RCODE table explains each code.
Names are treated as absolute: resolve('example.test') asks for example.test.; pass search=True to try the search domains from the system configuration.
FAQ
Why not use socket.getaddrinfo?
getaddrinfo returns only addresses (A and AAAA, plus the hosts file) and follows the operating system's resolver configuration. It cannot ask for MX, TXT, NS or SOA records or choose the server, which dnspython does.
What is the difference between resolve() and query()?
dnspython 2.0 renamed query() to resolve(). resolve() does not apply the search list from resolv.conf unless you pass search=True; query() is kept as a deprecated alias with the old behaviour.
How do I get the TTL of the answer?
The Answer object has rrset.ttl, or answer.rrset.ttl. It is the remaining TTL as reported by the server that answered, which for a caching resolver counts down over time.