1use crate::{
13 CAARecord, DnsRecord, DnsRecordType, Error, IntoFqdn, TlsaCertUsage, TlsaMatching, TlsaSelector,
14};
15use hickory_client::ClientError;
16use hickory_client::client::{Client, ClientHandle};
17use hickory_client::proto::ProtoError;
18use hickory_client::proto::dnssec::rdata::KEY;
19use hickory_client::proto::dnssec::rdata::tsig::TsigAlgorithm;
20use hickory_client::proto::dnssec::tsig::TSigner;
21use hickory_client::proto::dnssec::{Algorithm, DnsSecError, SigSigner, SigningKey};
22use hickory_client::proto::op::MessageFinalizer;
23use hickory_client::proto::op::ResponseCode;
24use hickory_client::proto::rr::rdata::caa::KeyValue;
25use hickory_client::proto::rr::rdata::tlsa::{CertUsage, Matching, Selector};
26use hickory_client::proto::rr::rdata::{A, AAAA, CAA, CNAME, MX, NS, SRV, TLSA, TXT};
27use hickory_client::proto::rr::{Name, RData, Record, RecordType};
28use hickory_client::proto::runtime::TokioRuntimeProvider;
29use hickory_client::proto::tcp::TcpClientStream;
30use hickory_client::proto::udp::UdpClientStream;
31use std::net::{AddrParseError, SocketAddr};
32use std::sync::Arc;
33
34#[derive(Clone)]
35pub struct Rfc2136Provider {
36 addr: DnsAddress,
37 signer: Arc<dyn MessageFinalizer>,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum DnsAddress {
42 Tcp(SocketAddr),
43 Udp(SocketAddr),
44}
45
46impl Rfc2136Provider {
47 pub(crate) fn new_tsig(
48 addr: impl TryInto<DnsAddress>,
49 key_name: impl AsRef<str>,
50 key: impl Into<Vec<u8>>,
51 algorithm: TsigAlgorithm,
52 ) -> crate::Result<Self> {
53 Ok(Rfc2136Provider {
54 addr: addr
55 .try_into()
56 .map_err(|_| Error::Parse("Invalid address".to_string()))?,
57 signer: Arc::new(TSigner::new(
58 key.into(),
59 algorithm,
60 Name::from_ascii(key_name.as_ref())?,
61 60,
62 )?),
63 })
64 }
65
66 pub(crate) fn new_sig0(
68 addr: impl TryInto<DnsAddress>,
69 signer_name: impl AsRef<str>,
70 key: Box<dyn SigningKey>,
71 public_key: impl Into<Vec<u8>>,
72 algorithm: Algorithm,
73 ) -> crate::Result<Self> {
74 let sig0key = KEY::new(
75 Default::default(),
76 Default::default(),
77 Default::default(),
78 Default::default(),
79 algorithm,
80 public_key.into(),
81 );
82
83 let signer = SigSigner::sig0(sig0key, key, Name::from_str_relaxed(signer_name.as_ref())?);
84
85 Ok(Rfc2136Provider {
86 addr: addr
87 .try_into()
88 .map_err(|_| Error::Parse("Invalid address".to_string()))?,
89 signer: Arc::new(signer),
90 })
91 }
92
93 async fn connect(&self) -> crate::Result<Client> {
94 match &self.addr {
95 DnsAddress::Udp(addr) => {
96 let stream = UdpClientStream::builder(*addr, TokioRuntimeProvider::new())
97 .with_signer(Some(self.signer.clone()))
98 .build();
99 let (client, bg) = Client::connect(stream).await?;
100 tokio::spawn(bg);
101 Ok(client)
102 }
103 DnsAddress::Tcp(addr) => {
104 let (stream, sender) =
105 TcpClientStream::new(*addr, None, None, TokioRuntimeProvider::new());
106 let (client, bg) = Client::new(stream, sender, Some(self.signer.clone())).await?;
107 tokio::spawn(bg);
108 Ok(client)
109 }
110 }
111 }
112
113 pub(crate) async fn create(
114 &self,
115 name: impl IntoFqdn<'_>,
116 record: DnsRecord,
117 ttl: u32,
118 origin: impl IntoFqdn<'_>,
119 ) -> crate::Result<()> {
120 let (_rr_type, rdata) = convert_record(record)?;
121 let record = Record::from_rdata(
122 Name::from_str_relaxed(name.into_name().as_ref())?,
123 ttl,
124 rdata,
125 );
126
127 let mut client = self.connect().await?;
128 let result = client
129 .create(record, Name::from_str_relaxed(origin.into_fqdn().as_ref())?)
130 .await?;
131 if result.response_code() == ResponseCode::NoError {
132 Ok(())
133 } else {
134 Err(crate::Error::Response(result.response_code().to_string()))
135 }
136 }
137
138 pub(crate) async fn update(
139 &self,
140 name: impl IntoFqdn<'_>,
141 record: DnsRecord,
142 ttl: u32,
143 origin: impl IntoFqdn<'_>,
144 ) -> crate::Result<()> {
145 let (_rr_type, rdata) = convert_record(record)?;
146 let record = Record::from_rdata(
147 Name::from_str_relaxed(name.into_name().as_ref())?,
148 ttl,
149 rdata,
150 );
151
152 let mut client = self.connect().await?;
153 let result = client
154 .append(
155 record,
156 Name::from_str_relaxed(origin.into_fqdn().as_ref())?,
157 false,
158 )
159 .await?;
160 if result.response_code() == ResponseCode::NoError {
161 Ok(())
162 } else {
163 Err(crate::Error::Response(result.response_code().to_string()))
164 }
165 }
166
167 pub(crate) async fn delete(
168 &self,
169 name: impl IntoFqdn<'_>,
170 origin: impl IntoFqdn<'_>,
171 record_type: DnsRecordType,
172 ) -> crate::Result<()> {
173 let record = Record::update0(
174 Name::from_str_relaxed(name.into_name().as_ref())?,
175 0,
176 record_type.into(),
177 );
178
179 let mut client = self.connect().await?;
180 let result = client
181 .delete_rrset(record, Name::from_str_relaxed(origin.into_fqdn().as_ref())?)
182 .await?;
183 if result.response_code() == ResponseCode::NoError {
184 Ok(())
185 } else {
186 Err(crate::Error::Response(result.response_code().to_string()))
187 }
188 }
189}
190
191impl From<DnsRecordType> for RecordType {
192 fn from(record_type: DnsRecordType) -> Self {
193 match record_type {
194 DnsRecordType::A => RecordType::A,
195 DnsRecordType::AAAA => RecordType::AAAA,
196 DnsRecordType::CNAME => RecordType::CNAME,
197 DnsRecordType::NS => RecordType::NS,
198 DnsRecordType::MX => RecordType::MX,
199 DnsRecordType::TXT => RecordType::TXT,
200 DnsRecordType::SRV => RecordType::SRV,
201 DnsRecordType::TLSA => RecordType::TLSA,
202 DnsRecordType::CAA => RecordType::CAA,
203 }
204 }
205}
206
207fn convert_record(record: DnsRecord) -> crate::Result<(RecordType, RData)> {
208 Ok(match record {
209 DnsRecord::A(content) => (RecordType::A, RData::A(A::from(content))),
210 DnsRecord::AAAA(content) => (RecordType::AAAA, RData::AAAA(AAAA::from(content))),
211 DnsRecord::CNAME(content) => (
212 RecordType::CNAME,
213 RData::CNAME(CNAME(Name::from_str_relaxed(content)?)),
214 ),
215 DnsRecord::NS(content) => (
216 RecordType::NS,
217 RData::NS(NS(Name::from_str_relaxed(content)?)),
218 ),
219 DnsRecord::MX(content) => (
220 RecordType::MX,
221 RData::MX(MX::new(
222 content.priority,
223 Name::from_str_relaxed(content.exchange)?,
224 )),
225 ),
226 DnsRecord::TXT(content) => (RecordType::TXT, RData::TXT(TXT::new(vec![content]))),
227 DnsRecord::SRV(content) => (
228 RecordType::SRV,
229 RData::SRV(SRV::new(
230 content.priority,
231 content.weight,
232 content.port,
233 Name::from_str_relaxed(content.target)?,
234 )),
235 ),
236 DnsRecord::TLSA(content) => (
237 RecordType::TLSA,
238 RData::TLSA(TLSA::new(
239 content.cert_usage.into(),
240 content.selector.into(),
241 content.matching.into(),
242 content.cert_data,
243 )),
244 ),
245 DnsRecord::CAA(caa) => (
246 RecordType::CAA,
247 RData::CAA(match caa {
248 CAARecord::Issue {
249 issuer_critical,
250 name,
251 options,
252 } => CAA::new_issue(
253 issuer_critical,
254 name.map(Name::from_str_relaxed).transpose()?,
255 options
256 .into_iter()
257 .map(|kv| KeyValue::new(kv.key, kv.value))
258 .collect(),
259 ),
260 CAARecord::IssueWild {
261 issuer_critical,
262 name,
263 options,
264 } => CAA::new_issuewild(
265 issuer_critical,
266 name.map(Name::from_str_relaxed).transpose()?,
267 options
268 .into_iter()
269 .map(|kv| KeyValue::new(kv.key, kv.value))
270 .collect(),
271 ),
272 CAARecord::Iodef {
273 issuer_critical,
274 url,
275 } => CAA::new_iodef(
276 issuer_critical,
277 url.parse()
278 .map_err(|_| Error::Parse("Invalid URL in CAA record".to_string()))?,
279 ),
280 }),
281 ),
282 })
283}
284
285impl From<TlsaCertUsage> for CertUsage {
286 fn from(usage: TlsaCertUsage) -> Self {
287 match usage {
288 TlsaCertUsage::PkixTa => CertUsage::PkixTa,
289 TlsaCertUsage::PkixEe => CertUsage::PkixEe,
290 TlsaCertUsage::DaneTa => CertUsage::DaneTa,
291 TlsaCertUsage::DaneEe => CertUsage::DaneEe,
292 TlsaCertUsage::Private => CertUsage::Private,
293 }
294 }
295}
296
297impl From<TlsaMatching> for Matching {
298 fn from(matching: TlsaMatching) -> Self {
299 match matching {
300 TlsaMatching::Raw => Matching::Raw,
301 TlsaMatching::Sha256 => Matching::Sha256,
302 TlsaMatching::Sha512 => Matching::Sha512,
303 TlsaMatching::Private => Matching::Private,
304 }
305 }
306}
307
308impl From<TlsaSelector> for Selector {
309 fn from(selector: TlsaSelector) -> Self {
310 match selector {
311 TlsaSelector::Full => Selector::Full,
312 TlsaSelector::Spki => Selector::Spki,
313 TlsaSelector::Private => Selector::Private,
314 }
315 }
316}
317
318impl TryFrom<&str> for DnsAddress {
319 type Error = ();
320
321 fn try_from(url: &str) -> Result<Self, Self::Error> {
322 let (host, is_tcp) = if let Some(host) = url.strip_prefix("udp://") {
323 (host, false)
324 } else if let Some(host) = url.strip_prefix("tcp://") {
325 (host, true)
326 } else {
327 (url, false)
328 };
329 let (host, port) = if let Some(host) = host.strip_prefix('[') {
330 let (host, maybe_port) = host.rsplit_once(']').ok_or(())?;
331
332 (
333 host,
334 maybe_port
335 .rsplit_once(':')
336 .map(|(_, port)| port)
337 .unwrap_or("53"),
338 )
339 } else if let Some((host, port)) = host.rsplit_once(':') {
340 (host, port)
341 } else {
342 (host, "53")
343 };
344
345 let addr = SocketAddr::new(host.parse().map_err(|_| ())?, port.parse().map_err(|_| ())?);
346
347 if is_tcp {
348 Ok(DnsAddress::Tcp(addr))
349 } else {
350 Ok(DnsAddress::Udp(addr))
351 }
352 }
353}
354
355impl TryFrom<&String> for DnsAddress {
356 type Error = ();
357
358 fn try_from(url: &String) -> Result<Self, Self::Error> {
359 DnsAddress::try_from(url.as_str())
360 }
361}
362
363impl TryFrom<String> for DnsAddress {
364 type Error = ();
365
366 fn try_from(url: String) -> Result<Self, Self::Error> {
367 DnsAddress::try_from(url.as_str())
368 }
369}
370
371impl From<crate::TsigAlgorithm> for TsigAlgorithm {
372 fn from(alg: crate::TsigAlgorithm) -> Self {
373 match alg {
374 crate::TsigAlgorithm::HmacMd5 => TsigAlgorithm::HmacMd5,
375 crate::TsigAlgorithm::Gss => TsigAlgorithm::Gss,
376 crate::TsigAlgorithm::HmacSha1 => TsigAlgorithm::HmacSha1,
377 crate::TsigAlgorithm::HmacSha224 => TsigAlgorithm::HmacSha224,
378 crate::TsigAlgorithm::HmacSha256 => TsigAlgorithm::HmacSha256,
379 crate::TsigAlgorithm::HmacSha256_128 => TsigAlgorithm::HmacSha256_128,
380 crate::TsigAlgorithm::HmacSha384 => TsigAlgorithm::HmacSha384,
381 crate::TsigAlgorithm::HmacSha384_192 => TsigAlgorithm::HmacSha384_192,
382 crate::TsigAlgorithm::HmacSha512 => TsigAlgorithm::HmacSha512,
383 crate::TsigAlgorithm::HmacSha512_256 => TsigAlgorithm::HmacSha512_256,
384 }
385 }
386}
387
388impl From<crate::Algorithm> for Algorithm {
389 fn from(alg: crate::Algorithm) -> Self {
390 match alg {
391 crate::Algorithm::RSASHA256 => Algorithm::RSASHA256,
392 crate::Algorithm::RSASHA512 => Algorithm::RSASHA512,
393 crate::Algorithm::ECDSAP256SHA256 => Algorithm::ECDSAP256SHA256,
394 crate::Algorithm::ECDSAP384SHA384 => Algorithm::ECDSAP384SHA384,
395 crate::Algorithm::ED25519 => Algorithm::ED25519,
396 }
397 }
398}
399
400impl From<ProtoError> for Error {
401 fn from(e: ProtoError) -> Self {
402 Error::Protocol(e.to_string())
403 }
404}
405
406impl From<AddrParseError> for Error {
407 fn from(e: AddrParseError) -> Self {
408 Error::Parse(e.to_string())
409 }
410}
411
412impl From<ClientError> for Error {
413 fn from(e: ClientError) -> Self {
414 Error::Client(e.to_string())
415 }
416}
417
418impl From<DnsSecError> for Error {
419 fn from(e: DnsSecError) -> Self {
420 Error::Protocol(e.to_string())
421 }
422}