Error Handling¶
Every public Python API in Nadzoring follows one of two contracts:
Result-dict pattern — functions return a typed dict that always contains an
"error"field. The function never raises on DNS or network failures; exceptions are caught internally and surfaced as human-readable strings.Return-None pattern — functions return
None(or an empty container) when an operation cannot be completed.
This page documents both patterns, their error values, and recommended handling idioms.
DNS Result-Dict Pattern¶
All functions in nadzoring.dns_lookup that query nameservers return
a DNSResult dict. The contract is:
result["error"] is None→ success;result["records"]is populatedresult["error"] is not None→ failure;result["records"]is[]
Possible error strings:
Error value |
Meaning |
|---|---|
|
NXDOMAIN — the domain is not registered |
|
The domain exists but has no records of the queried type |
|
The nameserver did not respond within the timeout |
arbitrary string |
Unexpected |
from nadzoring.dns_lookup.utils import resolve_with_timer
result = resolve_with_timer("example.com", "A")
if result["error"]:
print("DNS error:", result["error"])
# "Domain does not exist" — check spelling
# "No A records" — try a different record type
# "Query timeout" — try a different nameserver
else:
for record in result["records"]:
print(record)
print(f"RTT: {result['response_time']} ms")
Reverse DNS¶
reverse_dns() uses the same pattern:
from nadzoring.dns_lookup.reverse import reverse_dns
result = reverse_dns("8.8.8.8")
if result["error"]:
# "No PTR record" — IP has no reverse entry
# "No reverse DNS" — NXDOMAIN on reverse zone
# "Query timeout"
# "Invalid IP address: …" — malformed input
hostname = f"[{result['error']}]"
else:
hostname = result["hostname"]
print(f"8.8.8.8 → {hostname}")
Health Check¶
health_check_dns() always returns a
complete dict — it never fails outright. Check "status" and
iterate "issues" / "warnings":
from nadzoring.dns_lookup.health import health_check_dns
health = health_check_dns("example.com")
print(f"Score: {health['score']}/100 Status: {health['status']}")
# status: "healthy" (>=80) | "degraded" (50-79) | "unhealthy" (<50)
for issue in health["issues"]:
print(" CRITICAL:", issue)
for warning in health["warnings"]:
print(" WARN:", warning)
for rtype, score in health["record_scores"].items():
print(f" {rtype}: {score}/100")
DNS Comparison¶
compare_dns_servers() returns a
ServerComparisonResult.
Iterate "differences" to find discrepancies:
from nadzoring.dns_lookup.compare import compare_dns_servers
result = compare_dns_servers(
"example.com",
servers=["8.8.8.8", "1.1.1.1", "9.9.9.9"],
record_types=["A", "MX"],
)
if not result["differences"]:
print("All servers agree")
else:
for diff in result["differences"]:
print(
f"Server {diff['server']} returned different"
f" {diff['type']} records:"
)
print(f" Expected (baseline): {diff['expected']}")
print(f" Got: {diff['got']}")
if diff["ttl_difference"] is not None:
print(f" TTL delta: {diff['ttl_difference']}s")
DNS Poisoning¶
check_dns_poisoning() returns a
PoisoningCheckResult.
Severity levels in ascending order: NONE → LOW → MEDIUM →
HIGH → CRITICAL / SUSPICIOUS.
from nadzoring.dns_lookup.poisoning import check_dns_poisoning
result = check_dns_poisoning("example.com")
level = result.get("poisoning_level", "NONE")
confidence = result.get("confidence", 0.0)
print(f"Level: {level} Confidence: {confidence:.0f}%")
if result.get("poisoned"):
for inc in result.get("inconsistencies", []):
print("Inconsistency:", inc)
if result.get("cdn_detected"):
print(f"CDN detected: {result['cdn_owner']}")
Geolocation¶
geo_ip() returns an empty
dict {} on failure:
from nadzoring.network_base.geolocation_ip import geo_ip
result = geo_ip("8.8.8.8")
if not result:
print("Geolocation unavailable (private IP, rate-limit, or error)")
else:
print(f"{result['city']}, {result['country']}")
print(f"Coordinates: {result['lat']}, {result['lon']}")
Ping¶
ping_addr() returns a plain
bool. Note that ICMP may be blocked by firewalls even for reachable
hosts:
from nadzoring.network_base.ping_address import ping_addr
if ping_addr("8.8.8.8"):
print("Host is reachable")
else:
print("No ICMP reply (host may still be up)")
ARP Cache¶
ARPCache raises
ARPCacheRetrievalError when the underlying
system command fails:
from nadzoring.arp.cache import ARPCache, ARPCacheRetrievalError
try:
cache = ARPCache()
entries = cache.get_cache()
except ARPCacheRetrievalError as exc:
print("Cannot read ARP cache:", exc)
else:
for entry in entries:
print(f"{entry.ip_address} {entry.mac_address} {entry.interface}")
HTTP Ping¶
http_ping() never raises;
check the error attribute:
from nadzoring.network_base.http_ping import http_ping
result = http_ping("https://example.com")
if result.error:
print("HTTP probe failed:", result.error)
else:
print(f"Status: {result.status_code}")
print(f"DNS: {result.dns_ms} ms")
print(f"TTFB: {result.ttfb_ms} ms")
print(f"Total: {result.total_ms} ms")
Batch Processing¶
When processing many hosts, collect errors instead of stopping:
from nadzoring.dns_lookup.utils import resolve_with_timer
domains = ["example.com", "nonexistent.invalid", "google.com"]
errors: list[dict] = []
results: list[dict] = []
for domain in domains:
result = resolve_with_timer(domain, "A")
if result["error"]:
errors.append({"domain": domain, "error": result["error"]})
else:
results.append(result)
print(f"Resolved: {len(results)}/{len(domains)}")
for err in errors:
print(f" FAILED: {err['domain']} — {err['error']}")