nadzoring.dns_lookup package

DNS lookup module for domain name resolution and DNS record checking.

nadzoring.dns_lookup.benchmark_dns_servers(domain: str = 'google.com', servers: list[str] | None = None, record_type: Literal['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'PTR', 'SOA', 'DNSKEY'] = 'A', queries: int = 10, max_workers: int = 5, progress_callback: Callable[[str, int], None] | None = None, *, parallel: bool = True) list[BenchmarkResult][source]

Benchmark multiple DNS servers and compare their performance.

Tests multiple DNS servers either in parallel or sequentially, measuring response times and success rates. Results are sorted by average response time for easy comparison.

Parameters:
  • domain – Domain name to query for benchmarking. Defaults to “google.com”.

  • servers – List of DNS server IP addresses to benchmark. If None, uses get_public_dns_servers() for a comprehensive list of public DNS providers.

  • record_type – DNS record type to query. Defaults to “A” records.

  • queries – Number of queries to perform per server. Defaults to 10.

  • max_workers – Maximum number of parallel threads when parallel=True. Defaults to 5. Ignored when parallel=False.

  • progress_callback – Optional callback function called after each server completes benchmarking. Receives the server IP and the completion index (1-based). Useful for UI progress bars.

  • parallel – If True, benchmarks servers in parallel using multiple threads. If False, benchmarks sequentially. Defaults to True.

Returns:

List of benchmark results for each server,

sorted by average response time (fastest first). Each result contains the same fields as benchmark_single_server().

Return type:

List[BenchmarkResult]

Examples

>>> # Basic parallel benchmark of all public DNS servers
>>> results = benchmark_dns_servers()
>>> for i, r in enumerate(results[:5], 1):
...     print(f"{i}. {r['server']}: {r['avg_response_time']:.2f}ms")
>>> # Benchmark specific servers sequentially with progress tracking
>>> def progress(server: str, index: int):
...     print(f"[{index}] Completed {server}")
>>>
>>> results = benchmark_dns_servers(
...     servers=["8.8.8.8", "1.1.1.1", "9.9.9.9"],
...     parallel=False,
...     progress_callback=progress,
... )
>>> # Custom benchmark with more queries
>>> results = benchmark_dns_servers(
...     domain="example.com",
...     servers=["8.8.8.8", "1.1.1.1"],
...     queries=20,
...     max_workers=2,
... )
>>> fastest = results[0]
>>> slowest = results[-1]
>>> print(
...     f"Fastest: {fastest['server']} ({fastest['avg_response_time']:.2f}ms)"
... )
>>> print(
...     f"Slowest: {slowest['server']} ({slowest['avg_response_time']:.2f}ms)"
... )

Notes

  • Parallel benchmarking uses ThreadPoolExecutor for concurrent queries

  • Results are always sorted by average response time (fastest first)

  • Failed servers are still included in results with 0 response times

  • The progress callback receives both server IP and completion index

  • Logging at debug level captures individual server failures

  • Consider rate limiting when using parallel=True with many servers

  • Default server list includes major public DNS providers:

    Google, Cloudflare, OpenDNS, Quad9, Verisign, and others

nadzoring.dns_lookup.check_dns(domain: str, nameserver: str | None = None, record_types: list[str] | None = None, *, validate_mx: bool = False, validate_txt: bool = False) DetailedCheckResult[source]

Perform a comprehensive DNS check with detailed per-record information.

Queries specified DNS record types for a domain and returns detailed information including actual records, response times, errors, and optional validation results for MX and TXT records.

Parameters:
  • domain – Domain name to check (e.g., “example.com”).

  • nameserver – Optional specific nameserver IP to use for queries. If None, uses system default resolvers.

  • record_types – List of DNS record types to query. If None, defaults to [“A”, “AAAA”, “MX”, “NS”, “TXT”, “CNAME”].

  • validate_mx – If True, perform additional validation on MX records (checks for duplicate priorities).

  • validate_txt – If True, perform additional validation on TXT records (checks SPF and DKIM compliance).

Returns:

Dictionary containing detailed check results:
  • domain: The domain that was checked

  • records: Dict mapping record types to lists of resolved records

  • errors: Dict mapping record types to error messages (if any)

  • response_times: Dict mapping record types to response times in ms

  • validations: Dict containing validation results:
    • mx: MX validation result (if validate_mx=True and MX records exist)

    • txt: TXT validation result (if validate_txt=True, TXT records exist)

Return type:

DetailedCheckResult

Examples

>>> # Basic check with default record types
>>> result = check_dns("example.com")
>>> if "A" in result["records"]:
...     print(f"A records: {result['records']['A']}")
>>> # Check specific record types with validation
>>> result = check_dns(
...     "example.com",
...     record_types=["MX", "TXT"],
...     validate_mx=True,
...     validate_txt=True,
... )
>>> if "validations" in result:
...     mx_valid = result["validations"].get("mx", {})
...     if not mx_valid.get("valid", True):
...         print("MX issues:", mx_valid.get("issues", []))
>>> # Check with custom nameserver
>>> result = check_dns(
...     "example.com", nameserver="1.1.1.1", record_types=["A", "AAAA"]
... )
>>> for rtype, time in result["response_times"].items():
...     print(f"{rtype} resolved in {time}ms")

Notes

  • Response times are in milliseconds, rounded to 2 decimal places

  • Records without trailing dots for consistency

  • MX validation checks for duplicate priorities

  • TXT validation checks SPF for missing all and DKIM for missing public key

  • Errors are recorded per record type if resolution fails

nadzoring.dns_lookup.check_dns_poisoning(domain: str, control_server: str = '8.8.8.8', test_servers: list[str] | None = None, record_type: str = 'A', additional_types: list[str] | None = None) PoisoningCheckResult[source]

Check for signs of DNS poisoning, censorship, or manipulation.

Comprehensive DNS poisoning detection by comparing responses from multiple DNS resolvers against a trusted control resolver. Analyzes patterns, identifies inconsistencies, and provides confidence scoring.

Parameters:
  • domain – Domain name to test for poisoning (e.g., “example.com”).

  • control_server – Trusted DNS server IP to use as baseline comparison. Defaults to Google DNS (8.8.8.8).

  • test_servers – List of DNS server IPs to test. If None, uses get_public_dns_servers() for a comprehensive list.

  • record_type – DNS record type to query for poisoning detection. Defaults to “A” records.

  • additional_types – Optional list of additional record types to query from the control server for extra context.

Returns:

Comprehensive poisoning analysis containing:
  • domain: The tested domain

  • record_type: The record type queried

  • control_server: IP of control server used

  • control_result: DNSResult from control server

  • test_results: Dict mapping test servers to their DNSResults

  • inconsistencies: List of detected inconsistencies

  • poisoned: Boolean indicating poisoning detection

  • poisoning_level: Severity level (“NONE”, “LOW”, “MEDIUM”,

    ”HIGH”, “CRITICAL”, “SUSPICIOUS”)

  • confidence: Confidence score (0-100)

  • Additional metrics including CDN detection, geo-diversity, consensus analysis, and more

Return type:

PoisoningCheckResult

Examples

>>> # Basic poisoning check
>>> result = check_dns_poisoning("example.com")
>>> if result["poisoned"]:
...     print(f"Poisoning detected! Confidence: {result['confidence']}%")
>>> # Check multiple record types
>>> result = check_dns_poisoning("example.com", additional_types=["MX", "TXT"])
>>> # Custom test servers
>>> result = check_dns_poisoning(
...     "example.com", test_servers=["1.1.1.1", "9.9.9.9"]
... )

Notes

  • High confidence (>80%) with mismatches indicates probable poisoning

  • CDN variations are flagged as informational, not poisoning

  • Geographic diversity of test servers improves detection accuracy

  • Timeout errors are logged but don’t affect poisoning detection

nadzoring.dns_lookup.compare_dns_servers(domain: str, servers: list[str], record_types: list[str], progress_callback: Callable[[], None] | None = None) ServerComparisonResult[source]

Compare DNS responses from multiple servers for the same domain.

Queries multiple DNS servers for the same set of record types and identifies differences in responses. Uses the first server in the list as the baseline for comparison.

Parameters:
  • domain – Domain name to query (e.g., “example.com”).

  • servers – List of DNS server IP addresses to compare. The first server in the list is used as the baseline.

  • record_types – List of DNS record types to query for each server (e.g., [“A”, “MX”, “TXT”]).

  • progress_callback – Optional callback function called after each successful query to report progress. Useful for UI progress bars.

Returns:

Dictionary containing comparison results:
  • domain: The domain that was queried

  • servers: Nested dictionary mapping server IPs to their results:
    {
    “server_ip”: {

    “A”: DNSResult, “MX”: DNSResult, …

    }

  • differences: List of detected differences, each containing:
    • server: IP of the differing server

    • type: Record type that differs

    • expected: Records from baseline server

    • got: Records from this server

    • ttl_difference: TTL difference if applicable

Return type:

ServerComparisonResult

Examples

>>> # Basic comparison
>>> result = compare_dns_servers(
...     "example.com",
...     servers=["8.8.8.8", "1.1.1.1", "9.9.9.9"],
...     record_types=["A", "MX"],
... )
>>> for diff in result["differences"]:
...     print(f"{diff['server']}: {diff['type']} differs")
...     print(f"  Expected: {diff['expected']}")
...     print(f"  Got: {diff['got']}")
>>> # With progress tracking
>>> def update_progress():
...     print(".", end="", flush=True)
>>>
>>> result = compare_dns_servers(
...     "example.com",
...     servers=["8.8.8.8", "1.1.1.1", "9.9.9.9"],
...     record_types=["A", "AAAA", "MX"],
...     progress_callback=update_progress,
... )
>>> # Check if all servers agree
>>> if not result["differences"]:
...     print("All servers returned identical results")
>>> else:
...     print(f"Found {len(result['differences'])} differences")

Notes

  • The first server in the list serves as the baseline for all comparisons

  • TTL values are included in the DNSResult objects but not compared by default

  • Differences are recorded only when the actual records differ, not TTLs

  • Each query includes TTL information (include_ttl=True)

  • The progress callback is called after each successful query, allowing for accurate progress tracking in UIs

  • Empty responses (no records) are considered valid and compared normally

  • Error responses are included in the comparison and may cause differences

nadzoring.dns_lookup.health_check_dns(domain: str, nameserver: str | None = None) HealthCheckResult[source]

Perform a comprehensive DNS health check with scoring.

Evaluates the health of a domain’s DNS configuration by checking multiple record types, calculating individual scores, and producing an overall health score and status.

Parameters:
  • domain – Domain name to check (e.g., “example.com”).

  • nameserver – Optional specific nameserver IP to use for queries. If None, uses system default resolvers.

Returns:

Dictionary containing health check results:
  • domain: The domain that was checked

  • score: Overall health score (0-100)

  • status: Health status (‘healthy’, ‘degraded’, ‘unhealthy’)

  • issues: List of critical issues found during checks

  • warnings: List of non-critical warnings found

  • record_scores: Dict with scores for each record type:
    • A: IPv4 address records score

    • AAAA: IPv6 address records score

    • MX: Mail exchange records score

    • NS: Nameserver records score

    • TXT: Text records score

    • CNAME: Canonical name records score (if applicable)

Return type:

HealthCheckResult

Examples

>>> # Basic health check
>>> result = health_check_dns("example.com")
>>> print(f"Health score: {result['score']} - {result['status']}")
>>> for rtype, score in result["record_scores"].items():
...     print(f"  {rtype}: {score}")
>>> # Using specific nameserver
>>> result = health_check_dns("example.com", nameserver="8.8.8.8")
>>> if result["issues"]:
...     print("Issues found:", result["issues"])

Notes

  • Checks all major record types: A, AAAA, MX, NS, TXT, CNAME

  • CNAME records are only scored for subdomains (as per DNS standards)

  • Scores are calculated using validation rules from validation module

  • The final score is the average of all non-CNAME record scores

  • Status is determined by the overall score:
    • >= 80: healthy

    • 50-79: degraded

    • < 50: unhealthy

nadzoring.dns_lookup.resolve_dns(domain: str, record_type: Literal['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'PTR', 'SOA', 'DNSKEY'] = 'A', nameserver: str | None = None, *, include_ttl: bool = False, timeout: float = 5.0, lifetime: float = 10.0) DNSResult

Perform DNS resolution with timing information and error handling.

Resolves a domain name for a specific record type, measuring response time and optionally capturing TTL information. Handles common DNS errors gracefully.

Parameters:
  • domain – Domain name to resolve (e.g., “example.com”).

  • record_type – DNS record type to query (e.g., “A”, “MX”, “TXT”). Defaults to “A”.

  • nameserver – Optional specific nameserver IP to use for resolution. If None, uses system default.

  • include_ttl – Whether to include TTL (Time To Live) value in result. Defaults to False.

  • timeout – Query timeout in seconds for each nameserver. Defaults to 5.0.

  • lifetime – Total query lifetime in seconds, including retries. Defaults to 10.0.

Returns:

Dictionary containing resolution results with structure:
  • domain: The queried domain name

  • record_type: The queried record type

  • records: List of resolved records (empty if resolution failed)

  • ttl: TTL in seconds if include_ttl=True and available, else None

  • error: Error message string if resolution failed, else None

  • response_time: Query response time in milliseconds, rounded to 2 decimals

Return type:

DNSResult

Example

>>> result = resolve_with_timer("example.com", "MX", include_ttl=True)
>>> if not result["error"]:
...     print(f"Records: {result['records']}, TTL: {result['ttl']}")
nadzoring.dns_lookup.reverse_dns(ip_address: str, nameserver: str | None = None) dict[str, str | float | None][source]

Perform a reverse DNS lookup to resolve an IP address to a hostname.

Queries the PTR (Pointer) record for a given IP address to find the associated domain name. This is the reverse of a forward DNS lookup.

Parameters:
  • ip_address – IPv4 or IPv6 address to look up (e.g., “8.8.8.8” or “2001:4860:4860::8888”).

  • nameserver – Optional specific nameserver IP address to use for the query. If None, uses the system default resolvers.

Returns:

A dictionary containing:
  • ip_address (str): The original IP address that was queried.

  • hostname (Optional[str]): The resolved hostname if found, with trailing dot removed. None if resolution failed.

  • error (Optional[str]): Error message if lookup failed, None for successful lookups.

  • response_time (Optional[float]): Query response time in milliseconds, rounded to 2 decimal places. None if the query failed before timing could be recorded.

Return type:

Dict[str, Union[str, float, None]]

Examples

>>> # Successful reverse lookup
>>> result = reverse_dns("8.8.8.8")
>>> print(result["hostname"])
'dns.google'
>>> print(f"Resolved in {result['response_time']}ms")
>>> # Failed reverse lookup
>>> result = reverse_dns("192.168.1.1")
>>> print(result["error"])
'No PTR record'
>>> # Using specific nameserver
>>> result = reverse_dns("1.1.1.1", nameserver="9.9.9.9")

Notes

  • The function handles both IPv4 and IPv6 addresses automatically using dns.reversename.from_address().

  • Common errors include:
    • “No PTR record”: IP exists but has no reverse DNS configured

    • “No reverse DNS”: IP range has no reverse delegation

    • “Query timeout”: DNS server didn’t respond in time

  • Trailing dots are automatically removed from hostnames for consistency with forward lookup formats.

  • Debug logs are generated for failed lookups to aid troubleshooting.

nadzoring.dns_lookup.trace_dns(domain: str, nameserver: str | None = None) dict[str, Any][source]

Trace the complete DNS resolution path for a domain.

Performs a DNS trace following the delegation chain from root servers to authoritative nameservers, similar to dig +trace functionality.

Parameters:
  • domain – Domain name to trace (e.g., “example.com”).

  • nameserver – Optional starting nameserver IP. If None, starts from root server (198.41.0.4 - a.root-servers.net).

Returns:

Trace result containing:
  • domain: The domain that was traced

  • hops: List of hop dictionaries, each representing a nameserver

    queried along the path

  • final_answer: The hop dictionary containing the final answer

    (None if resolution failed)

Return type:

Dict[str, Any]

Example

>>> result = trace_dns("example.com")
>>> for i, hop in enumerate(result["hops"]):
...     print(f"Hop {i + 1}: {hop['nameserver']} ({hop['response_time']}ms)")
>>> if result["final_answer"]:
...     print(f"Final answer: {result['final_answer']['records']}")

Notes

  • Maximum hops limited to 30 to prevent infinite loops

  • Detects and reports loops in delegation chain

  • Tracks visited nameservers to avoid repetition

  • Gracefully handles delegation failures and errors

Submodules