cdns_rs/a_sync/
request.rs

1/*-
2 * cdns-rs - a simple sync/async DNS query library
3 * 
4 * Copyright (C) 2020  Aleksandr Morozov
5 * 
6 * Copyright 2025 Aleksandr Morozov
7 * 
8 * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
9 * the European Commission - subsequent versions of the EUPL (the "Licence").
10 * 
11 * You may not use this work except in compliance with the Licence.
12 * 
13 * You may obtain a copy of the Licence at:
14 * 
15 *    https://joinup.ec.europa.eu/software/page/eupl
16 * 
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the Licence is distributed on an "AS IS" basis, WITHOUT
19 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
20 * Licence for the specific language governing permissions and limitations
21 * under the Licence.
22 */
23
24use std::collections::VecDeque;
25use std::net::IpAddr;
26use std::sync::Arc;
27
28use crate::error::*;
29use crate::internal_error;
30
31use super::caches::CACHE;
32use super::query::QDns;
33use super::{QuerySetup, QDnsQueriesRes, ResolveConfig};
34use super::common::{QType, DnsRdata, DnsSoa};
35
36/// Resolves the A and AAAA query.
37///
38/// # Arguments
39/// 
40/// * `fqdn` - [AsRef] [str] a domain name
41/// 
42/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
43/// 
44/// # Returns
45/// 
46/// [CDnsResult] - Ok with inner type [Vec] [DnsSoa]. If vector is empty
47///     then no results found.
48/// 
49/// [CDnsResult] - Err with error description 
50pub async 
51fn resolve_fqdn<C>(fqdn: C, custom_resolv: Option<Arc<ResolveConfig>>) -> CDnsResult<Vec<IpAddr>>
52where C: AsRef<str>
53{
54    let resolvers = 
55        custom_resolv.map_or(CACHE.clone_resolve_list().await?, |f| f);
56
57    // forming request
58    let dns = 
59        QDns::make_a_aaaa_request(resolvers, fqdn.as_ref(), QuerySetup::default());
60
61    // sending request and receiving results
62    let res = dns.query().await;
63  
64    // extracting data
65    let mut iplist: Vec<IpAddr> = vec![];
66
67    match res
68    {
69        QDnsQueriesRes::DnsOk{ res } =>
70        {
71            for r in res
72            {
73                if r.is_ok() == false
74                {
75                    // skip errors
76                    continue;
77                }
78
79                for dnsr in r.get_responses()
80                {
81                    match dnsr.rdata
82                    {
83                        DnsRdata::A{ ip } => iplist.push(IpAddr::from(ip)),
84                        DnsRdata::AAAA{ ip } => iplist.push(IpAddr::from(ip)),
85                        _ => continue,
86                    }
87                }
88            }
89        },
90        QDnsQueriesRes::DnsNotAvailable =>
91        {
92            internal_error!(CDnsErrorType::DnsNotAvailable, "");
93        }
94    }
95
96    return Ok(iplist);
97}
98
99/// Resolves the MX record by domain name. It returns a list of domains.
100/// The list is sorted by the `preference`.
101/// 
102/// # Arguments
103/// 
104/// * `fqdn` - [AsRef] [str] a domain name
105/// 
106/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
107/// 
108/// # Returns
109/// 
110/// [CDnsResult] - Ok with inner type [Vec] [DnsSoa]. If vector is empty
111///     then no results found.
112/// 
113/// [CDnsResult] - Err with error description 
114pub async 
115fn resolve_mx<C>(fqdn: C, custom_resolv: Option<Arc<ResolveConfig>>) -> CDnsResult<Vec<String>>
116where C: AsRef<str>
117{
118    let resolvers = 
119        custom_resolv.map_or(CACHE.clone_resolve_list().await?, |f| f);
120
121    let mut dns_req = 
122        QDns::make_empty(resolvers, 1, QuerySetup::default());
123
124    dns_req.add_request(QType::MX, fqdn.as_ref());
125
126    // sending request and receiving results
127    let res = dns_req.query().await;
128  
129    // extracting data
130    let mut mxlist: VecDeque<(u16, String)> = VecDeque::with_capacity(5);
131
132    match res
133    {
134        QDnsQueriesRes::DnsOk{ res } =>
135        {
136            for r in res
137            {
138                if r.is_ok() == false
139                {
140                    // skip errors
141                    continue;
142                }
143
144                for dnsr in r.get_responses()
145                {
146                    match dnsr.rdata
147                    {
148                        DnsRdata::MX{ ref preference, ref exchange } => 
149                        {
150                            //iterate and search for the suitable place
151                            let mut index: usize = 0;
152
153                            for (pref, _) in mxlist.iter()
154                            {
155                                if *pref >= *preference
156                                {
157                                    break;
158                                }
159
160                                index += 1;
161                            }
162
163                            if index == mxlist.len()
164                            {
165                                // push back
166                                mxlist.push_back((*preference, exchange.clone()));
167                            }
168                            else
169                            {
170                                mxlist.insert(index, (*preference, exchange.clone()));
171                            }
172                        }
173                        _ => continue,
174                    }
175                }
176            }
177        },
178        QDnsQueriesRes::DnsNotAvailable =>
179        {
180            internal_error!(CDnsErrorType::DnsNotAvailable, "");
181        }
182    }
183
184    return Ok(mxlist.into_iter().map( |(_, ip)| ip).collect());
185}
186
187/// Resolves the SOA record
188/// 
189/// # Arguments
190/// 
191/// * `fqdn` - [AsRef] [str] a domain name
192/// 
193/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
194/// 
195/// # Returns
196/// 
197/// [CDnsResult] - Ok with inner type [Vec] [DnsSoa]. If vector is empty
198///     then no results found.
199/// 
200/// [CDnsResult] - Err with error description 
201pub async 
202fn resolve_soa<C>(fqdn: C, custom_resolv: Option<Arc<ResolveConfig>>) -> CDnsResult<Vec<DnsSoa>>
203where C: AsRef<str>
204{
205    let resolvers = 
206        custom_resolv.map_or(CACHE.clone_resolve_list().await?, |f| f);
207
208    let mut dns_req = 
209        QDns::make_empty(resolvers, 1, QuerySetup::default());
210
211    dns_req.add_request(QType::SOA, fqdn.as_ref());
212
213    // sending request and receiving results
214    let res = dns_req.query().await;
215
216    let mut soa_list: Vec<DnsSoa> = Vec::new();
217
218    match res
219    {
220        QDnsQueriesRes::DnsOk{ res } =>
221        {
222            for r in res
223            {
224                if r.is_ok() == false
225                {
226                    // skip errors
227                    continue;
228                }
229
230                for dnsr in r.move_responses()
231                {
232                    match dnsr.rdata
233                    {
234                        DnsRdata::SOA{ soa } =>
235                        {
236                            soa_list.push(soa);
237                        },
238                        _ => {}
239                    }
240                }
241            }
242        },
243        QDnsQueriesRes::DnsNotAvailable => {}
244    }
245
246    return Ok(soa_list);
247}
248
249/// Resolves the IP address to FQDN
250/// 
251/// # Arguments
252/// 
253/// * `ipaddr` - [AsRef] [str] an IP address
254/// 
255/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
256/// 
257/// # Returns
258/// 
259/// [CDnsResult] - Ok with inner type [Vec] [DnsSoa]. If vector is empty
260///     then no results found.
261/// 
262/// [CDnsResult] - Err with error description 
263pub async 
264fn resolve_reverse<C>(ipaddr: C, custom_resolv: Option<Arc<ResolveConfig>>) -> CDnsResult<Vec<String>>
265where C: AsRef<str>
266{
267    let resolvers = 
268        custom_resolv.map_or(CACHE.clone_resolve_list().await?, |f| f);
269
270    let mut dns_req = 
271        QDns::make_empty(resolvers, 1, QuerySetup::default());
272
273    dns_req.add_request(QType::PTR, ipaddr.as_ref());
274
275    // sending request and receiving results
276    let res = dns_req.query().await;
277
278    let mut ptr_list: Vec<String> = Vec::new();
279
280    match res
281    {
282        QDnsQueriesRes::DnsOk{ res } =>
283        {
284            for r in res
285            {
286                if r.is_ok() == false
287                {
288                    // skip errors
289                    continue;
290                }
291
292                for dnsr in r.move_responses()
293                {
294                    match dnsr.rdata
295                    {
296                        DnsRdata::PTR{ fqdn } =>
297                        {
298                            ptr_list.push(fqdn);
299                        },
300                        _ => {}
301                    }
302                }
303            }
304        },
305        QDnsQueriesRes::DnsNotAvailable => {}
306    }
307
308    return Ok(ptr_list);
309}
310
311#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
312async fn test_mx_resolve()
313{
314    let mx_doms = resolve_mx("protonmail.com", None).await;
315
316    assert_eq!(mx_doms.is_err(), false);
317
318    let mx_doms = mx_doms.unwrap();
319
320    let mut index = 0;
321    for di in mx_doms
322    {
323        match index
324        {
325            0 => assert_eq!(di.as_str(), "mail.protonmail.ch"),
326            1 => assert_eq!(di.as_str(), "mailsec.protonmail.ch"),
327            _ => panic!("test is required to be modified")
328        }
329
330        index += 1;
331
332        println!("{}", di);
333    }
334    
335}
336
337
338#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
339async fn test_a_aaaa_resolve()
340{
341    let a_aaaa = resolve_fqdn("protonmail.com", None).await;
342
343    assert_eq!(a_aaaa.is_ok(), true);
344
345    let a_aaaa = a_aaaa.unwrap();
346
347    for di in a_aaaa
348    {
349        println!("{}", di);
350    }
351    
352}
353
354#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
355async fn test_ptr_resolve_fail()
356{
357    let ptr = resolve_reverse("protonmail.com", None).await;
358
359    assert_eq!(ptr.is_ok(), true);
360
361    let ptr = ptr.unwrap();
362
363    assert_eq!(ptr.len(), 0);
364    
365}