diff options
Diffstat (limited to 'src/dns_updater.rs')
-rw-r--r-- | src/dns_updater.rs | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/src/dns_updater.rs b/src/dns_updater.rs new file mode 100644 index 0000000..06ba408 --- /dev/null +++ b/src/dns_updater.rs @@ -0,0 +1,98 @@ +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::sync::Arc; + +use anyhow::{anyhow, bail, Result}; +use log::*; +use tokio::select; +use tokio::sync::watch; + +use crate::dns_config::*; +use crate::provider::DnsProvider; + +pub async fn dns_updater_task( + mut rx_dns_config: watch::Receiver<Arc<DnsConfig>>, + provider: Box<dyn DnsProvider>, + allowed_domains: Vec<String>, + mut must_exit: watch::Receiver<bool>, +) { + let mut config = Arc::new(DnsConfig::new()); + while !*must_exit.borrow() { + select!( + c = rx_dns_config.changed() => { + if c.is_err() { + break; + } + } + _ = must_exit.changed() => continue, + ); + let new_config: Arc<DnsConfig> = rx_dns_config.borrow().clone(); + + for (k, v) in new_config.entries.iter() { + if config.entries.get(k) != Some(v) { + let fulldomain = format!("{}.{}", k.subdomain, k.domain); + if !allowed_domains.iter().any(|d| fulldomain.ends_with(d)) { + error!( + "Got an entry for domain {} which is not in allowed list", + k.domain + ); + continue; + } + + info!("Updating {} {}", k, v); + if let Err(e) = update_dns_entry(k, v, provider.as_ref()).await { + error!("Unable to update entry {} {}: {}", k, v, e); + } + } + } + + config = new_config; + } +} + +async fn update_dns_entry( + key: &DnsEntryKey, + value: &DnsEntryValue, + provider: &dyn DnsProvider, +) -> Result<()> { + if value.targets.is_empty() { + bail!("zero targets (internal error)"); + } + + match key.record_type { + DnsRecordType::A => { + let mut targets = vec![]; + for tgt in value.targets.iter() { + targets.push( + tgt.parse::<Ipv4Addr>() + .map_err(|_| anyhow!("Invalid ipv4 address: {}", tgt))?, + ); + } + provider + .update_a(&key.domain, &key.subdomain, &targets) + .await?; + } + DnsRecordType::AAAA => { + let mut targets = vec![]; + for tgt in value.targets.iter() { + targets.push( + tgt.parse::<Ipv6Addr>() + .map_err(|_| anyhow!("Invalid ipv6 address: {}", tgt))?, + ); + } + provider + .update_aaaa(&key.domain, &key.subdomain, &targets) + .await?; + } + DnsRecordType::CNAME => { + let mut targets = value.targets.iter().cloned().collect::<Vec<_>>(); + if targets.len() > 1 { + targets.sort(); + warn!("Several CNAME targets for {}: {:?}. Taking first one in alphabetical order. Consider switching to a single global target instead.", key, targets); + } + provider + .update_cname(&key.domain, &key.subdomain, &targets[0]) + .await?; + } + } + Ok(()) +} |