diff --git a/Cargo.toml b/Cargo.toml index efc8743..c3c59cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/chenxiaolong/ddns-updater" readme = "README.md" license = "GPL-3.0" version = "0.1.8" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index 10cbc50..97bc419 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The resulting executable will be in `target/release/ddns-updater` or `target\rel If an issue occurs, the best way to troubleshoot is to set `log_level` to `debug` or `trace` in the config file. The `debug` level includes information like the detected IP addresses, while the `trace` level will also print out the raw DNS update request and response. Note that the `trace` output is not safe to paste online because it includes a dump of the TSIG key. -To enable trace logging for everything, including the underlying trust-dns library, set the `RUST_LOG` environment variable to `trace`. +To enable trace logging for everything, including the underlying hickory-dns library, set the `RUST_LOG` environment variable to `trace`. ## Limitations diff --git a/config.sample.toml b/config.sample.toml index 7528c92..693d9ac 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -23,7 +23,7 @@ name_server = "" # # Due to a bug, using `tcp` will currently result in a 5 second delay after the # DDNS update response is received. See: -# https://github.com/bluejekyll/trust-dns/issues/1607 +# https://github.com/hickory-dns/hickory-dns/issues/1607 #protocol = "udp" # The interface to query IP addresses for populating A/AAAA records. If no diff --git a/src/config.rs b/src/config.rs index 37a076f..796ad1c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,20 +1,8 @@ -use { - std::{ - fmt, - path::Path, - time::Duration, - }, - serde::Deserialize, - serde_with::{ - base64::Base64, - serde_as, - DisplayFromStr, - }, - hickory_client::rr::{ - rdata::tsig::TsigAlgorithm, - Name, - }, -}; +use std::{fmt, path::Path, time::Duration}; + +use hickory_client::rr::{rdata::tsig::TsigAlgorithm, Name}; +use serde::Deserialize; +use serde_with::{base64::Base64, serde_as, DisplayFromStr}; const DEFAULT_TTL: u32 = 300; const DEFAULT_TIMEOUT: u64 = 5; diff --git a/src/dns.rs b/src/dns.rs index 2cc7da5..b2785d6 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -1,49 +1,46 @@ -use { - std::{ - future::Future, - net::{IpAddr, SocketAddr}, - pin::Pin, - time::Duration, +use hickory_client::{ + client::{AsyncClient, Client, SyncClient}, + error::ClientResult, + op::{Edns, Message, MessageType, OpCode, Query, UpdateMessage}, + proto::{ + error::ProtoError, + rr::{dnssec::tsig::TSigner, DNSClass, Name, RData, Record, RecordType}, + xfer::DnsExchangeSend, }, - hickory_client::{ - client::{AsyncClient, Client, SyncClient}, - error::ClientResult, - op::{Edns, Message, MessageType, OpCode, Query, UpdateMessage}, - proto::{ - error::ProtoError, - rr::{ - dnssec::tsig::TSigner, - DNSClass, Name, RData, Record, RecordType, - }, - xfer::DnsExchangeSend, - }, - tcp::TcpClientConnection, - udp::UdpClientConnection, - }, - crate::config::Protocol, + tcp::TcpClientConnection, + udp::UdpClientConnection, +}; + +use std::{ + future::Future, + net::{IpAddr, SocketAddr}, + pin::Pin, + time::Duration, }; -/// `trust_dns_client::client::NewFutureObj` is not public +use crate::config::Protocol; + +/// `hickory_client::client::NewFutureObj` is not public type NewFutureObj = Pin< Box< dyn Future< - Output = Result< - ( - H, - Box> + 'static + Send + Unpin>, - ), - ProtoError, - >, - > - + 'static - + Send, + Output = Result< + ( + H, + Box> + 'static + Send + Unpin>, + ), + ProtoError, + >, + > + + 'static + + Send, >, >; /// Small wrapper to avoid callers needing to distinguish between TCP/UDP. pub enum DnsClient { - Tcp(SyncClient::), - Udp(SyncClient::), + Tcp(SyncClient), + Udp(SyncClient), } impl DnsClient { @@ -87,9 +84,6 @@ pub fn replace_addrs_message( ttl: u32, addrs: &[IpAddr], ) -> Message { - // trust_dns_client::client::AsyncClient::MAX_PAYLOAD_LEN is not public - const MAX_PAYLOAD_LEN: u16 = 1232; - let mut zone = Query::new(); zone.set_name(zone_origin.clone()) .set_query_class(DNSClass::IN) @@ -121,7 +115,7 @@ pub fn replace_addrs_message( message .extensions_mut() .get_or_insert_with(Edns::new) - .set_max_payload(MAX_PAYLOAD_LEN) + .set_max_payload(hickory_client::proto::op::update_message::MAX_PAYLOAD_LEN) .set_version(0); message diff --git a/src/iface.rs b/src/iface.rs index b0890ab..be286a9 100644 --- a/src/iface.rs +++ b/src/iface.rs @@ -1,11 +1,10 @@ -use { - std::{ - io, - net::{IpAddr, SocketAddr, TcpStream}, - }, - netif::Interface, +use std::{ + io, + net::{IpAddr, SocketAddr, TcpStream}, }; +use netif::Interface; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Could not connect to: {0}: {1}")] @@ -24,19 +23,17 @@ pub struct Interfaces { impl Interfaces { pub fn new() -> Result { - let ifaces = netif::up() - .map_err(Error::QueryInterface)? - .collect(); + let ifaces = netif::up().map_err(Error::QueryInterface)?.collect(); - Ok(Self { - ifaces, - }) + Ok(Self { ifaces }) } pub fn get_addrs_by_name(&self, name: &str) -> Option> { let mut found = false; - let addrs = self.ifaces.iter() + let addrs = self + .ifaces + .iter() .filter(|iface| iface.name() == name) .inspect(|_| found = true) .map(|iface| *iface.address()) @@ -52,7 +49,8 @@ impl Interfaces { pub fn get_iface_by_tcp_source_ip(&self, server: SocketAddr) -> Result<&str> { let source_ip = Self::get_tcp_source_ip(server)?; - self.ifaces.iter() + self.ifaces + .iter() .find(|iface| *iface.address() == source_ip) .map(Interface::name) .ok_or(Error::InterfaceNotFound(source_ip)) diff --git a/src/main.rs b/src/main.rs index 3094d1b..0ba870e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,32 +3,29 @@ mod dns; mod iface; mod ip; -use { - std::{ - borrow::Cow, - ffi::OsString, - net::{SocketAddr, ToSocketAddrs}, - path::PathBuf, - str::FromStr, - sync::atomic::{AtomicBool, Ordering}, - }, - clap::Parser, - log::{debug, error, info, trace, warn}, - hickory_client::{ - client::Client, - error::ClientError, - op::{ResponseCode, UpdateMessage}, - proto::{ - error::ProtoError, - rr::{ - dnssec::tsig::TSigner, - DNSClass, Name, RecordType, - }, - }, +use std::{ + borrow::Cow, + ffi::OsString, + net::{SocketAddr, ToSocketAddrs}, + path::PathBuf, + str::FromStr, + sync::atomic::{AtomicBool, Ordering}, +}; + +use clap::Parser; +use hickory_client::{ + client::Client, + error::ClientError, + op::{ResponseCode, UpdateMessage}, + proto::{ + error::ProtoError, + rr::{dnssec::tsig::TSigner, DNSClass, Name, RecordType}, }, - iface::Interfaces, - crate::dns::DnsClient, }; +use iface::Interfaces; +use log::{debug, error, info, trace, warn}; + +use crate::dns::DnsClient; // Same as nsupdate const DEFAULT_FUDGE: u16 = 300; @@ -72,52 +69,58 @@ fn update_dns(server: SocketAddr, config: &config::Config) -> Result<()> { let iface = match &config.global.interface { Some(s) => s.as_str(), None => { - debug!("Autodetecting interface from source IP of TCP connection to {}", server); + debug!("Autodetecting interface from source IP of TCP connection to {server}"); ifaces.get_iface_by_tcp_source_ip(server)? } }; - debug!("Interface: {:?}", iface); + debug!("Interface: {iface:?}"); - let addrs = ifaces.get_addrs_by_name(iface) + let addrs = ifaces + .get_addrs_by_name(iface) .ok_or_else(|| Error::InterfaceNotFound(iface.to_string()))?; - let valid_addrs = addrs.into_iter() + let valid_addrs = addrs + .into_iter() .filter(|ip| ip::is_suitable_ip(*ip)) .collect::>(); - debug!("Addresses: {:?}", valid_addrs); + debug!("Addresses: {valid_addrs:?}"); let name = match &config.global.hostname { Some(n) => Cow::Borrowed(n), None => { let hostname = gethostname::gethostname(); - let hostname_str = hostname.to_str() + let hostname_str = hostname + .to_str() .ok_or_else(|| Error::InvalidHostnameUtf8(hostname.clone()))?; let name = Name::from_str(hostname_str) .map_err(|e| Error::InvalidHostnameDns(hostname.clone(), e))?; Cow::Owned(name) } }; - debug!("Hostname: {}", name); + debug!("Hostname: {name}"); let tsig = TSigner::new( config.tsig.secret.clone(), config.tsig.algorithm.into(), name.clone().into_owned(), DEFAULT_FUDGE, - ).unwrap(); + ) + .unwrap(); let client = DnsClient::new( server, config.global.protocol, config.global.timeout.to_duration(), tsig, - ).map_err(|e| Error::DnsClient(server, e))?; + ) + .map_err(|e| Error::DnsClient(server, e))?; let zone = match &config.global.zone { Some(n) => Cow::Borrowed(n), None => { - debug!("Querying SOA for: {}", name); + debug!("Querying SOA for: {name}"); - let response = client.query(&name, DNSClass::IN, RecordType::SOA) + let response = client + .query(&name, DNSClass::IN, RecordType::SOA) .map_err(|e| Error::DnsClient(server, e))?; let authority = response.name_servers(); if authority.is_empty() { @@ -127,18 +130,13 @@ fn update_dns(server: SocketAddr, config: &config::Config) -> Result<()> { Cow::Owned(authority[0].name().clone()) } }; - debug!("Zone: {}", zone); + debug!("Zone: {zone}"); - let request = dns::replace_addrs_message( - &zone, - &name, - config.global.ttl.0, - &valid_addrs, - ); - trace!("Update request: {:?}", request); + let request = dns::replace_addrs_message(&zone, &name, config.global.ttl.0, &valid_addrs); + trace!("Update request: {request:?}"); for record in request.updates() { - info!("Record update: {}", record); + info!("Record update: {record}"); } let responses = client.send(request); @@ -146,12 +144,12 @@ fn update_dns(server: SocketAddr, config: &config::Config) -> Result<()> { for response in responses { let r = response.map_err(|e| Error::DnsClient(server, e))?; - trace!("Update response: {:?}", r); + trace!("Update response: {r:?}"); let code = r.response_code(); if code != ResponseCode::NoError { - warn!("Received error response: {0:?} ({0})", code); + warn!("Received error response: {code:?} ({code})"); errored = true; } } @@ -165,38 +163,36 @@ fn update_dns(server: SocketAddr, config: &config::Config) -> Result<()> { fn main_wrapper() -> Result<()> { let opts: Opts = Opts::parse(); - let config = config::load_config(&opts.config) - .map_err(|e| Error::Config(opts.config.clone(), e))?; - - env_logger::Builder::from_env( - env_logger::Env::default() - .default_filter_or(format!( - "{}={}", - env!("CARGO_PKG_NAME").replace('-', "_"), - config.global.log_level, - )) - ) - .format_timestamp(None) - .init(); + let config = + config::load_config(&opts.config).map_err(|e| Error::Config(opts.config.clone(), e))?; + + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(format!( + "{}={}", + env!("CARGO_PKG_NAME").replace('-', "_"), + config.global.log_level, + ))) + .format_timestamp(None) + .init(); LOGGING_INITIALIZED.store(true, Ordering::SeqCst); - trace!("Loaded config: {:?}", config); + trace!("Loaded config: {config:?}"); let server_with_port = match &config.global.name_server { h if h.contains(':') => Cow::Borrowed(h.as_str()), - h => Cow::Owned(format!("{}:{}", h, config.global.protocol.default_port())), + h => Cow::Owned(format!("{h}:{}", config.global.protocol.default_port())), }; - debug!("Name server: {:?}", server_with_port); + debug!("Name server: {server_with_port:?}"); - let servers = server_with_port.to_socket_addrs() + let servers = server_with_port + .to_socket_addrs() .map_err(|e| Error::ResolveDnsHost(server_with_port.to_string(), e))? .collect::>(); - debug!("Resolved name servers: {:?}", servers); + debug!("Resolved name servers: {servers:?}"); let mut last_error = None; for server in servers { - debug!("Attempting to use name server: {}", server); + debug!("Attempting to use name server: {server}"); match update_dns(server, &config) { Ok(_) => break,