external_ip/sources/
dns.rs1use 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#[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 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
166pub 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}