update dependencies & rust edition; add better logging
This commit is contained in:
2447
Cargo.lock
generated
2447
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@@ -1,16 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "r53-ddns"
|
name = "r53-ddns"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aws-config = { version = "1.1.7", features = ["behavior-version-latest"] }
|
aws-config = "1"
|
||||||
aws-sdk-route53 = "1.37.0"
|
aws-sdk-route53 = "1"
|
||||||
clap = { version = "4.5.11", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
hickory-resolver = "0.25"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
reqwest = { version = "0.12.5", features = ["blocking", "json"] }
|
reqwest = { version = "0.13", features = ["blocking", "json", "rustls"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
trust-dns-proto = "0.23.2"
|
|
||||||
trust-dns-resolver = "0.23.2"
|
|
||||||
79
src/dns.rs
79
src/dns.rs
@@ -1,38 +1,60 @@
|
|||||||
|
use hickory_resolver::Resolver;
|
||||||
|
use hickory_resolver::config::{ResolverConfig, ResolverOpts};
|
||||||
|
use hickory_resolver::name_server::TokioConnectionProvider;
|
||||||
|
use log::info;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use log::info;
|
pub struct DnsResolver {
|
||||||
use trust_dns_proto::rr::record_type::RecordType;
|
resolver: Resolver<TokioConnectionProvider>,
|
||||||
use trust_dns_proto::rr::RecordData;
|
}
|
||||||
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
|
|
||||||
use trust_dns_resolver::TokioAsyncResolver;
|
|
||||||
|
|
||||||
pub async fn is_addr_current(domain: &str, ip_addr: IpAddr) -> Result<bool, Box<dyn Error>> {
|
impl DnsResolver {
|
||||||
let response = TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), ResolverOpts::default())
|
pub fn build() -> Self {
|
||||||
.lookup(
|
Self {
|
||||||
domain,
|
resolver: Resolver::builder_with_config(
|
||||||
match ip_addr {
|
ResolverConfig::cloudflare(),
|
||||||
IpAddr::V4(_) => RecordType::A,
|
TokioConnectionProvider::default(),
|
||||||
IpAddr::V6(_) => RecordType::AAAA,
|
)
|
||||||
},
|
.with_options(ResolverOpts::default())
|
||||||
)
|
.build(),
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut record_ip: Option<IpAddr> = None;
|
|
||||||
for record in response.into_iter() {
|
|
||||||
record_ip = record.into_rdata().ip_addr();
|
|
||||||
if !record_ip.is_none() && record_ip == Some(ip_addr) {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
pub async fn is_addr_current(
|
||||||
"dynamic ip drift detected: {} -> {}",
|
&self,
|
||||||
record_ip.unwrap(),
|
domain: &str,
|
||||||
ip_addr
|
ip_addr: IpAddr,
|
||||||
);
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
|
let dns_ip = match ip_addr {
|
||||||
|
IpAddr::V4(_) => self
|
||||||
|
.resolver
|
||||||
|
.ipv4_lookup(domain)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|r| IpAddr::V4(r.0))
|
||||||
|
.next(),
|
||||||
|
IpAddr::V6(_) => self
|
||||||
|
.resolver
|
||||||
|
.ipv6_lookup(domain)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|r| IpAddr::V6(r.0))
|
||||||
|
.next(),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(false)
|
if dns_ip == Some(ip_addr) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"dynamic ip drift detected: {} -> {}",
|
||||||
|
dns_ip.map_or("unknown".to_string(), |ip| ip.to_string()),
|
||||||
|
ip_addr
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -42,10 +64,11 @@ mod unit {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_is_addr_current() {
|
async fn test_is_addr_current() {
|
||||||
|
let resolver = super::DnsResolver::build();
|
||||||
let domain = "rskio.com";
|
let domain = "rskio.com";
|
||||||
let ip_addr = IpAddr::from_str("0.0.0.0").unwrap();
|
let ip_addr = IpAddr::from_str("0.0.0.0").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
super::is_addr_current(domain, ip_addr).await.unwrap(),
|
resolver.is_addr_current(domain, ip_addr).await.unwrap(),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/main.rs
31
src/main.rs
@@ -7,7 +7,9 @@ use log::info;
|
|||||||
use reqwest::get;
|
use reqwest::get;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{Duration, sleep};
|
||||||
|
|
||||||
|
const WEEK_SECS: u64 = 604_800; // 7 * 24 * 60 * 60
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(
|
#[clap(
|
||||||
@@ -41,19 +43,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut next_status_time = SystemTime::now();
|
let mut next_status_time = SystemTime::now();
|
||||||
|
let mut await_dns_sync = false;
|
||||||
|
|
||||||
|
let resolver = dns::DnsResolver::build();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let public_ip = get_public_ip().await?;
|
let public_ip = get_public_ip().await?;
|
||||||
|
|
||||||
// print the current public ip each week
|
|
||||||
if SystemTime::now() > next_status_time {
|
if SystemTime::now() > next_status_time {
|
||||||
info!("current public address is: {}", public_ip);
|
info!("current public address is: {}", public_ip);
|
||||||
next_status_time += Duration::from_secs(7 * 24 * 60 * 60);
|
next_status_time += Duration::from_secs(WEEK_SECS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update record if current public ip drifts from dns record
|
match resolver
|
||||||
if !dns::is_addr_current(&args.domain_name, public_ip).await? {
|
.is_addr_current(&args.domain_name, public_ip)
|
||||||
route53::update_record(&args.dns_zone_id, &args.domain_name, public_ip).await?;
|
.await?
|
||||||
|
{
|
||||||
|
true => {
|
||||||
|
if await_dns_sync {
|
||||||
|
info! {"dns updates propogated successfully"}
|
||||||
|
await_dns_sync = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
if !await_dns_sync {
|
||||||
|
route53::update_record(&args.dns_zone_id, &args.domain_name, public_ip).await?;
|
||||||
|
await_dns_sync = true;
|
||||||
|
} else {
|
||||||
|
info! {"awaiting dns propagation delay"};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(Duration::from_secs(args.seconds)).await;
|
sleep(Duration::from_secs(args.seconds)).await;
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ use std::error::Error;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use aws_config::meta::region::RegionProviderChain;
|
use aws_config::BehaviorVersion;
|
||||||
use aws_sdk_route53 as r53;
|
use aws_sdk_route53 as r53;
|
||||||
use aws_sdk_route53::types::{
|
use aws_sdk_route53::types::{
|
||||||
Change, ChangeAction, ChangeBatch, ResourceRecord, ResourceRecordSet,
|
Change, ChangeAction, ChangeBatch, ResourceRecord, ResourceRecordSet,
|
||||||
};
|
};
|
||||||
use log::info;
|
use log::info;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{Duration, sleep};
|
||||||
|
|
||||||
pub async fn update_record(
|
pub async fn update_record(
|
||||||
dns_zone_id: &str,
|
dns_zone_id: &str,
|
||||||
@@ -24,20 +24,14 @@ pub async fn update_record(
|
|||||||
let resource_record_set: Option<ResourceRecordSet> =
|
let resource_record_set: Option<ResourceRecordSet> =
|
||||||
get_single_record_set(&client, &dns_zone_id, &domain_name, &record_type).await?;
|
get_single_record_set(&client, &dns_zone_id, &domain_name, &record_type).await?;
|
||||||
|
|
||||||
match resource_record_set.is_none() {
|
match resource_record_set {
|
||||||
true => return Err(Box::new(Route53UpdateError::NoRecordAvailable)),
|
None => Err(Box::new(Route53UpdateError::NoRecordAvailable)),
|
||||||
false => {
|
Some(rrs) => {
|
||||||
info!(
|
info!(
|
||||||
"requesting update to route53 record for {} {} -> {}",
|
"requesting update to route53 record for {} {} -> {}",
|
||||||
record_type, domain_name, public_ip
|
record_type, domain_name, public_ip
|
||||||
);
|
);
|
||||||
return Ok(submit_single_change_request(
|
return Ok(submit_single_change_request(&client, rrs, &public_ip, &dns_zone_id).await?);
|
||||||
&client,
|
|
||||||
resource_record_set.unwrap(),
|
|
||||||
&public_ip,
|
|
||||||
&dns_zone_id,
|
|
||||||
)
|
|
||||||
.await?);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,10 +39,7 @@ pub async fn update_record(
|
|||||||
pub async fn get_client() -> Result<aws_sdk_route53::Client, Box<dyn Error>> {
|
pub async fn get_client() -> Result<aws_sdk_route53::Client, Box<dyn Error>> {
|
||||||
// get aws r53 client
|
// get aws r53 client
|
||||||
Ok(r53::Client::new(
|
Ok(r53::Client::new(
|
||||||
&aws_config::from_env()
|
&aws_config::load_defaults(BehaviorVersion::latest()).await,
|
||||||
.region(RegionProviderChain::default_provider())
|
|
||||||
.load()
|
|
||||||
.await,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user