1use hickory_resolver::{
7 config::*,
8 name_server::TokioConnectionProvider,
9 proto::{rr::RecordType, xfer::Protocol},
10 TokioResolver,
11};
12use std::{
13 net::{IpAddr, SocketAddr},
14 sync::Arc,
15};
16
17use crate::prelude::*;
18use cloudillo_types::address::{parse_address_type, AddressType};
19
20const ROOT_SERVERS: [&str; 13] = [
22 "198.41.0.4", "199.9.14.201", "192.33.4.12", "199.7.91.13", "192.203.230.10", "192.5.5.241", "192.112.36.4", "198.97.190.53", "192.36.148.17", "192.58.128.30", "193.0.14.129", "199.7.83.42", "202.12.27.33", ];
36
37pub struct DnsResolver {}
39
40impl DnsResolver {
41 pub fn new() -> ClResult<Self> {
43 debug!("Created DNS resolver with {} root servers", ROOT_SERVERS.len());
44 Ok(Self {})
45 }
46
47 fn create_resolver_for_ns(&self, ns_ips: &[IpAddr]) -> ClResult<TokioResolver> {
49 let mut config = ResolverConfig::new();
50 for ip in ns_ips {
51 let socket_addr = SocketAddr::new(*ip, 53);
52 config.add_name_server(NameServerConfig::new(socket_addr, Protocol::Udp));
53 }
54 Ok(TokioResolver::builder_with_config(config, TokioConnectionProvider::default()).build())
55 }
56
57 async fn resolve_ns_to_ips(
59 &self,
60 ns_names: &[String],
61 resolver: &TokioResolver,
62 ) -> Vec<IpAddr> {
63 let mut ips = Vec::new();
64 for ns_name in ns_names {
65 if let Ok(lookup) = resolver.lookup_ip(ns_name.as_str()).await {
66 for ip in lookup.iter() {
67 ips.push(ip);
68 }
69 }
70 }
71 ips
72 }
73
74 async fn find_authoritative_ns(&self, domain: &str) -> ClResult<Vec<IpAddr>> {
76 let labels: Vec<&str> = domain.trim_end_matches('.').split('.').collect();
77
78 let mut current_ns_ips: Vec<IpAddr> =
80 ROOT_SERVERS.iter().filter_map(|ip| ip.parse().ok()).collect();
81
82 let mut current_resolver = self.create_resolver_for_ns(¤t_ns_ips)?;
83
84 for i in (0..labels.len()).rev() {
86 let subdomain = labels[i..].join(".") + ".";
87
88 debug!(subdomain = %subdomain, "Looking up NS for zone");
89
90 match current_resolver.lookup(subdomain.as_str(), RecordType::NS).await {
92 Ok(ns_lookup) => {
93 let mut ns_names: Vec<String> = Vec::new();
94 let mut glue_ips: Vec<IpAddr> = Vec::new();
95
96 for record in ns_lookup.record_iter() {
98 if let Some(ns) = record.data().as_ns() {
99 let ns_name = ns.0.to_string();
100 debug!(subdomain = %subdomain, ns = %ns_name, "Found NS record");
101 ns_names.push(ns_name);
102 }
103 }
104
105 for record in ns_lookup.record_iter() {
107 if let Some(a) = record.data().as_a() {
108 glue_ips.push(IpAddr::V4(a.0));
109 }
110 if let Some(aaaa) = record.data().as_aaaa() {
111 glue_ips.push(IpAddr::V6(aaaa.0));
112 }
113 }
114
115 if !ns_names.is_empty() {
116 let ns_ips = if glue_ips.is_empty() {
118 self.resolve_ns_to_ips(&ns_names, ¤t_resolver).await
119 } else {
120 glue_ips
121 };
122
123 if !ns_ips.is_empty() {
124 debug!(
125 subdomain = %subdomain,
126 ns_count = ns_ips.len(),
127 "Updated authoritative NS"
128 );
129 current_ns_ips = ns_ips;
130 current_resolver = self.create_resolver_for_ns(¤t_ns_ips)?;
131 }
132 }
133 }
134 Err(e) => {
135 debug!(
137 subdomain = %subdomain,
138 error = %e,
139 "No NS delegation at this level"
140 );
141 }
142 }
143 }
144
145 debug!(
146 domain = %domain,
147 ns_count = current_ns_ips.len(),
148 "Found authoritative nameservers"
149 );
150
151 Ok(current_ns_ips)
152 }
153
154 pub async fn resolve_a(&self, domain: &str) -> ClResult<Option<String>> {
156 debug!(domain = %domain, "Starting A record resolution from root");
157
158 let auth_ns = self.find_authoritative_ns(domain).await?;
159 if auth_ns.is_empty() {
160 warn!(domain = %domain, "Could not find authoritative nameservers");
161 return Ok(None);
162 }
163
164 let auth_resolver = self.create_resolver_for_ns(&auth_ns)?;
165
166 debug!(domain = %domain, "Querying A records from authoritative NS");
167 match auth_resolver.lookup(domain, RecordType::A).await {
168 Ok(lookup) => {
169 for record in lookup.record_iter() {
170 if let Some(a) = record.data().as_a() {
171 let ip = a.0.to_string();
172 debug!(domain = %domain, ip = %ip, "Found A record");
173 return Ok(Some(ip));
174 }
175 }
176 }
177 Err(e) => {
178 debug!(domain = %domain, error = %e, "A lookup failed");
179 }
180 }
181
182 Ok(None)
183 }
184
185 pub async fn resolve_cname(&self, domain: &str) -> ClResult<Option<String>> {
187 debug!(domain = %domain, "Starting CNAME record resolution from root");
188
189 let auth_ns = self.find_authoritative_ns(domain).await?;
190 if auth_ns.is_empty() {
191 warn!(domain = %domain, "Could not find authoritative nameservers");
192 return Ok(None);
193 }
194
195 let auth_resolver = self.create_resolver_for_ns(&auth_ns)?;
196
197 debug!(domain = %domain, "Querying CNAME records from authoritative NS");
198 match auth_resolver.lookup(domain, RecordType::CNAME).await {
199 Ok(lookup) => {
200 for record in lookup.record_iter() {
201 if let Some(cname) = record.data().as_cname() {
202 let target = cname.0.to_string().trim_end_matches('.').to_string();
203 debug!(domain = %domain, cname = %target, "Found CNAME record");
204 return Ok(Some(target));
205 }
206 }
207 }
208 Err(e) => {
209 debug!(domain = %domain, error = %e, "CNAME lookup failed");
210 }
211 }
212
213 Ok(None)
214 }
215}
216
217pub fn create_recursive_resolver() -> ClResult<Arc<DnsResolver>> {
219 Ok(Arc::new(DnsResolver::new()?))
220}
221
222pub async fn resolve_domain_addresses(
225 domain: &str,
226 resolver: &DnsResolver,
227) -> ClResult<Option<String>> {
228 debug!(domain = %domain, "Resolving domain addresses");
229
230 if let Some(cname) = resolver.resolve_cname(domain).await? {
232 return Ok(Some(cname));
233 }
234 if let Some(ip) = resolver.resolve_a(domain).await? {
235 return Ok(Some(ip));
236 }
237
238 debug!(domain = %domain, "No DNS records found");
239 Ok(None)
240}
241
242pub async fn validate_domain_address(
245 domain: &str,
246 local_address: &[Box<str>],
247 resolver: &DnsResolver,
248) -> ClResult<(String, AddressType)> {
249 if local_address.is_empty() {
250 return Err(Error::ValidationError("no local address configured".to_string()));
251 }
252
253 let local_addr_type = parse_address_type(local_address[0].as_ref())?;
255
256 debug!(
257 domain = %domain,
258 local_addresses = ?local_address,
259 local_addr_type = %local_addr_type,
260 "Starting DNS validation with recursive resolver"
261 );
262
263 match local_addr_type {
264 AddressType::Ipv4 => {
265 if let Some(resolved_ip) = resolver.resolve_a(domain).await? {
267 for local_addr in local_address {
268 if resolved_ip == local_addr.as_ref() {
269 info!(
270 domain = %domain,
271 resolved_ip = %resolved_ip,
272 matched_local_address = %local_addr,
273 "Domain validated via A record"
274 );
275 return Ok((resolved_ip, AddressType::Ipv4));
276 }
277 }
278 warn!(
279 domain = %domain,
280 resolved_ip = %resolved_ip,
281 local_addresses = ?local_address,
282 "DNS A record doesn't match local address"
283 );
284 return Err(Error::ValidationError("address".to_string()));
285 }
286 warn!(domain = %domain, "DNS validation failed: no A record found");
287 Err(Error::ValidationError("nodns".to_string()))
288 }
289 AddressType::Hostname => {
290 if let Some(resolved_cname) = resolver.resolve_cname(domain).await? {
292 for local_addr in local_address {
293 if resolved_cname.eq_ignore_ascii_case(local_addr.as_ref()) {
294 info!(
295 domain = %domain,
296 resolved_cname = %resolved_cname,
297 matched_local_address = %local_addr,
298 "Domain validated via CNAME record"
299 );
300 return Ok((resolved_cname, AddressType::Hostname));
301 }
302 }
303 warn!(
304 domain = %domain,
305 resolved_cname = %resolved_cname,
306 local_addresses = ?local_address,
307 "DNS CNAME record doesn't match local address"
308 );
309 return Err(Error::ValidationError("address".to_string()));
310 }
311 warn!(domain = %domain, "DNS validation failed: no CNAME record found");
312 Err(Error::ValidationError("nodns".to_string()))
313 }
314 AddressType::Ipv6 => {
315 Err(Error::ValidationError("IPv6 local address not supported".to_string()))
317 }
318 }
319}
320
321