1use super::{DnsEntry, QueryError, QueryResult};
13use crate::{Error, Instant, MessageAuthenticator, authenticator::DEFAULT_MAX_NEGATIVE_TTL};
14use hickory_resolver::{
15 TokioResolver,
16 config::{CLOUDFLARE, GOOGLE, QUAD9, ResolverConfig, ResolverOpts},
17 net::{DnsError, NetError, runtime::TokioRuntimeProvider},
18 proto::{
19 ProtoError,
20 rr::{Name, RData},
21 },
22 system_conf::read_system_conf,
23};
24use std::{
25 net::{IpAddr, Ipv4Addr, Ipv6Addr},
26 sync::Arc,
27};
28
29impl MessageAuthenticator {
30 #[cfg(feature = "aws-lc-rs")]
38 pub fn new_cloudflare_tls() -> Result<Self, NetError> {
39 Self::new(ResolverConfig::tls(&CLOUDFLARE), ResolverOpts::default())
40 }
41
42 pub fn new_cloudflare() -> Result<Self, NetError> {
49 Self::new(
50 ResolverConfig::udp_and_tcp(&CLOUDFLARE),
51 ResolverOpts::default(),
52 )
53 }
54
55 pub fn new_google() -> Result<Self, NetError> {
62 Self::new(
63 ResolverConfig::udp_and_tcp(&GOOGLE),
64 ResolverOpts::default(),
65 )
66 }
67
68 pub fn new_quad9() -> Result<Self, NetError> {
75 Self::new(ResolverConfig::udp_and_tcp(&QUAD9), ResolverOpts::default())
76 }
77
78 #[cfg(feature = "aws-lc-rs")]
85 pub fn new_quad9_tls() -> Result<Self, NetError> {
86 Self::new(ResolverConfig::tls(&QUAD9), ResolverOpts::default())
87 }
88
89 pub fn new_system_conf() -> Result<Self, NetError> {
97 let (config, options) = read_system_conf()?;
98 Self::new(config, options)
99 }
100
101 pub fn new(config: ResolverConfig, options: ResolverOpts) -> Result<Self, NetError> {
108 Ok(MessageAuthenticator {
109 resolver: TokioResolver::builder_with_config(config, TokioRuntimeProvider::default())
110 .with_options(options)
111 .build()?,
112 max_negative_ttl: DEFAULT_MAX_NEGATIVE_TTL,
113 })
114 }
115
116 pub(crate) async fn query_txt(&self, key: &str) -> QueryResult<Vec<Vec<u8>>> {
117 let lookup = self
118 .resolver
119 .txt_lookup(Name::from_str_relaxed::<&str>(key)?)
120 .await?;
121 let expires = lookup.valid_until();
122 let mut entry: Vec<Vec<u8>> = Vec::new();
123 for record in lookup.answers() {
124 let RData::TXT(txt) = &record.data else {
125 continue;
126 };
127 match txt.txt_data.len() {
128 0 => {}
129 1 => entry.push(txt.txt_data[0].to_vec()),
130 _ => {
131 let mut data = Vec::with_capacity(255 * txt.txt_data.len());
132 for chunk in txt.txt_data.iter() {
133 data.extend_from_slice(chunk);
134 }
135 entry.push(data);
136 }
137 }
138 }
139 Ok(DnsEntry { entry, expires })
140 }
141
142 pub(crate) async fn query_mx(&self, key: &str) -> QueryResult<Vec<(u16, Box<str>)>> {
143 let lookup = self
144 .resolver
145 .mx_lookup(Name::from_str_relaxed::<&str>(key)?)
146 .await?;
147 let expires = lookup.valid_until();
148 let entry = lookup
149 .answers()
150 .iter()
151 .filter_map(|r| {
152 let RData::MX(mx) = &r.data else {
153 return None;
154 };
155 Some((
156 mx.preference,
157 mx.exchange.to_lowercase().to_ascii().into_boxed_str(),
158 ))
159 })
160 .collect();
161 Ok(DnsEntry { entry, expires })
162 }
163
164 pub(crate) async fn query_ipv4(&self, key: &str) -> QueryResult<Arc<[Ipv4Addr]>> {
165 let lookup = self
166 .resolver
167 .ipv4_lookup(Name::from_str_relaxed::<&str>(key)?)
168 .await?;
169 let expires = lookup.valid_until();
170 let entry: Arc<[Ipv4Addr]> = lookup
171 .answers()
172 .iter()
173 .filter_map(|r| {
174 if let RData::A(a) = &r.data {
175 Some(a.0)
176 } else {
177 None
178 }
179 })
180 .collect::<Vec<Ipv4Addr>>()
181 .into();
182 Ok(DnsEntry { entry, expires })
183 }
184
185 pub(crate) async fn query_ipv6(&self, key: &str) -> QueryResult<Arc<[Ipv6Addr]>> {
186 let lookup = self
187 .resolver
188 .ipv6_lookup(Name::from_str_relaxed::<&str>(key)?)
189 .await?;
190 let expires = lookup.valid_until();
191 let entry: Arc<[Ipv6Addr]> = lookup
192 .answers()
193 .iter()
194 .filter_map(|r| {
195 if let RData::AAAA(aaaa) = &r.data {
196 Some(aaaa.0)
197 } else {
198 None
199 }
200 })
201 .collect::<Vec<Ipv6Addr>>()
202 .into();
203 Ok(DnsEntry { entry, expires })
204 }
205
206 pub(crate) async fn query_ptr(&self, addr: IpAddr) -> QueryResult<Arc<[Box<str>]>> {
207 let lookup = self.resolver.reverse_lookup(addr).await?;
208 let expires: Instant = lookup.valid_until();
209 let entry = lookup
210 .answers()
211 .iter()
212 .filter_map(|r| {
213 let RData::PTR(ptr) = &r.data else {
214 return None;
215 };
216 if !ptr.is_empty() {
217 Some(ptr.to_lowercase().to_ascii().into_boxed_str())
218 } else {
219 None
220 }
221 })
222 .collect::<Arc<[Box<str>]>>();
223 Ok(DnsEntry { entry, expires })
224 }
225
226 pub(crate) async fn query_exists(&self, key: &str) -> crate::Result<bool> {
227 match self
228 .resolver
229 .lookup_ip(Name::from_str_relaxed::<&str>(key)?)
230 .await
231 {
232 Ok(result) => Ok(result.as_lookup().answers().iter().any(|r| {
233 matches!(
234 &r.data.record_type(),
235 hickory_resolver::proto::rr::RecordType::A
236 | hickory_resolver::proto::rr::RecordType::AAAA
237 )
238 })),
239 Err(err) if err.is_no_records_found() => Ok(false),
240 Err(err) => Err(err.into()),
241 }
242 }
243}
244
245impl From<ProtoError> for Error {
246 fn from(_: ProtoError) -> Self {
247 Error::Parse
248 }
249}
250
251impl From<ProtoError> for QueryError {
252 fn from(_: ProtoError) -> Self {
253 QueryError::Other(Error::Parse)
254 }
255}
256
257impl From<NetError> for QueryError {
258 fn from(err: NetError) -> Self {
259 match &err {
260 NetError::Dns(DnsError::NoRecordsFound(no_records)) => QueryError::NotFound {
261 code: no_records.response_code,
262 negative_ttl: no_records.negative_ttl,
263 },
264 _ => QueryError::Other(Error::Dns(crate::DnsError::Resolver(err.to_string()))),
265 }
266 }
267}
268
269impl From<NetError> for Error {
270 fn from(err: NetError) -> Self {
271 QueryError::from(err).into()
272 }
273}
274
275#[cfg(test)]
276mod test {
277 use super::Name;
278 use crate::Error;
279
280 #[test]
281 fn invalid_name_is_permanent_error() {
282 let err = Name::from_str_relaxed(format!("{}.example.org", "a".repeat(64))).unwrap_err();
283 assert_eq!(Error::from(err), Error::Parse);
284 }
285}