Skip to main content

external_ip/sources/
dns.rs

1use crate::sources::interfaces::{Error, Family, IpFuture, IpResult, Source};
2use log::trace;
3
4use hickory_resolver::TokioResolver;
5use hickory_resolver::config::*;
6use hickory_resolver::net::runtime::TokioRuntimeProvider;
7use hickory_resolver::proto::rr::RData;
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<TokioResolver, 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        trace!(
73            "Bootstrapping resolver for {} with strategy {:?}",
74            self.server, resolver_opts.ip_strategy
75        );
76        let mut builder = TokioResolver::builder_with_config(
77            ResolverConfig::udp_and_tcp(&GOOGLE),
78            TokioRuntimeProvider::default(),
79        );
80        *builder.options_mut() = resolver_opts.clone();
81        let resolver = builder.build()?;
82
83        let mut name_servers = Vec::new();
84        for found_ip in resolver.lookup_ip(&self.server).await?.iter() {
85            trace!("DNS address {}", found_ip);
86            name_servers.push(NameServerConfig::udp(found_ip));
87        }
88
89        let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
90
91        let mut builder =
92            TokioResolver::builder_with_config(config, TokioRuntimeProvider::default());
93        *builder.options_mut() = resolver_opts;
94        Ok(builder.build()?)
95    }
96}
97
98impl Source for DNSSource {
99    fn get_ip(&self, family: Family) -> IpFuture<'_> {
100        async fn run(_self: &DNSSource, family: Family) -> IpResult {
101            if matches!(
102                (family, _self.record_type),
103                (Family::IPv4, QueryType::AAAA) | (Family::IPv6, QueryType::A)
104            ) {
105                return Err(Error::UnsupportedFamily);
106            }
107            trace!("Contacting {:?} for {}", _self.server, _self.record);
108            let resolver: TokioResolver = _self
109                .get_resolver(match _self.record_type {
110                    QueryType::A => Family::IPv4,
111                    QueryType::AAAA => Family::IPv6,
112                    _ => family,
113                })
114                .await?;
115
116            match _self.record_type {
117                QueryType::TXT => {
118                    for reply in resolver.txt_lookup(_self.record.clone()).await?.answers() {
119                        if let RData::TXT(txt) = &reply.data {
120                            for txt in txt.txt_data.iter() {
121                                if let Ok(data) = std::str::from_utf8(txt) {
122                                    let ip: std::net::IpAddr = data.parse()?;
123                                    if family == Family::Any
124                                        || (family == Family::IPv4 && ip.is_ipv4())
125                                        || (family == Family::IPv6 && ip.is_ipv6())
126                                    {
127                                        return Ok(ip);
128                                    }
129                                }
130                            }
131                        }
132                    }
133                }
134                QueryType::A => {
135                    if family == Family::IPv4 || family == Family::Any {
136                        for reply in resolver.lookup_ip(_self.record.clone()).await?.iter() {
137                            if reply.is_ipv4() {
138                                return Ok(reply);
139                            }
140                        }
141                    }
142                    return Err(Error::UnsupportedFamily);
143                }
144                QueryType::AAAA => {
145                    if family == Family::IPv6 || family == Family::Any {
146                        for reply in resolver.lookup_ip(_self.record.clone()).await?.iter() {
147                            if reply.is_ipv6() {
148                                return Ok(reply);
149                            }
150                        }
151                    }
152                    return Err(Error::UnsupportedFamily);
153                }
154            }
155            Err(Error::DnsResolutionEmpty)
156        }
157        Box::pin(run(self, family))
158    }
159
160    fn box_clone(&self) -> Box<dyn Source> {
161        Box::new(self.clone())
162    }
163}
164
165/// Returns a collection of DNS sources to use to retrieve the external ip
166pub fn get_dns_sources<T>() -> T
167where
168    T: std::iter::FromIterator<Box<dyn Source>>,
169{
170    vec![
171        DNSSource::source(
172            String::from("resolver1.opendns.com"),
173            QueryType::A,
174            "myip.opendns.com",
175        ),
176        DNSSource::source(
177            String::from("resolver1.opendns.com"),
178            QueryType::AAAA,
179            "myip.opendns.com",
180        ),
181        DNSSource::source(
182            String::from("ns1.google.com"),
183            QueryType::TXT,
184            "o-o.myaddr.l.google.com",
185        ),
186    ]
187    .into_iter()
188    .collect()
189}