From c72117e6e1e1c9825ca3c332e6a7a6cc5d90158e Mon Sep 17 00:00:00 2001 From: Antoine Van Elstraete Date: Mon, 26 Jan 2026 17:39:16 +0100 Subject: [PATCH] Add DNS checker Checks DNS server availability via ping then performs configurable DNS query (record_type: A, AAAA, TXT, MX, etc.). Co-Authored-By: Claude Opus 4.5 --- checkers/__init__.py | 2 ++ checkers/dns.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ config.yaml.example | 10 +++++++ requirements.txt | 1 + 4 files changed, 81 insertions(+) create mode 100644 checkers/dns.py diff --git a/checkers/__init__.py b/checkers/__init__.py index fdef1bb..91578bd 100644 --- a/checkers/__init__.py +++ b/checkers/__init__.py @@ -1,9 +1,11 @@ from .ping import PingChecker from .http import HttpChecker from .snmp import SnmpChecker +from .dns import DnsChecker CHECKERS = { "ping": PingChecker, "http": HttpChecker, "snmp": SnmpChecker, + "dns": DnsChecker, } diff --git a/checkers/dns.py b/checkers/dns.py new file mode 100644 index 0000000..61484e6 --- /dev/null +++ b/checkers/dns.py @@ -0,0 +1,68 @@ +import time + +import dns.resolver + +from .base import BaseChecker, CheckResult +from .ping import PingChecker + + +class DnsChecker(BaseChecker): + def check(self) -> CheckResult: + host = self.config["host"] + query_name = self.config["query"] + query_type = self.config.get("record_type", "A") + timeout = self.config.get("timeout", 5) + + # First check if host is reachable via ping + ping_checker = PingChecker(self.name, {"host": host, "timeout": timeout}) + ping_result = ping_checker.check() + + if not ping_result.success: + return CheckResult( + success=False, + message=f"DNS server unreachable: {ping_result.message}", + response_time=None + ) + + # Now perform DNS query + resolver = dns.resolver.Resolver() + resolver.nameservers = [host] + resolver.timeout = timeout + resolver.lifetime = timeout + + start = time.time() + try: + answers = resolver.resolve(query_name, query_type) + response_time = (time.time() - start) * 1000 # ms + + records = [str(rdata) for rdata in answers] + return CheckResult( + success=True, + message=f"DNS {query_type} query OK ({len(records)} record(s))", + response_time=response_time, + details={"records": records, "query": query_name, "type": query_type} + ) + except dns.resolver.NXDOMAIN: + return CheckResult( + success=False, + message=f"DNS query failed: {query_name} does not exist", + response_time=None + ) + except dns.resolver.NoAnswer: + return CheckResult( + success=False, + message=f"DNS query failed: no {query_type} record for {query_name}", + response_time=None + ) + except dns.resolver.Timeout: + return CheckResult( + success=False, + message="DNS query timeout", + response_time=None + ) + except Exception as e: + return CheckResult( + success=False, + message=f"DNS error: {e}", + response_time=None + ) diff --git a/config.yaml.example b/config.yaml.example index 3bbe778..3ecf042 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -50,3 +50,13 @@ checks: community: "public" oid: "1.3.6.1.2.1.1.1.0" # sysDescr interval: 120 + + # DNS checks + - id: pihole + name: "Pi-hole DNS" + type: dns + host: "192.168.1.10" + query: "google.com" + record_type: A # A, AAAA, TXT, MX, etc. + timeout: 5 + interval: 60 diff --git a/requirements.txt b/requirements.txt index 3ea5448..99d546c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ paho-mqtt>=2.0.0 PyYAML>=6.0 pysnmp>=4.4.12 requests>=2.31.0 +dnspython>=2.4.0