1use std::net::{AddrParseError, SocketAddr};
13use std::sync::Arc;
14
15use hickory_client::client::{AsyncClient, ClientConnection, ClientHandle, Signer};
16use hickory_client::error::ClientError;
17use hickory_client::op::ResponseCode;
18use hickory_client::proto::error::ProtoError;
19use hickory_client::proto::rr::dnssec::tsig::TSigner;
20use hickory_client::proto::rr::dnssec::{Algorithm, KeyPair, Private, SigSigner};
21use hickory_client::rr::rdata::key::KEY;
22use hickory_client::rr::rdata::tsig::TsigAlgorithm;
23use hickory_client::rr::rdata::{A, AAAA, CNAME, MX, NS, SRV, TXT};
24use hickory_client::rr::{DNSClass, Name, RData, Record, RecordType};
25use hickory_client::tcp::TcpClientConnection;
26use hickory_client::udp::UdpClientConnection;
27
28use crate::{DnsRecord, Error, IntoFqdn};
29
30#[derive(Clone)]
31pub struct Rfc2136Provider {
32 addr: DnsAddress,
33 signer: Arc<Signer>,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum DnsAddress {
38 Tcp(SocketAddr),
39 Udp(SocketAddr),
40}
41
42impl Rfc2136Provider {
43 pub(crate) fn new_tsig(
44 addr: impl TryInto<DnsAddress>,
45 key_name: impl AsRef<str>,
46 key: impl Into<Vec<u8>>,
47 algorithm: TsigAlgorithm,
48 ) -> crate::Result<Self> {
49 Ok(Rfc2136Provider {
50 addr: addr
51 .try_into()
52 .map_err(|_| Error::Parse("Invalid address".to_string()))?,
53 signer: Arc::new(Signer::from(TSigner::new(
54 key.into(),
55 algorithm,
56 Name::from_ascii(key_name.as_ref())?,
57 60,
58 )?)),
59 })
60 }
61
62 pub(crate) fn new_sig0(
63 addr: impl TryInto<DnsAddress>,
64 signer_name: impl AsRef<str>,
65 key: KeyPair<Private>,
66 public_key: impl Into<Vec<u8>>,
67 algorithm: Algorithm,
68 ) -> crate::Result<Self> {
69 let sig0key = KEY::new(
70 Default::default(),
71 Default::default(),
72 Default::default(),
73 Default::default(),
74 algorithm,
75 public_key.into(),
76 );
77
78 let signer = SigSigner::sig0(sig0key, key, Name::from_str_relaxed(signer_name.as_ref())?);
79
80 Ok(Rfc2136Provider {
81 addr: addr
82 .try_into()
83 .map_err(|_| Error::Parse("Invalid address".to_string()))?,
84 signer: Arc::new(Signer::from(signer)),
85 })
86 }
87
88 async fn connect(&self) -> crate::Result<AsyncClient> {
89 match &self.addr {
90 DnsAddress::Udp(addr) => {
91 let conn = UdpClientConnection::new(*addr)?.new_stream(Some(self.signer.clone()));
92 let (client, bg) = AsyncClient::connect(conn).await?;
93 tokio::spawn(bg);
94 Ok(client)
95 }
96 DnsAddress::Tcp(addr) => {
97 let conn = TcpClientConnection::new(*addr)?.new_stream(Some(self.signer.clone()));
98 let (client, bg) = AsyncClient::connect(conn).await?;
99 tokio::spawn(bg);
100 Ok(client)
101 }
102 }
103 }
104
105 pub(crate) async fn create(
106 &self,
107 name: impl IntoFqdn<'_>,
108 record: DnsRecord,
109 ttl: u32,
110 origin: impl IntoFqdn<'_>,
111 ) -> crate::Result<()> {
112 let (rr_type, rdata) = convert_record(record)?;
113 let mut record = Record::with(
114 Name::from_str_relaxed(name.into_name().as_ref())?,
115 rr_type,
116 ttl,
117 );
118 record.set_data(Some(rdata));
119
120 let mut client = self.connect().await?;
121 let result = client
122 .create(record, Name::from_str_relaxed(origin.into_fqdn().as_ref())?)
123 .await?;
124 if result.response_code() == ResponseCode::NoError {
125 Ok(())
126 } else {
127 Err(crate::Error::Response(result.response_code().to_string()))
128 }
129 }
130
131 pub(crate) async fn update(
132 &self,
133 name: impl IntoFqdn<'_>,
134 record: DnsRecord,
135 ttl: u32,
136 origin: impl IntoFqdn<'_>,
137 ) -> crate::Result<()> {
138 let (rr_type, rdata) = convert_record(record)?;
139 let mut record = Record::with(
140 Name::from_str_relaxed(name.into_name().as_ref())?,
141 rr_type,
142 ttl,
143 );
144 record.set_data(Some(rdata));
145
146 let mut client = self.connect().await?;
147 let result = client
148 .append(
149 record,
150 Name::from_str_relaxed(origin.into_fqdn().as_ref())?,
151 false,
152 )
153 .await?;
154 if result.response_code() == ResponseCode::NoError {
155 Ok(())
156 } else {
157 Err(crate::Error::Response(result.response_code().to_string()))
158 }
159 }
160
161 pub(crate) async fn delete(
162 &self,
163 name: impl IntoFqdn<'_>,
164 origin: impl IntoFqdn<'_>,
165 ) -> crate::Result<()> {
166 let mut client = self.connect().await?;
167 let result = client
168 .delete_all(
169 Name::from_str_relaxed(name.into_name().as_ref())?,
170 Name::from_str_relaxed(origin.into_fqdn().as_ref())?,
171 DNSClass::IN,
172 )
173 .await?;
174 if result.response_code() == ResponseCode::NoError {
175 Ok(())
176 } else {
177 Err(crate::Error::Response(result.response_code().to_string()))
178 }
179 }
180}
181
182fn convert_record(record: DnsRecord) -> crate::Result<(RecordType, RData)> {
183 Ok(match record {
184 DnsRecord::A { content } => (RecordType::A, RData::A(A::from(content))),
185 DnsRecord::AAAA { content } => (RecordType::AAAA, RData::AAAA(AAAA::from(content))),
186 DnsRecord::CNAME { content } => (
187 RecordType::CNAME,
188 RData::CNAME(CNAME(Name::from_str_relaxed(content)?)),
189 ),
190 DnsRecord::NS { content } => (
191 RecordType::NS,
192 RData::NS(NS(Name::from_str_relaxed(content)?)),
193 ),
194 DnsRecord::MX { content, priority } => (
195 RecordType::MX,
196 RData::MX(MX::new(priority, Name::from_str_relaxed(content)?)),
197 ),
198 DnsRecord::TXT { content } => (RecordType::TXT, RData::TXT(TXT::new(vec![content]))),
199 DnsRecord::SRV {
200 content,
201 priority,
202 weight,
203 port,
204 } => (
205 RecordType::SRV,
206 RData::SRV(SRV::new(
207 priority,
208 weight,
209 port,
210 Name::from_str_relaxed(content)?,
211 )),
212 ),
213 })
214}
215
216impl TryFrom<&str> for DnsAddress {
217 type Error = ();
218
219 fn try_from(url: &str) -> Result<Self, Self::Error> {
220 let (host, is_tcp) = if let Some(host) = url.strip_prefix("udp://") {
221 (host, false)
222 } else if let Some(host) = url.strip_prefix("tcp://") {
223 (host, true)
224 } else {
225 (url, false)
226 };
227 let (host, port) = if let Some(host) = host.strip_prefix('[') {
228 let (host, maybe_port) = host.rsplit_once(']').ok_or(())?;
229
230 (
231 host,
232 maybe_port
233 .rsplit_once(':')
234 .map(|(_, port)| port)
235 .unwrap_or("53"),
236 )
237 } else if let Some((host, port)) = host.rsplit_once(':') {
238 (host, port)
239 } else {
240 (host, "53")
241 };
242
243 let addr = SocketAddr::new(host.parse().map_err(|_| ())?, port.parse().map_err(|_| ())?);
244
245 if is_tcp {
246 Ok(DnsAddress::Tcp(addr))
247 } else {
248 Ok(DnsAddress::Udp(addr))
249 }
250 }
251}
252
253impl TryFrom<&String> for DnsAddress {
254 type Error = ();
255
256 fn try_from(url: &String) -> Result<Self, Self::Error> {
257 DnsAddress::try_from(url.as_str())
258 }
259}
260
261impl TryFrom<String> for DnsAddress {
262 type Error = ();
263
264 fn try_from(url: String) -> Result<Self, Self::Error> {
265 DnsAddress::try_from(url.as_str())
266 }
267}
268
269impl From<crate::TsigAlgorithm> for TsigAlgorithm {
270 fn from(alg: crate::TsigAlgorithm) -> Self {
271 match alg {
272 crate::TsigAlgorithm::HmacMd5 => TsigAlgorithm::HmacMd5,
273 crate::TsigAlgorithm::Gss => TsigAlgorithm::Gss,
274 crate::TsigAlgorithm::HmacSha1 => TsigAlgorithm::HmacSha1,
275 crate::TsigAlgorithm::HmacSha224 => TsigAlgorithm::HmacSha224,
276 crate::TsigAlgorithm::HmacSha256 => TsigAlgorithm::HmacSha256,
277 crate::TsigAlgorithm::HmacSha256_128 => TsigAlgorithm::HmacSha256_128,
278 crate::TsigAlgorithm::HmacSha384 => TsigAlgorithm::HmacSha384,
279 crate::TsigAlgorithm::HmacSha384_192 => TsigAlgorithm::HmacSha384_192,
280 crate::TsigAlgorithm::HmacSha512 => TsigAlgorithm::HmacSha512,
281 crate::TsigAlgorithm::HmacSha512_256 => TsigAlgorithm::HmacSha512_256,
282 }
283 }
284}
285
286impl From<crate::Algorithm> for Algorithm {
287 fn from(alg: crate::Algorithm) -> Self {
288 match alg {
289 crate::Algorithm::RSASHA256 => Algorithm::RSASHA256,
290 crate::Algorithm::RSASHA512 => Algorithm::RSASHA512,
291 crate::Algorithm::ECDSAP256SHA256 => Algorithm::ECDSAP256SHA256,
292 crate::Algorithm::ECDSAP384SHA384 => Algorithm::ECDSAP384SHA384,
293 crate::Algorithm::ED25519 => Algorithm::ED25519,
294 }
295 }
296}
297
298impl From<ProtoError> for Error {
299 fn from(e: ProtoError) -> Self {
300 Error::Protocol(e.to_string())
301 }
302}
303
304impl From<AddrParseError> for Error {
305 fn from(e: AddrParseError) -> Self {
306 Error::Parse(e.to_string())
307 }
308}
309
310impl From<ClientError> for Error {
311 fn from(e: ClientError) -> Self {
312 Error::Client(e.to_string())
313 }
314}