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 (C) 2025 Aleksandr Morozov
7 * 
8 * The syslog-rs crate can be redistributed and/or modified
9 * under the terms of either of the following licenses:
10 *
11 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
12 *                     
13 *   2. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
14 */
15
16use std::net::IpAddr;
17use std::sync::Arc;
18
19use crate::a_sync::interface::MutexedCaches;
20use crate::a_sync::network::SocketTaps;
21use crate::a_sync::CachesController;
22use crate::common::RecordSOA;
23use crate::error::*;
24
25use super::query::QDns;
26use super::DnsRdata;
27use super::QType;
28use super::{QuerySetup, ResolveConfig};
29
30/// Resolves the A and AAAA query.
31///
32/// # Arguments
33/// 
34/// * `fqdn` - [AsRef] [str] a domain name
35/// 
36/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
37/// 
38/// # Returns
39/// 
40/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
41///     then no results found.
42/// 
43/// [CDnsResult] - Err with error description 
44pub async 
45fn resolve_fqdn<C, LOC, TAP, MC>(
46    fqdn: C, 
47    custom_resolv: Option<Arc<ResolveConfig>>, 
48    cache: Arc<CachesController<MC>>
49) -> CDnsResult<Vec<IpAddr>>
50where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
51{
52    // forming request
53    let dns = 
54        QDns::<LOC, TAP, MC>::make_a_aaaa_request(custom_resolv, fqdn.as_ref(), 
55            QuerySetup::default(), cache).await?;
56
57    // sending request and receiving results
58    let res = dns.query().await;
59  
60    // extracting data
61    let mut iplist: Vec<IpAddr> = vec![];
62
63    let recs = res.get_result()?;
64
65    for dnsr in recs.into_iter()
66    {
67        for resp in dnsr.into_iter()
68        {
69            match resp.borrow_rdata()
70            {
71                DnsRdata::A(ip) => 
72                    iplist.push(IpAddr::from(ip.ip)),
73                DnsRdata::AAAA( ip ) => 
74                    iplist.push(IpAddr::from(ip.ip)),
75                _ => continue,
76            }
77        }
78    }
79
80    return Ok(iplist);
81}
82
83/// Resolves the MX record by domain name. It returns a list of domains.
84/// The list is sorted by the `preference`.
85/// 
86/// # Arguments
87/// 
88/// * `fqdn` - [AsRef] [str] a domain name
89/// 
90/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
91/// 
92/// # Returns
93/// 
94/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
95///     then no results found.
96/// 
97/// [CDnsResult] - Err with error description 
98pub async 
99fn resolve_mx<C, LOC, TAP, MC>(
100    fqdn: C, 
101    custom_resolv: Option<Arc<ResolveConfig>>, 
102    cache: Arc<CachesController<MC>>
103) -> CDnsResult<Vec<String>>
104where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
105{
106    let mut dns_req = 
107        QDns::<LOC, TAP, MC>::make_empty(custom_resolv, QuerySetup::default(), cache).await?;
108
109    dns_req.add_request(QType::MX, fqdn.as_ref());
110
111    // sending request and receiving results
112    let res = dns_req.query().await;
113  
114    // extracting data
115    let mut mxlist: Vec<(u16, String)> = Vec::new();
116
117    let recs = res.get_result()?;
118
119    for dnsr in recs.into_iter()
120    {
121        for resp in dnsr.into_iter()
122        {
123            match resp.borrow_rdata()
124            {
125                DnsRdata::MX(mx) =>
126                {
127                    //iterate and search for the suitable place
128                    //let mut index: usize = 0;
129
130                    let idx = mxlist.binary_search_by_key(&mx.preference, |x| x.0).map_or_else(|v| v, |f| f);
131                    mxlist.insert(idx, (mx.preference, mx.exchange));
132
133                    /*for (pref, _) in mxlist.iter()
134                    {
135                        if *pref >= mx.preference
136                        {
137                            break;
138                        }
139
140                        index += 1;
141                    }
142
143                    if index == mxlist.len()
144                    {
145                        // push back
146                        mxlist.push((mx.preference, mx.exchange.clone()));
147                    }
148                    else
149                    {
150                        mxlist.insert(index, (*preference, exchange.clone()));
151                    }*/
152                },
153                _ => continue,
154            }
155        }
156    }
157
158    return Ok(mxlist.into_iter().map( |(_, ip)| ip).collect());
159}
160
161/// Resolves the SOA record
162/// 
163/// # Arguments
164/// 
165/// * `fqdn` - [AsRef] [str] a domain name
166/// 
167/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
168/// 
169/// # Returns
170/// 
171/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
172///     then no results found.
173/// 
174/// [CDnsResult] - Err with error description 
175pub async 
176fn resolve_soa<C, LOC, TAP, MC>(
177    fqdn: C, 
178    custom_resolv: Option<Arc<ResolveConfig>>, 
179    cache: Arc<CachesController<MC>>
180) -> CDnsResult<Vec<RecordSOA>>
181where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
182{
183    let mut dns_req = 
184        QDns::<LOC, TAP, MC>::make_empty(custom_resolv, QuerySetup::default(), cache).await?;
185
186    dns_req.add_request(QType::SOA, fqdn.as_ref());
187
188    // sending request and receiving results
189    let res = dns_req.query().await;
190
191    let mut soa_list: Vec<RecordSOA> = Vec::new();
192
193    let recs = res.get_result()?;
194
195    for dnsr in recs.into_iter()
196    {
197        for resp in dnsr.into_iter()
198        {
199            match resp.borrow_rdata()
200            {
201                DnsRdata::SOA(soa) => 
202                    soa_list.push(soa),
203                _ => continue,
204            }
205        }
206    }
207
208    return Ok(soa_list);
209}
210
211/// Resolves the IP address to FQDN
212/// 
213/// # Arguments
214/// 
215/// * `ipaddr` - [AsRef] [str] an IP address
216/// 
217/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
218/// 
219/// # Returns
220/// 
221/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
222///     then no results found.
223/// 
224/// [CDnsResult] - Err with error description 
225pub async 
226fn resolve_reverse<C, LOC, TAP, MC>(
227    ipaddr: C, 
228    custom_resolv: Option<Arc<ResolveConfig>>,
229    cache: Arc<CachesController<MC>>
230) -> CDnsResult<Vec<String>>
231where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
232{
233    let mut dns_req = 
234        QDns::<LOC, TAP, MC>::make_empty(custom_resolv, QuerySetup::default(), cache).await?;
235
236    dns_req.add_request(QType::PTR, ipaddr.as_ref());
237
238    // sending request and receiving results
239    let res = dns_req.query().await;
240
241    let recs = res.get_result()?;
242
243    let mut ptr_list: Vec<String> = Vec::new();
244
245    for dnsr in recs.into_iter()
246    {
247        for resp in dnsr.into_iter()
248        {
249            match resp.borrow_rdata()
250            {
251                DnsRdata::PTR(ptr) => 
252                    ptr_list.push(ptr.fqdn),
253                _ => continue,
254            }
255        }
256    }
257
258    return Ok(ptr_list);
259}
260
261#[cfg(feature = "use_async_tokio")]
262#[cfg(test)]
263mod tests
264{
265    use std::sync::Arc;
266
267    use crate::a_sync::{request::{resolve_fqdn, resolve_mx, resolve_reverse}, SocketBase, CachesController, TokioInterf};
268
269    #[tokio::test]
270    async fn test_mx_resolve()
271    {
272        let cache = Arc::new(CachesController::new().await.unwrap());
273
274        let mx_doms = resolve_mx::<_, SocketBase, SocketBase, TokioInterf>("protonmail.com", None, cache).await;
275
276        assert_eq!(mx_doms.is_err(), false);
277
278        let mx_doms = mx_doms.unwrap();
279
280        let mut index = 0;
281        for di in mx_doms
282        {
283            match index
284            {
285                0 => assert_eq!(di.as_str(), "mail.protonmail.ch"),
286                1 => assert_eq!(di.as_str(), "mailsec.protonmail.ch"),
287                _ => panic!("test is required to be modified")
288            }
289
290            index += 1;
291
292            println!("{}", di);
293        }
294        
295    }
296
297
298    #[tokio::test]
299    async fn test_a_aaaa_resolve()
300    {
301        let cache = Arc::new(CachesController::new().await.unwrap());
302
303        let a_aaaa = resolve_fqdn::<_, SocketBase, SocketBase, TokioInterf>("protonmail.com", None, cache).await;
304
305        assert_eq!(a_aaaa.is_ok(), true);
306
307        let a_aaaa = a_aaaa.unwrap();
308
309        for di in a_aaaa
310        {
311            println!("{}", di);
312        }
313        
314    }
315
316    #[tokio::test]
317    async fn test_ptr_resolve_fail()
318    {
319        let cache = Arc::new(CachesController::new().await.unwrap());
320
321        let ptr = resolve_reverse::<_, SocketBase, SocketBase, TokioInterf>("protonmail.com", None, cache).await;
322
323        assert_eq!(ptr.is_ok(), true);
324
325        let ptr = ptr.unwrap();
326
327        assert_eq!(ptr.len(), 0);
328        
329    }
330}