//! Background DNS record check task. use tokio::task::JoinHandle; use tracing::info; use pom::alerts::Alerter; use pom::checks::dns; use pom::config::Config; use pom::db; use super::{CheckInterval, configured_targets}; pub(crate) fn spawn_dns_tasks( config: &Config, pool: &sqlx::SqlitePool, cancel: &tokio_util::sync::CancellationToken, alerter: Option<&Alerter>, ) -> Vec> { let dns_interval_secs = config.serve.dns_check_interval_secs; let mut handles = Vec::new(); for (name, target_config) in configured_targets(config) { if target_config.dns.is_empty() { continue; } let dns_records = target_config.dns.clone(); let label = target_config.label.clone(); let pool = pool.clone(); let alerter = alerter.cloned(); let cancel = cancel.clone(); let n = dns_records.len(); info!("{name}: DNS check every {dns_interval_secs}s ({n} records)"); handles.push(tokio::spawn(async move { // Prune stale DNS data from DB (records removed from config) let expected_dns_keys: Vec<(String, String)> = dns_records .iter() .map(|d| (d.name.clone(), d.record_type.to_string())) .collect(); match db::prune_stale_dns(&pool, &name, &expected_dns_keys).await { Ok(0) => {} Ok(n) => info!("{name}: pruned {n} stale DNS check rows"), Err(e) => tracing::error!("{name}: failed to prune stale DNS: {e}"), } let mut ticks = CheckInterval::new(dns_interval_secs, cancel); let mut prev_mismatched: std::collections::HashSet<( String, pom::types::DnsRecordType, )> = std::collections::HashSet::new(); while ticks.next().await { let results = dns::check_dns(&name, &dns_records).await; for result in &results { if let Err(e) = db::insert_dns_check(&pool, result).await { tracing::error!( "{}: failed to store DNS check for {} {}: {e}", name, result.name, result.record_type ); } } let current_mismatched: std::collections::HashSet<( String, pom::types::DnsRecordType, )> = results .iter() .filter(|r| !r.matches) .map(|r| (r.name.clone(), r.record_type)) .collect(); let ok_count = results.iter().filter(|r| r.matches).count(); info!("{name}: DNS {ok_count}/{n} match"); if let Some(ref alerter) = alerter { // New mismatches let new_mismatches: Vec<&pom::types::DnsCheckResult> = results .iter() .filter(|r| { !r.matches && !prev_mismatched.contains(&(r.name.clone(), r.record_type)) }) .collect(); if !new_mismatches.is_empty() { let owned: Vec = new_mismatches.into_iter().cloned().collect(); alerter.send_dns_mismatch_alert(&name, &label, &owned).await; } // All recovered if !prev_mismatched.is_empty() && current_mismatched.is_empty() { alerter.send_dns_recovery_alert(&name, &label).await; } } prev_mismatched = current_mismatched; } })); } handles }