1use std::borrow::Cow;
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
3use std::pin::Pin;
4use std::str;
5use std::task::{Context, Poll};
6
7use futures_core::Stream;
8use futures_util::ready;
9use futures_util::{future, stream};
10use pin_project_lite::pin_project;
11use tracing::trace_span;
12use tracing_futures::Instrument;
13use trust_dns_proto::{
14 error::{ProtoError, ProtoErrorKind},
15 op::Query,
16 rr::{Name, RData, RecordType},
17 udp::UdpClientStream,
18 xfer::{DnsHandle, DnsRequestOptions, DnsResponse},
19};
20
21#[cfg(feature = "tokio-dns-resolver")]
22use tokio::{net::UdpSocket, runtime::Handle};
23#[cfg(feature = "tokio-dns-resolver")]
24use trust_dns_client::client::AsyncClient;
25
26use crate::{Resolutions, Version};
27
28const DEFAULT_DNS_PORT: u16 = 53;
32
33pub const ALL: &dyn crate::Resolver<'static> = &&[
35 #[cfg(feature = "opendns")]
36 OPENDNS,
37 #[cfg(feature = "google")]
38 GOOGLE,
39];
40
41#[cfg(feature = "opendns")]
43#[cfg_attr(docsrs, doc(cfg(feature = "opendns")))]
44pub const OPENDNS: &dyn crate::Resolver<'static> = &&[OPENDNS_V4, OPENDNS_V6];
45
46#[cfg(feature = "opendns")]
48#[cfg_attr(docsrs, doc(cfg(feature = "opendns")))]
49pub const OPENDNS_V4: &dyn crate::Resolver<'static> = &Resolver::new_static(
50 "myip.opendns.com",
51 &[
52 IpAddr::V4(Ipv4Addr::new(208, 67, 222, 222)),
53 IpAddr::V4(Ipv4Addr::new(208, 67, 220, 220)),
54 IpAddr::V4(Ipv4Addr::new(208, 67, 222, 220)),
55 IpAddr::V4(Ipv4Addr::new(208, 67, 220, 222)),
56 ],
57 DEFAULT_DNS_PORT,
58 QueryMethod::A,
59);
60
61#[cfg(feature = "opendns")]
63#[cfg_attr(docsrs, doc(cfg(feature = "opendns")))]
64pub const OPENDNS_V6: &dyn crate::Resolver<'static> = &Resolver::new_static(
65 "myip.opendns.com",
66 &[
67 IpAddr::V6(Ipv6Addr::new(9760, 0, 3276, 0, 0, 0, 0, 2)),
69 IpAddr::V6(Ipv6Addr::new(9760, 0, 3277, 0, 0, 0, 0, 2)),
71 ],
72 DEFAULT_DNS_PORT,
73 QueryMethod::AAAA,
74);
75
76#[cfg(feature = "google")]
78#[cfg_attr(docsrs, doc(cfg(feature = "google")))]
79pub const GOOGLE: &dyn crate::Resolver<'static> = &&[GOOGLE_V4, GOOGLE_V6];
80
81#[cfg(feature = "google")]
83#[cfg_attr(docsrs, doc(cfg(feature = "google")))]
84pub const GOOGLE_V4: &dyn crate::Resolver<'static> = &Resolver::new_static(
85 "o-o.myaddr.l.google.com",
86 &[
87 IpAddr::V4(Ipv4Addr::new(216, 239, 32, 10)),
88 IpAddr::V4(Ipv4Addr::new(216, 239, 34, 10)),
89 IpAddr::V4(Ipv4Addr::new(216, 239, 36, 10)),
90 IpAddr::V4(Ipv4Addr::new(216, 239, 38, 10)),
91 ],
92 DEFAULT_DNS_PORT,
93 QueryMethod::TXT,
94);
95
96#[cfg(feature = "google")]
98#[cfg_attr(docsrs, doc(cfg(feature = "google")))]
99pub const GOOGLE_V6: &dyn crate::Resolver<'static> = &Resolver::new_static(
100 "o-o.myaddr.l.google.com",
101 &[
102 IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 50, 0, 0, 0, 10)),
104 IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 52, 0, 0, 0, 10)),
106 IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 54, 0, 0, 0, 10)),
108 IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 56, 0, 0, 0, 10)),
110 ],
111 DEFAULT_DNS_PORT,
112 QueryMethod::TXT,
113);
114
115pub type Error = ProtoError;
120
121#[derive(Debug, Clone)]
126pub struct Details {
127 name: Name,
128 server: SocketAddr,
129 method: QueryMethod,
130}
131
132impl Details {
133 #[must_use]
135 pub fn name(&self) -> &Name {
136 &self.name
137 }
138
139 #[must_use]
141 pub fn server(&self) -> SocketAddr {
142 self.server
143 }
144
145 #[must_use]
147 pub fn query_method(&self) -> QueryMethod {
148 self.method
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq)]
154#[allow(clippy::upper_case_acronyms)]
155pub enum QueryMethod {
156 A,
158 AAAA,
160 TXT,
162}
163
164#[derive(Debug)]
169pub struct Resolver<'r> {
170 port: u16,
171 name: Cow<'r, str>,
172 servers: Cow<'r, [IpAddr]>,
173 method: QueryMethod,
174}
175
176impl<'r> Resolver<'r> {
177 pub fn new<N, S>(name: N, servers: S, port: u16, method: QueryMethod) -> Self
179 where
180 N: Into<Cow<'r, str>>,
181 S: Into<Cow<'r, [IpAddr]>>,
182 {
183 Self {
184 port,
185 name: name.into(),
186 servers: servers.into(),
187 method,
188 }
189 }
190}
191
192impl Resolver<'static> {
193 #[must_use]
195 pub const fn new_static(
196 name: &'static str,
197 servers: &'static [IpAddr],
198 port: u16,
199 method: QueryMethod,
200 ) -> Self {
201 Self {
202 port,
203 name: Cow::Borrowed(name),
204 servers: Cow::Borrowed(servers),
205 method,
206 }
207 }
208}
209
210impl<'r> crate::Resolver<'r> for Resolver<'r> {
211 fn resolve(&self, version: Version) -> Resolutions<'r> {
212 let port = self.port;
213 let method = self.method;
214 let name = match Name::from_ascii(self.name.as_ref()) {
215 Ok(name) => name,
216 Err(err) => return Box::pin(stream::once(future::ready(Err(crate::Error::new(err))))),
217 };
218 let mut servers: Vec<_> = self
219 .servers
220 .iter()
221 .copied()
222 .filter(|addr| version.matches(*addr))
223 .collect();
224 let first_server = match servers.pop() {
225 Some(server) => server,
226 None => return Box::pin(stream::empty()),
227 };
228 let record_type = match self.method {
229 QueryMethod::A => RecordType::A,
230 QueryMethod::AAAA => RecordType::AAAA,
231 QueryMethod::TXT => RecordType::TXT,
232 };
233 let span = trace_span!("dns resolver", ?version, ?method, %name, %port);
234 let query = Query::query(name, record_type);
235 let stream = resolve(first_server, port, query.clone(), method);
236 let resolutions = DnsResolutions {
237 port,
238 version,
239 query,
240 method,
241 servers,
242 stream,
243 };
244 Box::pin(resolutions.instrument(span))
245 }
246}
247
248pin_project! {
252 struct DnsResolutions<'r> {
253 port: u16,
254 version: Version,
255 query: Query,
256 method: QueryMethod,
257 servers: Vec<IpAddr>,
258 #[pin]
259 stream: Resolutions<'r>,
260 }
261}
262
263impl<'r> Stream for DnsResolutions<'r> {
264 type Item = Result<(IpAddr, crate::Details), crate::Error>;
265
266 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
267 match ready!(self.as_mut().project().stream.poll_next(cx)) {
268 Some(o) => Poll::Ready(Some(o)),
269 None => self.servers.pop().map_or(Poll::Ready(None), |server| {
270 self.stream = resolve(server, self.port, self.query.clone(), self.method);
271 self.project().stream.poll_next(cx)
272 }),
273 }
274 }
275}
276
277#[cfg(feature = "tokio-dns-resolver")]
281async fn dns_query(
282 server: SocketAddr,
283 query: Query,
284 query_opts: DnsRequestOptions,
285) -> Result<DnsResponse, ProtoError> {
286 let handle = Handle::current();
287 let stream = UdpClientStream::<UdpSocket>::new(server);
288 let (mut client, bg) = AsyncClient::connect(stream).await?;
289 handle.spawn(bg);
290 client.lookup(query, query_opts).await
291}
292
293fn parse_dns_response(
294 mut response: DnsResponse,
295 method: QueryMethod,
296) -> Result<IpAddr, crate::Error> {
297 let answer = match response.take_answers().into_iter().next() {
298 Some(answer) => answer,
299 None => return Err(crate::Error::Addr),
300 };
301 match answer.into_data() {
302 RData::A(addr) if method == QueryMethod::A => Ok(IpAddr::V4(addr)),
303 RData::AAAA(addr) if method == QueryMethod::AAAA => Ok(IpAddr::V6(addr)),
304 RData::TXT(txt) if method == QueryMethod::TXT => match txt.iter().next() {
305 Some(addr_bytes) => Ok(str::from_utf8(&addr_bytes[..])?.parse()?),
306 None => Err(crate::Error::Addr),
307 },
308 _ => Err(ProtoError::from(ProtoErrorKind::Message("invalid response")).into()),
309 }
310}
311
312fn resolve<'r>(server: IpAddr, port: u16, query: Query, method: QueryMethod) -> Resolutions<'r> {
313 let fut = async move {
314 let name = query.name().clone();
315 let server = SocketAddr::new(server, port);
316 let query_opts = DnsRequestOptions {
317 use_edns: true,
318 expects_multiple_responses: false,
319 };
320 let response = dns_query(server, query, query_opts).await?;
321 let addr = parse_dns_response(response, method)?;
322 let details = Box::new(Details {
323 name,
324 server,
325 method,
326 });
327 Ok((addr, crate::Details::from(details)))
328 };
329 Box::pin(stream::once(
330 fut.instrument(trace_span!("query server", %server)),
331 ))
332}