When it comes to troubleshooting network issues, validating DNS records is one of the most essential tasks for developers, system administrators, and security analysts. While there are plenty of online tools that can query A, MX, TXT, and other record types, having a lightweight, scriptable utility written in Python gives you full control, automation capabilities, and the ability to integrate DNS checks directly into your CI/CD pipelines. In this article we’ll explore how to build a robust Python DNS record lookup utility from scratch, discuss the most popular libraries, and provide ready‑to‑run code examples that you can adapt to any environment.
Why Choose Python for DNS Lookups?
- Cross‑platform support: Python runs on Windows, macOS, and Linux without modification.
- Rich ecosystem: Libraries like
dnspythonand the built‑insocketmodule make DNS queries straightforward. - Automation friendly: Combine DNS checks with other Python scripts, testing frameworks, or orchestration tools such as Ansible.
- Readability: Python’s clear syntax reduces the learning curve for newcomers and speeds up maintenance.
Core Concepts Behind DNS Record Queries
Understanding DNS Record Types
Before diving into code, it’s helpful to know the most common DNS record types you’ll encounter:
- A – IPv4 address of a host.
- AAAA – IPv6 address of a host.
- CNAME – Canonical name (alias) for a domain.
- MX – Mail exchange server information.
- TXT – Arbitrary text, often used for SPF, DKIM, or verification tokens.
- NS – Authoritative name servers for a domain.
- SOA – Start of Authority record, containing zone metadata.
Recursive vs. Iterative Queries
Most public resolvers (Google 8.8.8.8, Cloudflare 1.1.1.1) perform recursive queries, meaning they chase the chain of authority until they return the final answer. In contrast, an iterative query asks a specific name server for the best answer it can provide, which is useful for debugging delegation problems. Our utility will let you toggle between the two modes.
Choosing the Right Python Library
1. Built‑in socket Module
The socket module can resolve A and AAAA records via socket.getaddrinfo(). It’s lightweight but limited to address lookups.
2. dns.resolver from dnspython
dnspython is the de‑facto standard for DNS in Python. It supports every record type, custom resolvers, DNSSEC validation, and asynchronous queries.
3. asyncio + aiodns
For high‑performance, non‑blocking applications, aiodns wraps c-ares and integrates with asyncio. This is ideal for scanning large lists of domains in parallel.
Step‑by‑Step: Building a Simple DNS Lookup Utility
Prerequisites
pip install dnspython
# Optional for async version:
pip install aiodns
2️⃣ Basic Synchronous Lookup with dnspython
The following script accepts a domain name and an optional record type, then prints a formatted result. It includes error handling for NXDOMAIN, timeouts, and unsupported types.
import sys
import dns.resolver
def lookup(domain: str, record_type: str = "A", nameserver: str = None):
resolver = dns.resolver.Resolver()
if nameserver:
resolver.nameservers = [nameserver]
try:
answers = resolver.resolve(domain, record_type)
print(f"\n{record_type} records for {domain}:")
for rdata in answers:
print(f" - {rdata.to_text()}")
except dns.resolver.NXDOMAIN:
print(f"❌ No such domain: {domain}")
except dns.resolver.NoAnswer:
print(f"⚠️ No {record_type} record found for {domain}")
except dns.exception.Timeout:
print("⏳ Query timed out")
except dns.resolver.NoNameservers:
print("🚫 All nameservers failed to respond")
except Exception as e:
print(f"❗ Unexpected error: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python dns_lookup.py [record_type] [nameserver]")
sys.exit(1)
domain = sys.argv[1]
record_type = sys.argv[2].upper() if len(sys.argv) > 2 else "A"
ns = sys.argv[3] if len(sys.argv) > 3 else None
lookup(domain, record_type, ns)
Key Features of the Script
- Accepts command‑line arguments for flexibility.
- Allows you to specify a custom
nameserver(useful for testing internal DNS). - Gracefully handles the most common DNS exceptions.
- Outputs results in a clean, human‑readable format that can be piped to other tools.
3️⃣ Extending to Multiple Record Types
If you need to query several types at once, wrap the call in a loop:
def multi_lookup(domain, types=("A", "AAAA", "MX", "TXT")):
for rtype in types:
lookup(domain, rtype)
4️⃣ Asynchronous Bulk Lookups with aiodns
Scanning hundreds of domains sequentially can be slow. Below is a compact async version that resolves A records for a list of hosts in parallel, respecting a configurable concurrency limit.
import asyncio
import aiodns
async def async_lookup(host, resolver):
try:
result = await resolver.query(host, "A")
return host, [r.host for r in result]
except Exception as e:
return host, str(e)
async def bulk_lookup(hosts, concurrency=100):
resolver = aiodns.DNSResolver()
semaphore = asyncio.Semaphore(concurrency)
async def bound_lookup(host):
async with semaphore:
return await async_lookup(host, resolver)
tasks = [bound_lookup(h) for h in hosts]
return await asyncio.gather(*tasks)
if __name__ == "__main__":
hosts = ["example.com", "python.org", "nonexistent.tld"]
results = asyncio.run(bulk_lookup(hosts))
for host, outcome in results:
print(f"{host}: {outcome}")
Best Practices for Production‑Ready DNS Utilities
- Validate user input. Always sanitize domain names to avoid injection attacks.
- Implement caching. Use
dns.resolver.Resolver.cacheor an external Redis layer to reduce query latency. - Respect rate limits. When querying public resolvers, throttle requests to avoid being blocked.
- Log responsibly. Store query timestamps, response codes, and errors for audit trails.
- Support DNSSEC. If integrity verification is required, enable
resolver.use_edns(0, dns.flags.DO, 1232)and validate signatures. - Provide JSON output. For API integrations, emit structured JSON rather than plain text.
Integrating the Utility into CI/CD Pipelines
Automated DNS checks can catch misconfigurations before they reach production. Here’s a quick example of how to embed the script in a GitHub Actions workflow:
name: DNS Validation
on: [push, pull_request]
jobs:
dns-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: pip install dnspython
- name: Run DNS lookup
run: |
python dns_lookup.py myapp.example.com MX 8.8.8.8 > dns-report.txt
cat dns-report.txt
- name: Fail on missing MX
run: |
if grep -q "No MX record" dns-report.txt; then
echo "❌ MX record missing!" && exit 1
fi
SEO‑Friendly Keywords and Phrases to Remember
When you publish the article or the utility’s documentation, sprinkle the following terms naturally to improve search visibility:
- Python DNS lookup script
- how to query DNS records with Python
- dnspython example code
- asynchronous DNS resolver Python
- CLI DNS utility Python
- automated DNS testing pipeline
- resolve MX records Python
Conclusion
Building a Python DNS record lookup utility is a rewarding exercise that blends networking fundamentals with modern scripting practices. By leveraging the powerful dnspython library—or its asynchronous counterpart aiodns—you can create a flexible tool that serves both ad‑hoc troubleshooting and large‑scale automation needs. Remember to follow the best‑practice checklist, integrate your script into
Leave a Reply