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