public_ip/
dns.rs

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
28///////////////////////////////////////////////////////////////////////////////
29// Hardcoded resolvers
30
31const DEFAULT_DNS_PORT: u16 = 53;
32
33/// All builtin DNS resolvers.
34pub const ALL: &dyn crate::Resolver<'static> = &&[
35    #[cfg(feature = "opendns")]
36    OPENDNS,
37    #[cfg(feature = "google")]
38    GOOGLE,
39];
40
41/// Combined OpenDNS IPv4 and IPv6 options.
42#[cfg(feature = "opendns")]
43#[cfg_attr(docsrs, doc(cfg(feature = "opendns")))]
44pub const OPENDNS: &dyn crate::Resolver<'static> = &&[OPENDNS_V4, OPENDNS_V6];
45
46/// OpenDNS IPv4 DNS resolver options.
47#[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/// OpenDNS IPv6 DNS resolver options.
62#[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        // 2620:0:ccc::2
68        IpAddr::V6(Ipv6Addr::new(9760, 0, 3276, 0, 0, 0, 0, 2)),
69        // 2620:0:ccd::2
70        IpAddr::V6(Ipv6Addr::new(9760, 0, 3277, 0, 0, 0, 0, 2)),
71    ],
72    DEFAULT_DNS_PORT,
73    QueryMethod::AAAA,
74);
75
76/// Combined Google DNS IPv4 and IPv6 options
77#[cfg(feature = "google")]
78#[cfg_attr(docsrs, doc(cfg(feature = "google")))]
79pub const GOOGLE: &dyn crate::Resolver<'static> = &&[GOOGLE_V4, GOOGLE_V6];
80
81/// Google DNS IPv4 DNS resolver options
82#[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/// Google DNS IPv6 DNS resolver options
97#[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        // 2001:4860:4802:32::a
103        IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 50, 0, 0, 0, 10)),
104        // 2001:4860:4802:34::a
105        IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 52, 0, 0, 0, 10)),
106        // 2001:4860:4802:36::a
107        IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 54, 0, 0, 0, 10)),
108        // 2001:4860:4802:38::a
109        IpAddr::V6(Ipv6Addr::new(8193, 18528, 18434, 56, 0, 0, 0, 10)),
110    ],
111    DEFAULT_DNS_PORT,
112    QueryMethod::TXT,
113);
114
115///////////////////////////////////////////////////////////////////////////////
116// Error
117
118/// DNS resolver error.
119pub type Error = ProtoError;
120
121///////////////////////////////////////////////////////////////////////////////
122// Details & options
123
124/// Details produced from a DNS resolution.
125#[derive(Debug, Clone)]
126pub struct Details {
127    name: Name,
128    server: SocketAddr,
129    method: QueryMethod,
130}
131
132impl Details {
133    /// DNS name used in the resolution of our IP address.
134    #[must_use]
135    pub fn name(&self) -> &Name {
136        &self.name
137    }
138
139    /// DNS server used in the resolution of our IP address.
140    #[must_use]
141    pub fn server(&self) -> SocketAddr {
142        self.server
143    }
144
145    /// The query method used in the resolution of our IP address.
146    #[must_use]
147    pub fn query_method(&self) -> QueryMethod {
148        self.method
149    }
150}
151
152/// Method used to query an IP address from a DNS server
153#[derive(Debug, Clone, Copy, PartialEq)]
154#[allow(clippy::upper_case_acronyms)]
155pub enum QueryMethod {
156    /// The first queried `A` name record is extracted as our IP address.
157    A,
158    /// The first queried `AAAA` name record is extracted as our IP address.
159    AAAA,
160    /// The first `TXT` record is extracted and parsed as our IP address.
161    TXT,
162}
163
164///////////////////////////////////////////////////////////////////////////////
165// Resolver
166
167/// Options to build a DNS resolver.
168#[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    /// Create a new DNS resolver.
178    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    /// Create a new DNS resolver from static options.
194    #[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
248///////////////////////////////////////////////////////////////////////////////
249// Resolutions
250
251pin_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///////////////////////////////////////////////////////////////////////////////
278// Client
279
280#[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}