Python Dns Record Lookup Utility

Written by

in

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 dnspython and the built‑in socket module 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

  1. Validate user input. Always sanitize domain names to avoid injection attacks.
  2. Implement caching. Use dns.resolver.Resolver.cache or an external Redis layer to reduce query latency.
  3. Respect rate limits. When querying public resolvers, throttle requests to avoid being blocked.
  4. Log responsibly. Store query timestamps, response codes, and errors for audit trails.
  5. Support DNSSEC. If integrity verification is required, enable resolver.use_edns(0, dns.flags.DO, 1232) and validate signatures.
  6. 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

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *