Python Dns Record Lookup Utility

Written by

in

Looking for a reliable way to query DNS records straight from your Python scripts? Whether you’re troubleshooting network issues, building a monitoring tool, or automating domain verification, a Python DNS record lookup utility can save you countless hours. In this guide we’ll explore the fundamentals of DNS, compare the built‑in socket module with the powerful dnspython library, and walk you through a complete, production‑ready script that handles A, AAAA, CNAME, MX, TXT, and more. By the end, you’ll have a clean, reusable utility that’s SEO‑friendly, well‑documented, and ready to drop into any project.

What is DNS and Why It Matters

The Domain Name System (DNS) translates human‑readable domain names (like example.com) into IP addresses that computers use to communicate. Beyond simple A/AAAA lookups, DNS stores a wealth of information: mail server locations (MX), service records (SRV), verification tokens (TXT), and even aliasing (CNAME). Understanding DNS is crucial for:

  • Network troubleshooting: Identify misconfigured records or propagation delays.
  • Security audits: Verify SPF, DKIM, and DMARC TXT records.
  • Automation: Dynamically adjust load balancers or CDN configurations.

Python DNS Lookup: Core Concepts

Using the built‑in socket module

The socket module offers a quick way to resolve A and AAAA records with socket.gethostbyname() and socket.getaddrinfo(). While it’s sufficient for simple checks, it has limitations:

  • Only returns IP addresses—no MX, TXT, or CNAME data.
  • Lacks control over query type, DNS server selection, or timeout handling.

Example:

import socket

def simple_a_lookup(host):
    try:
        return socket.gethostbyname(host)
    except socket.gaierror as e:
        return f"Lookup failed: {e}"

Leveraging dnspython for advanced queries

dnspython is the de‑facto library for DNS in Python. It supports every record type, custom resolvers, DNSSEC validation, and asynchronous operation. Installing it is as easy as:

pip install dnspython

Key classes you’ll use:

  • dns.resolver.Resolver – configure nameservers, timeout, and retry logic.
  • dns.message.make_query() – build low‑level DNS messages.
  • dns.rdatatype – enumerate supported record types.

Building a Simple DNS Record Lookup Utility

Step‑by‑step code walkthrough

The following script demonstrates a clean, reusable function called dns_lookup(). It accepts a domain name, a record type, and an optional list of DNS servers.

import dns.resolver
from typing import List, Union

def dns_lookup(
    domain: str,
    record_type: str = "A",
    nameservers: List[str] = None,
    timeout: float = 2.5,
) -> Union[List[str], str]:
    """
    Perform a DNS query for *domain* and return a list of string results.
    Supported record types: A, AAAA, CNAME, MX, TXT, NS, SRV, PTR.
    """
    # 1️⃣ Create a resolver instance
    resolver = dns.resolver.Resolver()
    resolver.timeout = timeout
    resolver.lifetime = timeout * 2

    # 2️⃣ Override system nameservers if provided
    if nameservers:
        resolver.nameservers = nameservers

    # 3️⃣ Normalize the record type (dnspython expects upper‑case)
    rtype = record_type.upper()

    try:
        # 4️⃣ Perform the query
        answers = resolver.resolve(domain, rtype)

        # 5️⃣ Extract human‑readable values
        results = []
        for rdata in answers:
            if rtype == "MX":
                results.append(f"{rdata.preference} {rdata.exchange}")
            elif rtype == "SRV":
                results.append(
                    f"{rdata.priority} {rdata.weight} {rdata.port} {rdata.target}"
                )
            elif rtype == "TXT":
                # TXT can be a list of strings; join them for readability
                results.append("".join(rdata.strings))
            else:
                results.append(str(rdata))

        return results

    except dns.resolver.NoAnswer:
        return f"No {rtype} record found for {domain}."
    except dns.resolver.NXDOMAIN:
        return f"The domain {domain} does not exist."
    except dns.exception.Timeout:
        return f"Query timed out after {timeout} seconds."
    except Exception as e:
        return f"Unexpected error: {e}"

Handling different record types

Each DNS record has a unique data structure. Below is a quick cheat‑sheet for the most common types and how dnspython represents them:

  • A / AAAA: Simple IP address strings.
  • CNAME: Canonical name (alias) as a string.
  • MX: preference (int) and exchange (domain).
  • TXT: A list of byte strings; join them to get the full text.
  • NS: Authoritative name server hostnames.
  • SRV: Service location with priority, weight, port, and target.

Best Practices and Common Pitfalls

  • Validate user input: Never trust raw domain strings; strip whitespace and reject characters outside the DNS alphabet.
  • Use explicit timeouts: Default resolver settings can hang for minutes on misbehaving nameservers.
  • Cache results wisely: Re‑querying the same record within seconds wastes bandwidth and may trigger rate limits.
  • Handle DNSSEC: For security‑sensitive applications, enable DNSSEC validation via resolver.use_edns(0, 0, 4096) and check answers.response.flags.
  • Log errors, not just raise: In production, capture dns.resolver.NoNameservers and dns.resolver.LifetimeTimeout separately for better diagnostics.

Performance Tips and Caching

When your utility runs thousands of lookups per minute, consider:

  1. In‑memory LRU cache: Use functools.lru_cache on dns_lookup for repeat queries.
  2. Dedicated resolver daemon: Run unbound or dnsmasq locally and point resolver.nameservers to 127.0.0.1. This reduces network latency dramatically.
  3. Batch queries: Build a single dns.message.make_query() with multiple QNAMEs using dns.message.Message and send it via dns.query.udp(). This is more advanced but cuts round‑trip overhead.

Putting It All Together: A Ready‑to‑Use Script

Below is a compact command‑line tool that ties everything together. Save it as dnslookup.py and run python dnslookup.py example.com MX to fetch MX records.

#!/usr/bin/env python3
import argparse
import sys

# Import the utility function defined earlier
from typing import List, Union

def main():
    parser = argparse.ArgumentParser(
        description="Python DNS record lookup utility – supports A, AAAA, CNAME, MX, TXT, NS, SRV."
    )
    parser.add_argument("domain", help="Domain name to query (e.g., example.com)")
    parser.add_argument(
        "type",
        nargs="?",
        default="A",
        help="Record type (A, AAAA, CNAME, MX, TXT, NS, SRV). Default is A.",
    )
    parser.add_argument(
        "-s",
        "--servers",
        nargs="+",
        help="Optional list of DNS servers (e.g., 8.8.8.8 1.1.1.1).",
    )
    parser.add_argument(
        "-t",
        "--timeout",
        type=float,
        default=2.5,
        help="Resolver timeout in seconds (default: 2.5).",
    )
    args = parser.parse_args()

    results = dns_lookup(
        domain=args.domain,
        record_type=args.type,
        nameservers=args.servers,
        timeout=args.timeout,
    )

    if isinstance(results, list):
        for r in results:
            print(r)
    else:
        # An error string was returned
        print(results, file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

This script demonstrates:

  • Argument parsing with argparse for a clean CLI.
  • Optional custom nameserver support.
  • Graceful error handling that returns a non‑zero exit code

Comments

Leave a Reply

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