#!/bin/bash # Redundant DNS/NTP Validation Test # Validates that the host is configured to use the redundant pfv-netinfra-01/02 # pair for name resolution and time, and that both servers actually answer. set -euo pipefail PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # The authoritative pair (pfv-netinfra-01 / pfv-netinfra-02). DNS_PRIMARY="192.168.3.252" DNS_SECONDARY="192.168.3.253" NTP_PRIMARY="192.168.3.252" NTP_SECONDARY="192.168.3.253" RESOLV_CONF="/etc/resolv.conf" NTP_CONF="/etc/ntpsec/ntp.conf" # A name every recursive resolver must be able to resolve. DNS_PROBE_NAME="github.com" failed=0 have() { command -v "$1" >/dev/null 2>&1; } # --- Configuration assertions ------------------------------------------------- function test_dns_config_present() { echo "🔍 Checking $RESOLV_CONF ..." local problems=0 if [[ -L "$RESOLV_CONF" ]]; then echo "❌ $RESOLV_CONF is a symlink (would be overwritten by a resolver manager)" ((++problems)) elif [[ ! -f "$RESOLV_CONF" ]]; then echo "❌ $RESOLV_CONF missing" ((++problems)) fi for ns in "$DNS_PRIMARY" "$DNS_SECONDARY"; do if grep -Eq "^[[:space:]]*nameserver[[:space:]]+$ns" "$RESOLV_CONF" 2>/dev/null; then echo "✅ nameserver $ns configured" else echo "❌ nameserver $ns NOT in $RESOLV_CONF" ((++problems)) fi done return $problems } function test_ntp_config_present() { echo "🔍 Checking $NTP_CONF ..." if [[ ! -f "$NTP_CONF" ]]; then echo "❌ $NTP_CONF missing (is ntpsec installed?)" return 1 fi local problems=0 for s in "$NTP_PRIMARY" "$NTP_SECONDARY"; do if grep -Eq "^[[:space:]]*(server|pool)[[:space:]]+$s" "$NTP_CONF"; then echo "✅ NTP server $s configured" else echo "❌ NTP server $s NOT in $NTP_CONF" ((++problems)) fi done return $problems } # --- Functional assertions: each server actually answers ---------------------- function _dns_resolves() { # $1 = server ip. Returns 0 if it resolves DNS_PROBE_NAME. local server="$1" if have dig; then dig @"$server" +short +time=4 +tries=1 "$DNS_PROBE_NAME" A 2>/dev/null | grep -Eq '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' elif have nslookup; then nslookup "$DNS_PROBE_NAME" "$server" 2>/dev/null | grep -Eq 'Address:[[:space:]]*[0-9]' elif have host; then host "$DNS_PROBE_NAME" "$server" 2>/dev/null | grep -Eq 'has address' else # Last resort: the resolver itself. getent ahostsv4 "$DNS_PROBE_NAME" >/dev/null 2>&1 fi } function test_dns_servers_answer() { echo "🔍 Probing DNS servers ..." local problems=0 for ns in "$DNS_PRIMARY" "$DNS_SECONDARY"; do if _dns_resolves "$ns"; then echo "✅ $ns resolves $DNS_PROBE_NAME" else echo "❌ $ns did not resolve $DNS_PROBE_NAME" ((++problems)) fi done return $problems } function _ntp_answers() { # $1 = server ip. Returns 0 if it responds to a time query. local server="$1" if have ntpdate; then timeout 8 ntpdate -q "$server" 2>/dev/null | grep -Eq 'no-leap|leap' elif have sntp; then timeout 8 sntp -t 4 "$server" >/dev/null 2>&1 elif have chronyc; then # NTS/chrony not expected here, but be tolerant. chronyc -n -h "$server" tracking >/dev/null 2>&1 else return 2 # cannot test fi } function test_ntp_servers_answer() { echo "🔍 Probing NTP servers ..." local problems=0 for s in "$NTP_PRIMARY" "$NTP_SECONDARY"; do if _ntp_answers "$s"; then echo "✅ NTP $s responds to time query" else echo "❌ $s did not respond to NTP query" ((++problems)) fi done return $problems } # --- End-to-end: the host is actually USING the pair -------------------------- function test_resolver_endtoend() { echo "🔍 End-to-end resolution via $RESOLV_CONF ..." if getent ahostsv4 "$DNS_PROBE_NAME" >/dev/null 2>&1; then echo "✅ Host resolves $DNS_PROBE_NAME via configured resolver" return 0 else echo "❌ Host cannot resolve $DNS_PROBE_NAME via configured resolver" return 1 fi } function test_ntp_daemon_peers() { echo "🔍 NTP daemon peer list ..." local peers if have ntpq; then peers="$(ntpq -pn 2>/dev/null || true)" elif have chronyc; then peers="$(chronyc -n sources 2>/dev/null || true)" else echo "⚠️ No ntpq/chronyc available; skipping daemon peer check" return 0 fi local problems=0 for s in "$NTP_PRIMARY" "$NTP_SECONDARY"; do if echo "$peers" | grep -Eq "^\\s*${s//./\\.}"; then echo "✅ NTP daemon has peer $s" else echo "❌ NTP daemon is NOT tracking $s" ((++problems)) fi done # Sync status is informational only: a freshly started daemon needs several # polls before the reach counter stabilises, so we warn rather than fail. if echo "$peers" | grep -Eq '\*'; then echo "✅ NTP daemon reports a synced peer" else echo "⚠️ NTP daemon not yet synced (normal for a few minutes after restart)" fi return $problems } # --- Main --------------------------------------------------------------------- function main() { echo "🛰️ Running Redundant DNS/NTP Validation Tests" echo "================================================" local total_failures=0 test_dns_config_present || ((++total_failures)) test_ntp_config_present || ((++total_failures)) test_dns_servers_answer || ((++total_failures)) test_ntp_servers_answer || ((++total_failures)) test_resolver_endtoend || ((++total_failures)) test_ntp_daemon_peers || ((++total_failures)) echo "================================================" if [[ $total_failures -eq 0 ]]; then echo "✅ All redundant DNS/NTP validation tests passed" exit 0 else echo "❌ $total_failures redundant DNS/NTP tests failed" exit 1 fi } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi