2023-01-11 21:35:56 +00:00
|
|
|
use std::collections::HashSet;
|
2022-12-07 14:35:12 +00:00
|
|
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
|
|
use std::sync::Arc;
|
2022-12-11 14:46:48 +00:00
|
|
|
use std::time::Duration;
|
2022-12-07 14:35:12 +00:00
|
|
|
|
|
|
|
use anyhow::{anyhow, bail, Result};
|
|
|
|
use tokio::select;
|
|
|
|
use tokio::sync::watch;
|
2022-12-11 14:46:48 +00:00
|
|
|
use tracing::*;
|
2022-12-07 14:35:12 +00:00
|
|
|
|
|
|
|
use crate::dns_config::*;
|
2022-12-11 15:29:06 +00:00
|
|
|
use crate::DomainProvider;
|
2022-12-07 14:35:12 +00:00
|
|
|
|
2023-01-11 21:35:56 +00:00
|
|
|
const RETRY_DELAY: Duration = Duration::from_secs(600); // 10 minutes
|
|
|
|
|
2022-12-07 14:35:12 +00:00
|
|
|
pub async fn dns_updater_task(
|
|
|
|
mut rx_dns_config: watch::Receiver<Arc<DnsConfig>>,
|
2022-12-11 15:29:06 +00:00
|
|
|
providers: Vec<DomainProvider>,
|
2022-12-07 14:35:12 +00:00
|
|
|
allowed_domains: Vec<String>,
|
|
|
|
mut must_exit: watch::Receiver<bool>,
|
|
|
|
) {
|
2022-12-11 15:29:06 +00:00
|
|
|
for dom in allowed_domains.iter() {
|
|
|
|
info!(domain = dom, "allowing subdomains of domain");
|
|
|
|
}
|
|
|
|
for prov in providers.iter() {
|
|
|
|
info!(
|
|
|
|
domain = prov.domain,
|
|
|
|
provider = prov.provider.provider(),
|
|
|
|
"got provider for domain"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-11 14:46:48 +00:00
|
|
|
info!("DNS updater starting");
|
|
|
|
|
2022-12-07 14:35:12 +00:00
|
|
|
let mut config = Arc::new(DnsConfig::new());
|
2023-01-11 21:35:56 +00:00
|
|
|
let mut failures = HashSet::new();
|
|
|
|
|
2022-12-07 14:35:12 +00:00
|
|
|
while !*must_exit.borrow() {
|
|
|
|
select!(
|
|
|
|
c = rx_dns_config.changed() => {
|
|
|
|
if c.is_err() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-01-11 21:35:56 +00:00
|
|
|
_ = tokio::time::sleep(RETRY_DELAY) => {
|
|
|
|
if failures.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2022-12-07 14:35:12 +00:00
|
|
|
_ = must_exit.changed() => continue,
|
|
|
|
);
|
2022-12-11 21:45:58 +00:00
|
|
|
|
|
|
|
// Always lag 15 seconds behind actual updates,
|
|
|
|
// to avoid sending too many at once and hitting rate limits
|
|
|
|
tokio::time::sleep(Duration::from_secs(15)).await;
|
|
|
|
|
|
|
|
let new_config: Arc<DnsConfig> = rx_dns_config.borrow_and_update().clone();
|
2023-01-11 21:35:56 +00:00
|
|
|
let mut new_failures = HashSet::new();
|
2022-12-07 14:35:12 +00:00
|
|
|
|
2022-12-11 15:29:06 +00:00
|
|
|
for (key, value) in new_config.entries.iter() {
|
2023-01-11 21:35:56 +00:00
|
|
|
if failures.contains(key) {
|
|
|
|
info!(
|
|
|
|
record = key.to_string(),
|
|
|
|
target = value.to_string(),
|
|
|
|
"retrying after failure"
|
|
|
|
);
|
|
|
|
} else if config.entries.get(key) == Some(value) {
|
|
|
|
// Skip entries that haven't changed, and that were
|
|
|
|
// successfully updated on the previous iteration
|
2022-12-11 15:29:06 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip entries for unallowed domains
|
2022-12-11 15:42:00 +00:00
|
|
|
if !allowed_domains
|
|
|
|
.iter()
|
|
|
|
.any(|d| key.dns_path == *d || key.dns_path.ends_with(&format!(".{}", d)))
|
|
|
|
{
|
2022-12-11 15:29:06 +00:00
|
|
|
error!(
|
|
|
|
domain = key.dns_path,
|
|
|
|
"domain/subdomain/hostname not in allowed list",
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-12-11 15:42:00 +00:00
|
|
|
let provider = providers.iter().find(|p| {
|
|
|
|
key.dns_path == p.domain || key.dns_path.ends_with(&format!(".{}", p.domain))
|
|
|
|
});
|
2022-12-11 15:29:06 +00:00
|
|
|
|
|
|
|
if let Some(provider) = provider {
|
|
|
|
if let Err(e) = update_dns_entry(key, value, provider).await {
|
2022-12-07 14:35:12 +00:00
|
|
|
error!(
|
2022-12-11 15:29:06 +00:00
|
|
|
record = key.to_string(),
|
|
|
|
target = value.to_string(),
|
|
|
|
error = e.to_string(),
|
2023-01-11 21:35:56 +00:00
|
|
|
"unable to update record, will retry later"
|
2022-12-07 14:35:12 +00:00
|
|
|
);
|
2023-01-11 21:35:56 +00:00
|
|
|
new_failures.insert(key.clone());
|
2022-12-07 14:35:12 +00:00
|
|
|
}
|
2022-12-11 15:29:06 +00:00
|
|
|
} else {
|
|
|
|
error!(
|
|
|
|
domain = key.dns_path,
|
|
|
|
"no provider matches this domain/subdomain/hostname"
|
|
|
|
);
|
2022-12-07 14:35:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
config = new_config;
|
2023-01-11 21:35:56 +00:00
|
|
|
failures = new_failures;
|
2022-12-07 14:35:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn update_dns_entry(
|
|
|
|
key: &DnsEntryKey,
|
|
|
|
value: &DnsEntryValue,
|
2022-12-11 15:29:06 +00:00
|
|
|
provider: &DomainProvider,
|
2022-12-07 14:35:12 +00:00
|
|
|
) -> Result<()> {
|
2022-12-11 15:42:00 +00:00
|
|
|
let subdomain = if key.dns_path == provider.domain {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(
|
|
|
|
key.dns_path
|
|
|
|
.strip_suffix(&format!(".{}", provider.domain))
|
|
|
|
.unwrap(),
|
|
|
|
)
|
|
|
|
};
|
2022-12-11 15:29:06 +00:00
|
|
|
info!(
|
|
|
|
record = key.to_string(),
|
|
|
|
target = value.to_string(),
|
|
|
|
domain = provider.domain,
|
|
|
|
subdomain = &subdomain,
|
|
|
|
provider = provider.provider.provider(),
|
|
|
|
"updating record"
|
|
|
|
);
|
|
|
|
|
2022-12-07 14:35:12 +00:00
|
|
|
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
|
2022-12-11 15:29:06 +00:00
|
|
|
.provider
|
2022-12-11 15:42:00 +00:00
|
|
|
.update_a(&provider.domain, subdomain, &targets)
|
2022-12-07 14:35:12 +00:00
|
|
|
.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
|
2022-12-11 15:29:06 +00:00
|
|
|
.provider
|
2022-12-11 15:42:00 +00:00
|
|
|
.update_aaaa(&provider.domain, subdomain, &targets)
|
2022-12-07 14:35:12 +00:00
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
DnsRecordType::CNAME => {
|
|
|
|
let mut targets = value.targets.iter().cloned().collect::<Vec<_>>();
|
|
|
|
if targets.len() > 1 {
|
|
|
|
targets.sort();
|
2022-12-11 15:29:06 +00:00
|
|
|
warn!(
|
|
|
|
record = key.to_string(),
|
|
|
|
all_targets = value.to_string(),
|
|
|
|
selected_target = targets[0],
|
|
|
|
"Several CNAME targets, taking first one in alphabetical order. Consider switching to a single global target instead."
|
|
|
|
);
|
2022-12-07 14:35:12 +00:00
|
|
|
}
|
|
|
|
provider
|
2022-12-11 15:29:06 +00:00
|
|
|
.provider
|
2022-12-11 15:42:00 +00:00
|
|
|
.update_cname(&provider.domain, subdomain, &targets[0])
|
2022-12-07 14:35:12 +00:00
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|