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