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