use std::net::{AddrParseError, SocketAddr};
use std::sync::Arc;
use hickory_client::client::{AsyncClient, ClientConnection, ClientHandle, Signer};
use hickory_client::error::ClientError;
use hickory_client::op::ResponseCode;
use hickory_client::proto::error::ProtoError;
use hickory_client::proto::rr::dnssec::tsig::TSigner;
use hickory_client::proto::rr::dnssec::{Algorithm, KeyPair, Private, SigSigner};
use hickory_client::rr::rdata::key::KEY;
use hickory_client::rr::rdata::tsig::TsigAlgorithm;
use hickory_client::rr::rdata::{A, AAAA, CNAME, MX, NS, SRV, TXT};
use hickory_client::rr::{DNSClass, Name, RData, Record, RecordType};
use hickory_client::tcp::TcpClientConnection;
use hickory_client::udp::UdpClientConnection;
use crate::{DnsRecord, Error, IntoFqdn};
#[derive(Clone)]
pub struct Rfc2136Provider {
addr: SocketAddr,
is_tcp: bool,
signer: Arc<Signer>,
}
impl Rfc2136Provider {
pub(crate) fn new_tsig(
url: impl AsRef<str>,
key_name: impl AsRef<str>,
key: impl Into<Vec<u8>>,
algorithm: TsigAlgorithm,
) -> crate::Result<Self> {
let (host, is_tcp) = parse_url(url);
Ok(Rfc2136Provider {
addr: host.parse()?,
is_tcp,
signer: Arc::new(Signer::from(TSigner::new(
key.into(),
algorithm,
Name::from_ascii(key_name.as_ref())?,
60,
)?)),
})
}
pub(crate) fn new_sig0(
url: impl AsRef<str>,
signer_name: impl AsRef<str>,
key: KeyPair<Private>,
public_key: impl Into<Vec<u8>>,
algorithm: Algorithm,
) -> crate::Result<Self> {
let (host, is_tcp) = parse_url(url);
let sig0key = KEY::new(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
algorithm,
public_key.into(),
);
let signer = SigSigner::sig0(sig0key, key, Name::from_str_relaxed(signer_name.as_ref())?);
Ok(Rfc2136Provider {
addr: host.parse()?,
is_tcp,
signer: Arc::new(Signer::from(signer)),
})
}
async fn connect(&self) -> crate::Result<AsyncClient> {
if !self.is_tcp {
let conn = UdpClientConnection::new(self.addr)?.new_stream(Some(self.signer.clone()));
let (client, bg) = AsyncClient::connect(conn).await?;
tokio::spawn(bg);
Ok(client)
} else {
let conn = TcpClientConnection::new(self.addr)?.new_stream(Some(self.signer.clone()));
let (client, bg) = AsyncClient::connect(conn).await?;
tokio::spawn(bg);
Ok(client)
}
}
pub(crate) async fn create(
&self,
name: impl IntoFqdn<'_>,
record: DnsRecord,
ttl: u32,
origin: impl IntoFqdn<'_>,
) -> crate::Result<()> {
let (rr_type, rdata) = convert_record(record)?;
let mut record = Record::with(
Name::from_str_relaxed(name.into_name().as_ref())?,
rr_type,
ttl,
);
record.set_data(Some(rdata));
let mut client = self.connect().await?;
let result = client
.create(record, Name::from_str_relaxed(origin.into_fqdn().as_ref())?)
.await?;
if result.response_code() == ResponseCode::NoError {
Ok(())
} else {
Err(crate::Error::Response(result.response_code().to_string()))
}
}
pub(crate) async fn update(
&self,
name: impl IntoFqdn<'_>,
record: DnsRecord,
ttl: u32,
origin: impl IntoFqdn<'_>,
) -> crate::Result<()> {
let (rr_type, rdata) = convert_record(record)?;
let mut record = Record::with(
Name::from_str_relaxed(name.into_name().as_ref())?,
rr_type,
ttl,
);
record.set_data(Some(rdata));
let mut client = self.connect().await?;
let result = client
.append(
record,
Name::from_str_relaxed(origin.into_fqdn().as_ref())?,
false,
)
.await?;
if result.response_code() == ResponseCode::NoError {
Ok(())
} else {
Err(crate::Error::Response(result.response_code().to_string()))
}
}
pub(crate) async fn delete(
&self,
name: impl IntoFqdn<'_>,
origin: impl IntoFqdn<'_>,
) -> crate::Result<()> {
let mut client = self.connect().await?;
let result = client
.delete_all(
Name::from_str_relaxed(name.into_name().as_ref())?,
Name::from_str_relaxed(origin.into_fqdn().as_ref())?,
DNSClass::IN,
)
.await?;
if result.response_code() == ResponseCode::NoError {
Ok(())
} else {
Err(crate::Error::Response(result.response_code().to_string()))
}
}
}
fn convert_record(record: DnsRecord) -> crate::Result<(RecordType, RData)> {
Ok(match record {
DnsRecord::A { content } => (RecordType::A, RData::A(A::from(content))),
DnsRecord::AAAA { content } => (RecordType::AAAA, RData::AAAA(AAAA::from(content))),
DnsRecord::CNAME { content } => (
RecordType::CNAME,
RData::CNAME(CNAME(Name::from_str_relaxed(content)?)),
),
DnsRecord::NS { content } => (
RecordType::NS,
RData::NS(NS(Name::from_str_relaxed(content)?)),
),
DnsRecord::MX { content, priority } => (
RecordType::MX,
RData::MX(MX::new(priority, Name::from_str_relaxed(content)?)),
),
DnsRecord::TXT { content } => (RecordType::TXT, RData::TXT(TXT::new(vec![content]))),
DnsRecord::SRV {
content,
priority,
weight,
port,
} => (
RecordType::SRV,
RData::SRV(SRV::new(
priority,
weight,
port,
Name::from_str_relaxed(content)?,
)),
),
})
}
fn parse_url(url: impl AsRef<str>) -> (String, bool) {
let url = url.as_ref();
let (host, is_tcp) = if let Some(host) = url.strip_prefix("udp://") {
(host, false)
} else if let Some(host) = url.strip_prefix("tcp://") {
(host, true)
} else {
(url, false)
};
let (host, port) = if let Some((host, port)) = host.split_once(':') {
(host, port)
} else {
(host, "53")
};
(format!("{host}:{port}"), is_tcp)
}
impl From<crate::TsigAlgorithm> for TsigAlgorithm {
fn from(alg: crate::TsigAlgorithm) -> Self {
match alg {
crate::TsigAlgorithm::HmacMd5 => TsigAlgorithm::HmacMd5,
crate::TsigAlgorithm::Gss => TsigAlgorithm::Gss,
crate::TsigAlgorithm::HmacSha1 => TsigAlgorithm::HmacSha1,
crate::TsigAlgorithm::HmacSha224 => TsigAlgorithm::HmacSha224,
crate::TsigAlgorithm::HmacSha256 => TsigAlgorithm::HmacSha256,
crate::TsigAlgorithm::HmacSha256_128 => TsigAlgorithm::HmacSha256_128,
crate::TsigAlgorithm::HmacSha384 => TsigAlgorithm::HmacSha384,
crate::TsigAlgorithm::HmacSha384_192 => TsigAlgorithm::HmacSha384_192,
crate::TsigAlgorithm::HmacSha512 => TsigAlgorithm::HmacSha512,
crate::TsigAlgorithm::HmacSha512_256 => TsigAlgorithm::HmacSha512_256,
}
}
}
impl From<crate::Algorithm> for Algorithm {
fn from(alg: crate::Algorithm) -> Self {
match alg {
crate::Algorithm::RSASHA256 => Algorithm::RSASHA256,
crate::Algorithm::RSASHA512 => Algorithm::RSASHA512,
crate::Algorithm::ECDSAP256SHA256 => Algorithm::ECDSAP256SHA256,
crate::Algorithm::ECDSAP384SHA384 => Algorithm::ECDSAP384SHA384,
crate::Algorithm::ED25519 => Algorithm::ED25519,
}
}
}
impl From<ProtoError> for Error {
fn from(e: ProtoError) -> Self {
Error::Protocol(e.to_string())
}
}
impl From<AddrParseError> for Error {
fn from(e: AddrParseError) -> Self {
Error::Parse(e.to_string())
}
}
impl From<ClientError> for Error {
fn from(e: ClientError) -> Self {
Error::Client(e.to_string())
}
}