external_ip/sources/
dns.rs

1use crate::sources::interfaces::{Error, Family, IpFuture, IpResult, Source};
2use log::trace;
3
4use std::net::SocketAddr;
5
6use hickory_resolver::config::*;
7use hickory_resolver::TokioAsyncResolver;
8
9#[derive(Debug, Clone, Copy)]
10pub enum QueryType {
11    TXT,
12    A,
13    AAAA,
14}
15
16/// DNS Source of the external ip
17///
18/// It expects a DNS server to target for a query (currently only A and TXT), to retrive in the
19/// reply of the message the IP.
20/// A few services are known for replying with the IP of the query sender.
21#[derive(Debug, Clone)]
22pub struct DNSSource {
23    server: String,
24    record_type: QueryType,
25    record: String,
26}
27
28impl DNSSource {
29    pub fn new<S: Into<String>, R: Into<String>>(
30        server: S,
31        record_type: QueryType,
32        record: R,
33    ) -> Self {
34        DNSSource {
35            server: server.into(),
36            record_type,
37            record: record.into(),
38        }
39    }
40    fn source<R: Into<String>>(
41        server: String,
42        record_type: QueryType,
43        record: R,
44    ) -> Box<dyn Source> {
45        Box::new(DNSSource {
46            server,
47            record_type,
48            record: record.into(),
49        })
50    }
51}
52
53impl std::fmt::Display for DNSSource {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(
56            f,
57            "DnsSource: {:?} {:?} {}",
58            self.server, self.record_type, self.record
59        )
60    }
61}
62
63impl DNSSource {
64    async fn get_resolver(self: &DNSSource, family: Family) -> Result<TokioAsyncResolver, Error> {
65        let mut resolver_opts = ResolverOpts::default();
66        resolver_opts.ip_strategy = match family {
67            Family::IPv4 => LookupIpStrategy::Ipv4Only,
68            Family::IPv6 => LookupIpStrategy::Ipv6Only,
69            Family::Any => resolver_opts.ip_strategy,
70        };
71
72        let resolver = TokioAsyncResolver::tokio(ResolverConfig::default(), resolver_opts.clone());
73        let mut config = ResolverConfig::new();
74        for found_ip in resolver.lookup_ip(&self.server).await?.iter() {
75            let address = SocketAddr::new(found_ip, 53);
76            trace!("DNS address {}", address);
77            config.add_name_server(NameServerConfig {
78                bind_addr: None,
79                socket_addr: address,
80                protocol: hickory_resolver::config::Protocol::Udp,
81                tls_dns_name: None,
82                trust_negative_responses: true,
83            });
84        }
85
86        Ok(TokioAsyncResolver::tokio(config, resolver_opts))
87    }
88}
89
90impl Source for DNSSource {
91    fn get_ip(&self, family: Family) -> IpFuture<'_> {
92        async fn run(_self: &DNSSource, family: Family) -> IpResult {
93            if matches!(
94                (family, _self.record_type),
95                (Family::IPv4, QueryType::AAAA) | (Family::IPv6, QueryType::A)
96            ) {
97                return Err(Error::UnsupportedFamily);
98            }
99            trace!("Contacting {:?} for {}", _self.server, _self.record);
100            let resolver = _self
101                .get_resolver(match _self.record_type {
102                    QueryType::A => Family::IPv4,
103                    QueryType::AAAA => Family::IPv6,
104                    _ => family,
105                })
106                .await?;
107
108            match _self.record_type {
109                QueryType::TXT => {
110                    for reply in resolver.txt_lookup(_self.record.clone()).await?.iter() {
111                        for txt in reply.txt_data().iter() {
112                            let data = std::str::from_utf8(txt);
113                            if data.is_err() {
114                                continue;
115                            }
116
117                            let ip = data.unwrap().parse()?;
118                            if family == Family::Any {
119                                return Ok(ip);
120                            } else if family == Family::IPv4 {
121                                if ip.is_ipv4() {
122                                    return Ok(ip);
123                                }
124                                return Err(Error::DnsResolutionEmpty);
125                            } else {
126                                // if family == Family::IPv6
127                                if ip.is_ipv6() {
128                                    return Ok(ip);
129                                }
130                                return Err(Error::UnsupportedFamily);
131                            }
132                        }
133                    }
134                }
135                QueryType::A => {
136                    if family == Family::IPv4 || family == Family::Any {
137                        for reply in resolver.lookup_ip(_self.record.clone()).await?.iter() {
138                            if reply.is_ipv4() {
139                                return Ok(reply);
140                            }
141                        }
142                    }
143                    return Err(Error::UnsupportedFamily);
144                }
145                QueryType::AAAA => {
146                    if family == Family::IPv6 || family == Family::Any {
147                        for reply in resolver.lookup_ip(_self.record.clone()).await?.iter() {
148                            if reply.is_ipv6() {
149                                return Ok(reply);
150                            }
151                        }
152                    }
153                    return Err(Error::UnsupportedFamily);
154                }
155            }
156            Err(Error::DnsResolutionEmpty)
157        }
158        Box::pin(run(self, family))
159    }
160
161    fn box_clone(&self) -> Box<dyn Source> {
162        Box::new(self.clone())
163    }
164}
165
166/// Returns a collection of DNS sources to use to retrieve the external ip
167pub fn get_dns_sources<T>() -> T
168where
169    T: std::iter::FromIterator<Box<dyn Source>>,
170{
171    vec![
172        DNSSource::source(
173            String::from("resolver1.opendns.com"),
174            QueryType::A,
175            "myip.opendns.com",
176        ),
177        DNSSource::source(
178            String::from("resolver1.opendns.com"),
179            QueryType::AAAA,
180            "myip.opendns.com",
181        ),
182        DNSSource::source(
183            String::from("ns1.google.com"),
184            QueryType::TXT,
185            "o-o.myaddr.l.google.com",
186        ),
187    ]
188    .into_iter()
189    .collect()
190}