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