cdns_rs/a_sync/
query.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
24
25use std::collections::{BTreeSet, HashMap};
26use std::convert::TryFrom;
27use std::net::IpAddr;
28use std::sync::Arc;
29use std::time::Duration;
30use std::time::Instant;
31
32use async_recursion::async_recursion;
33use tokio::net::{TcpStream, UdpSocket};
34
35use crate::cfg_resolv_parser::{ConfigEntryTls, ResolveConfEntry, ResolveConfigFamily};
36use crate::common::{CDdnsGlobals, DnsRequestAnswer, DnsRequestHeader};
37use crate::{error::*, DnsResponsePayload, QDnsQueryResult};
38use crate::query::QDnsQuery;
39use crate::{write_error, internal_error};
40use crate::query_private::QDnsReq;
41
42use super::caches::CACHE;
43use super::network::{NetworkTap, NetworkTapType, SocketTap, TcpTlsConnection};
44use super::{QDnsName, QType, QuerySetup, ResolveConfig};
45
46/// A main instance which contains all common logic.
47#[derive(Clone, Debug)]
48pub struct QDns
49{
50    /// An instance of the parser /etc/resolv.conf or custom config
51    resolvers: Arc<ResolveConfig>,
52
53    /// A pre-ordered list of the requests, if more than one
54    ordered_req_list: Vec<QDnsReq>,
55
56    /// Override options
57    opts: QuerySetup,
58}
59
60
61impl QDns
62{
63    /// Initializes new empty storage for requests.
64    /// 
65    /// In some cases it is good idea to combine different requests, because by default
66    /// all requests are performed in parallel. But in some cases it is bad idea.
67    /// 
68    /// # Arguments
69    /// 
70    /// * `resolvers` - an [Arc] [ResolveConfig] which contains configuration i.e nameservers
71    /// 
72    /// * `planned_reqs_len` - how many requests are planned
73    /// 
74    /// * `opts` - [QuerySetup] additional options or overrides. Use default() for default
75    ///     values.
76    /// 
77    /// # Returns
78    /// 
79    /// Never panics. Returns Self.
80    pub async 
81    fn make_empty(resolvers: Option<Arc<ResolveConfig>>, opts: QuerySetup) -> CDnsResult<Self>
82    {
83        return Ok(
84            Self
85            {
86                resolvers: resolvers.unwrap_or(CACHE.clone_resolve_list().await?),
87                ordered_req_list: Vec::new(),
88                opts: opts,
89            }
90        );
91    }
92
93    /// Adds new request to previously created empty storage for request with [QDns::make_empty].
94    /// 
95    /// # Arguemnts
96    /// 
97    /// * `qtype` - a [QType] type of the request
98    /// 
99    /// * `req_name` - a [Into] [QDnsName] which is target. i.e 'localhost' or 'domain.tld'
100    /// 
101    /// # Returns
102    /// 
103    /// * [CDnsResult] - Ok with nothing in inner type
104    /// 
105    /// * [CDnsResult] - Err with error description
106    pub 
107    fn add_request(&mut self, qtype: QType, req_name: impl Into<QDnsName>)
108    {
109        let qr = QDnsReq::new_into(req_name, qtype);
110
111        self.ordered_req_list.push(qr);
112
113        return;
114    }
115
116    /// This is helper which makes for you an A, AAAA query. The order of A and AAAA and
117    /// which from both are allowed is defined in the [ResolveConfig].
118    /// 
119    /// Use this function directly. Do not use [QDns::make_empty]
120    /// 
121    /// # Arguments
122    /// 
123    /// * `resolvers` - an [Option] value [Arc] [ResolveConfig] which can be used to 
124    ///     override the system's `resolv.conf`
125    /// 
126    /// * `req_name` - a [Into] [QDnsName] which is target i.e 'localhost' or 'domain.tld'
127    /// 
128    /// * `opts` - [QuerySetup] additional options or overrides. Use default() for default
129    ///     values.
130    /// 
131    /// # Returns
132    /// 
133    /// A [CDnsResult] is returned;
134    /// 
135    /// * [Result::Ok] - with Self as inner type
136    /// 
137    /// * [Result::Err] - with error description
138    pub async 
139    fn make_a_aaaa_request(resolvers_opt: Option<Arc<ResolveConfig>>, req_name: impl Into<QDnsName>, 
140        opts: QuerySetup) -> CDnsResult<Self>
141    {
142        let resolvers = resolvers_opt.unwrap_or(CACHE.clone_resolve_list().await?);
143
144        // store the A and AAAA depending on order
145        let reqs: Vec<QDnsReq> = 
146            match resolvers.family
147            {
148                ResolveConfigFamily::INET4_INET6 => 
149                {
150                    let req_n: QDnsName = req_name.into();
151
152                    vec![
153                        QDnsReq::new(req_n.clone(), QType::A),
154                        QDnsReq::new(req_n, QType::AAAA),
155                    ]
156                },
157                ResolveConfigFamily::INET6_INET4 => 
158                {
159                    let req_n: QDnsName = req_name.into();
160
161                    vec![
162                        QDnsReq::new(req_n.clone(), QType::AAAA),
163                        QDnsReq::new(req_n, QType::A),
164                    ]
165                },
166                ResolveConfigFamily::INET6 => 
167                {
168                    vec![
169                        QDnsReq::new(req_name.into(), QType::AAAA),
170                    ]
171                },
172                ResolveConfigFamily::INET4 => 
173                {
174                    vec![
175                        QDnsReq::new(req_name.into(), QType::A),
176                    ]
177                }
178                _ =>
179                {
180                    // set default
181                    let req_n: QDnsName = req_name.into();
182
183                    vec![
184                        QDnsReq::new(req_n.clone(), QType::A),
185                        QDnsReq::new(req_n, QType::AAAA),
186                    ]
187                }
188            };
189
190        
191        
192        return Ok(
193            Self
194            {
195                resolvers: resolvers,
196                ordered_req_list: reqs,
197                opts: opts,
198            }
199        );
200    }
201
202    /// Runs the created query/ies
203    /// 
204    /// # Returns
205    /// 
206    /// * [CDnsResult] with [QDnsQueryResult] which may contain results
207    /// 
208    /// * [CDnsResult] with error
209    /// 
210    /// Should not panic. MT-safe.
211    pub async 
212    fn query(mut self) -> QDnsQueryResult
213    {
214        // check if we need to measure time
215        let now = 
216            if self.opts.measure_time == true
217            {
218                Some(Instant::now())
219            }
220            else
221            {
222                None
223            };
224
225        // determine where to look firstly i.e file -> bind, bind -> file
226        // or bind only, or file only
227        if self.resolvers.lookup.is_file_first()
228        {
229            let mut query_res = 
230                match self.lookup_file(now.as_ref()).await
231                {
232                    Ok(file) => 
233                    {
234                        if file.is_empty() == false
235                        {
236                            // remove records which was resolved
237                            self.ordered_req_list.retain(|req| 
238                                {
239                                    return !file.contains_dnsreq(req);
240                                }
241                            );
242                        }
243
244                        file
245                    },
246                    Err(e) => 
247                    {
248                        write_error!(e);
249
250                        QDnsQueryResult::default()
251                    }
252                };
253
254
255            // if something left unresolved, try ask internet
256            if self.ordered_req_list.is_empty() == false && self.resolvers.lookup.is_bind() == true
257            {
258                let res = self.process_request(now.as_ref()).await;
259                
260                query_res.extend(res);
261            }
262
263            return query_res;
264        }
265        else
266        {
267            let mut dns_res = self.process_request(now.as_ref()).await;
268            if dns_res.is_empty() == false
269            {
270                // remove records which was resolved
271                self.ordered_req_list.retain(|req| 
272                    {
273                        return !dns_res.contains_dnsreq(req);
274                    }
275                );
276            }
277
278
279
280            if self.ordered_req_list.is_empty() == false && self.resolvers.lookup.is_file() == true
281            {
282                match self.lookup_file(now.as_ref()).await
283                {
284                    Ok(res) => 
285                    {
286                        dns_res.extend(res);
287                    },
288                    Err(e) =>
289                    {
290                        write_error!(e);
291                    }
292                }
293            }
294
295            return dns_res;
296        }
297    }
298
299    /// Returns timeout
300    fn get_timeout(&self) -> Duration
301    {
302        if let Some(timeout) = self.opts.timeout
303        {
304            return Duration::from_secs(timeout as u64);
305        }
306        else
307        {
308            return Duration::from_secs(self.resolvers.timeout as u64);
309        }
310    }
311
312    /// Searches in /etc/hosts
313    async 
314    fn lookup_file(&mut self, now: Option<&Instant>) -> CDnsResult<QDnsQueryResult>
315    {
316        let mut dnsquries: QDnsQueryResult = QDnsQueryResult::default();
317
318        // check if the it is overriden
319        if self.opts.ign_hosts == false
320        {
321            let hlist = CACHE.clone_host_list().await?;
322
323            for req in self.ordered_req_list.iter()
324            {
325                match *req.get_type()
326                {
327                    QType::A | QType::AAAA => 
328                    {
329                        let req_name = String::from(req.get_req_name());
330
331                        let Some(host_name_ent) = hlist.search_by_fqdn(req.get_type(), req_name.as_str())
332                            else { continue };
333                        
334                        let Some(drp) = DnsResponsePayload::new_local(*req.get_type(), host_name_ent)
335                            else { continue };
336                        
337                        // store to list
338                        dnsquries.push(req.clone(), Ok(QDnsQuery::from_local(drp, now)));
339                    },
340                    QType::PTR => 
341                    {
342                        let Ok(ip) = IpAddr::try_from(req.get_req_name())
343                            else { continue };
344
345                        let Some(host_name_ent) = hlist.search_by_ip(&ip)
346                            else { continue };
347
348                        let Some(drp) = DnsResponsePayload::new_local(*req.get_type(), host_name_ent)
349                            else { continue };    
350
351                        dnsquries.push(req.clone(), Ok(QDnsQuery::from_local(drp, now))); 
352                    },
353                    _ => 
354                        continue,
355                }
356            }
357
358        }
359
360        return Ok(dnsquries);
361    }
362
363/// Creates socket based on config and flag. If `force_tcp` is set then Tcp tap will
364    /// be created.
365    #[inline]
366    fn create_socket(&self, force_tcp: bool, resolver: Arc<ResolveConfEntry>) -> CDnsResult<Box<NetworkTapType>>
367    {
368//        if self.resolvers.option_flags.contains(OptionFlags::OPT_SINGLE_REQUEST_REOPEN)
369        if resolver.get_tls_type() != ConfigEntryTls::None
370        {
371            return NetworkTap::<TcpTlsConnection>::new(resolver, self.get_timeout());
372        }
373        else if self.resolvers.option_flags.is_force_tcp() == true || force_tcp == true
374        {
375            return NetworkTap::<TcpStream>::new(resolver, self.get_timeout());
376        }
377        else
378        {
379            return NetworkTap::<UdpSocket>::new(resolver, self.get_timeout());
380        }
381    }
382
383    /// Quering all nameservers
384    async 
385    fn process_request(&mut self, now: Option<&Instant>) -> QDnsQueryResult
386    {
387        let mut responses: QDnsQueryResult = QDnsQueryResult::with_capacity(self.ordered_req_list.len());
388
389        if self.resolvers.option_flags.is_no_parallel() == true
390        {
391            for req in self.ordered_req_list.iter()
392            {
393                let mut last_resp: Option<CDnsResult<QDnsQuery>> = None;
394
395                for resolver in self.resolvers.get_resolvers_iter()
396                {
397                    match self.query_exec_seq(now, resolver.clone(), req, None).await
398                    {
399                        Ok(resp) =>
400                        {
401                            if resp.should_check_next_ns() == true
402                            {
403                                last_resp = Some(Ok(resp));
404
405                                continue;
406                            }
407                            else
408                            {
409                                responses.push(req.clone(), Ok(resp));
410
411                                let _ = last_resp.take();
412
413                                break;
414                            }
415                        },
416                        Err(e) =>
417                        {
418                            if last_resp.is_none() == true
419                            {
420                                last_resp = Some(Err(e));
421                            }                      
422
423                            continue;
424                        }
425                    }
426                } // for
427
428                responses.push(req.clone(), last_resp.take().unwrap());
429            }// for
430        }
431        else
432        {
433           // let mut responses: QDnsQueryResult = QDnsQueryResult::with_capacity(self.ordered_req_list.len());
434
435            for resolver in self.resolvers.get_resolvers_iter()
436            {
437                if self.ordered_req_list.is_empty() == true
438                {
439                    break;
440                }
441
442                match self.query_exec_pipelined(now, resolver.clone(), None).await
443                {
444                    Ok(resp) =>
445                    {
446                        for (qdns_res, qdns_que) in resp
447                        {
448                            if let Ok(ref resp) = qdns_que
449                            {
450                                if resp.should_check_next_ns() == false
451                                    {
452                                        self
453                                            .ordered_req_list
454                                            .retain(
455                                                |req_item|
456                                                req_item != &qdns_res
457                                            );
458                                    }
459                            }
460                            
461                            responses.push(qdns_res, qdns_que);
462                        }
463                    },
464                    Err(e) =>
465                    {
466                        write_error!(e);
467
468                        continue;
469                    }
470                }
471            }
472        }
473
474        return responses;
475    }
476
477    #[async_recursion]
478    async 
479    fn query_exec_pipelined(
480        &self, 
481        now: Option<&Instant>, 
482        resolver: Arc<ResolveConfEntry>,
483        requery: Option<HashMap<DnsRequestHeader, QDnsReq>>,
484    ) -> CDnsResult<QDnsQueryResult>
485    {
486        let force_tcp = self.resolvers.option_flags.is_force_tcp() || requery.is_some();
487
488        let mut query_headers: HashMap<DnsRequestHeader, QDnsReq> = 
489            if let Some(requer) = requery
490            {
491                let pkts_ids = requer.iter().map(|q| q.0.get_id()).collect::<BTreeSet<u16>>();
492
493                // regenerate que ID and collect
494                requer
495                    .into_iter()
496                    .map(
497                        |(mut qrr, qdr)| 
498                        { 
499                            loop
500                            {
501                                qrr.regenerate_id(); 
502
503                                if pkts_ids.contains(&qrr.get_id()) == false
504                                {
505                                    break;
506                                }
507                            }
508                            (qrr, qdr)
509                        })
510                    .collect::<HashMap<DnsRequestHeader, QDnsReq>>()
511            }
512            else    
513            {
514                let mut pkts_ids: BTreeSet<u16> = BTreeSet::new();
515
516                // create que
517                self
518                    .ordered_req_list
519                    .iter()
520                    .map(
521                        |query| 
522                        {
523                            
524                            let mut drh_res = DnsRequestHeader::try_from(query);
525                            
526                            loop
527                            {
528                                if let Ok(ref mut drh) = drh_res
529                                {
530                                    if pkts_ids.contains(&drh.get_id()) == true
531                                    {
532                                        drh.regenerate_id();
533
534                                        continue;
535                                    }
536                                    else
537                                    {
538                                        pkts_ids.insert(drh.get_id());
539                                        break;
540                                    }
541                                }
542                                else
543                                {
544                                    break;
545                                }
546                            }
547
548                            drh_res.map(|dh| (dh, query.clone()))
549                        }
550                    )
551                    .collect::<CDnsResult<HashMap<DnsRequestHeader, QDnsReq>>>()?
552            };
553
554        // create socket and store
555        let mut tap = self.create_socket(force_tcp, resolver.clone())?;
556
557        tap.connect(CDdnsGlobals::get_tcp_conn_timeout()).await?;
558
559        // send everything
560        for qh in query_headers.iter()
561        {
562            let pkt = qh.0.to_bytes(tap.should_append_len())?;
563
564            tap.send(pkt.as_slice()).await?;
565        }
566
567        let mut resp: QDnsQueryResult = QDnsQueryResult::with_capacity(self.ordered_req_list.len());
568        let mut requery: HashMap<DnsRequestHeader, QDnsReq> = HashMap::new();
569        // poll socket
570        
571        loop
572        {
573            if query_headers.len() == 0
574            {
575                break;
576            }
577            
578            tap.poll_read().await?;
579
580            let ans = self.read_response(tap.as_mut()).await?;
581
582            let Some((query_header, qdnsreq)) = query_headers.remove_entry(&ans.req_header)
583                else
584                {
585                    internal_error!(CDnsErrorType::IoError, "can not find response with request: {}", ans.req_header);
586                };
587
588            ans.verify(&query_header)?;
589
590            // verified
591            let qdns_resp = QDnsQuery::from_response(tap.get_remote_addr(), ans, now);
592
593            if let Ok(ref qdns) = qdns_resp
594            {
595                if qdns.get_status().should_try_tcp() == true && force_tcp == false
596                {
597                    requery.insert(query_header, qdnsreq);
598                }
599                else
600                {
601                    resp.push(qdnsreq, qdns_resp);
602                }
603            }
604            else
605            {
606                resp.push(qdnsreq, qdns_resp);
607            }
608        }
609
610        if requery.is_empty() == false
611        {
612            let res = self.query_exec_pipelined(now, resolver, Some(requery)).await?;
613
614            resp.extend(res);
615        }
616        
617        return Ok(resp);   
618    }
619
620    /// Processes request/s for nameserver
621    #[async_recursion]
622    async 
623    fn query_exec_seq(
624        &self, 
625        now: Option<&Instant>, 
626        resolver: Arc<ResolveConfEntry>,
627        query: &QDnsReq,
628        requery: Option<DnsRequestHeader>,
629    ) -> CDnsResult<QDnsQuery>
630    {
631        let force_tcp = self.resolvers.option_flags.is_force_tcp() || requery.is_some();
632
633        // create socket and store
634        let mut tap = self.create_socket(force_tcp, resolver.clone())?;
635
636        let query_header = 
637            // form the list of reqests binded to taps
638            if let Some(mut requery) = requery
639            {
640                requery.regenerate_id();
641
642                requery
643            }
644            else
645            {
646                let drh_req = DnsRequestHeader::try_from(query)?;
647                
648                drh_req
649            };
650
651        let res = 
652            {
653                // open connection, with timeout because working in non parallel mode
654                tap.connect(CDdnsGlobals::get_tcp_conn_timeout()).await?;
655
656                // convert to byte packet (the error returned is global error i.e OOM)
657                let pkt = query_header.to_bytes(tap.should_append_len())?;
658
659                // send to DNS server, if error happens, then stop processing this server
660                tap.send(pkt.as_slice()).await?;
661
662                let ans = self.read_response(tap.as_mut()).await?;
663
664                ans.verify(&query_header)?;
665
666                // verified
667                let resp = QDnsQuery::from_response(tap.get_remote_addr(), ans, now)?;
668
669                Ok(resp)
670            };
671
672        if (res.is_ok() == true && res.as_ref().unwrap().status.should_try_tcp() == false) || 
673            (res.is_err() == true && force_tcp == true)
674        {
675            return res;
676        }
677        
678
679        return 
680            self.query_exec_seq(now, resolver.clone(), query, Some(query_header)).await;
681    }
682
683    /// Reads response from tap
684    async 
685    fn read_response(&self, socktap: &mut (dyn SocketTap)) -> CDnsResult<DnsRequestAnswer>
686    {
687        if socktap.is_tcp() == false
688        {
689            let mut rcvbuf = vec![0_u8; 1457];
690
691            // receive message
692            let n = socktap.recv(rcvbuf.as_mut_slice()).await?;
693
694            // parsing response to structure
695            return DnsRequestAnswer::parse(&rcvbuf); //DnsRequestAnswer::try_from(rcvbuf.as_slice(), n, socktap.is_tcp())?;
696        }
697        else
698        {
699            let mut pkg_pen: [u8; 2] = [0, 0];
700            let n = socktap.recv(&mut pkg_pen).await?;
701
702            if n == 0
703            {
704                internal_error!(CDnsErrorType::IoError, "tcp received zero len message!");
705            }
706            else if n != 2
707            {
708                internal_error!(CDnsErrorType::IoError, "tcp expected 2 bytes to be read!");
709            }
710
711            let ln = u16::from_be_bytes(pkg_pen);
712
713            let mut rcvbuf = vec![0_u8; ln as usize];
714
715            // receive message
716            let mut n = socktap.recv(rcvbuf.as_mut_slice()).await?;
717
718            if n == 0
719            {
720                internal_error!(CDnsErrorType::IoError, "tcp received zero len message!");
721            }
722            else if n == 1
723            {
724                n = socktap.recv(&mut rcvbuf[1..]).await?;
725
726                if n == 0
727                {
728                    internal_error!(CDnsErrorType::IoError, "tcp received zero len message again!");
729                }
730
731                n += 1;
732            }
733
734            return DnsRequestAnswer::parse(&rcvbuf); 
735        }
736    }
737}
738
739#[cfg(test)]
740mod tests
741{
742    use std::net::IpAddr;
743
744    use crate::{a_sync::query::QDns, common::{byte2hexchar, ip2pkt, RecordPTR, RecordReader}, QDnsQueryRec, QType, QuerySetup};
745
746    #[tokio::test]
747    async fn test_ip2pkt()
748    {
749        use tokio::time::Instant;
750        use std::net::{IpAddr, Ipv4Addr};
751        
752        let test = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8));
753
754        let now = Instant::now();
755
756        let res = ip2pkt(&test);
757
758        let elapsed = now.elapsed();
759        println!("Elapsed: {:.2?}", elapsed);
760
761        assert_eq!(res.is_ok(), true, "err: {}", res.err().unwrap());
762
763        let res = res.unwrap();
764        let ctrl = b"\x01\x38\x01\x38\x01\x38\x01\x38\x07\x69\x6e\x2d\x61\x64\x64\x72\x04\x61\x72\x70\x61\x00";
765
766        assert_eq!(res.as_slice(), ctrl);
767    }
768
769
770    #[tokio::test]
771    async fn test_byte2hexchar()
772    {
773        assert_eq!(byte2hexchar(1), 0x31);
774        assert_eq!(byte2hexchar(9), 0x39);
775        assert_eq!(byte2hexchar(10), 'a' as u8);
776        assert_eq!(byte2hexchar(15), 'f' as u8);
777    }
778
779
780    #[tokio::test]
781    async fn reverse_lookup_test()
782    {
783        use tokio::time::Instant;
784        
785        let ipp: IpAddr = "8.8.8.8".parse().unwrap();
786        //let test = QDnsName::from(&ipp);
787
788        let mut query_setup = QuerySetup::default();
789        query_setup.set_measure_time(true);
790
791        let now = Instant::now();
792
793        let mut dns_req = 
794            QDns::make_empty(None, query_setup).await.unwrap();
795
796        dns_req.add_request(QType::PTR, ipp);
797
798        let res = dns_req.query().await;
799
800        let elapsed = now.elapsed();
801        println!("Elapsed: {:.2?}", elapsed);
802
803        println!("{}", res);
804
805        assert_eq!(res.is_empty(), false);
806
807        let recs = res.collect_ok();
808        let rec = &recs[0];
809        //assert_eq!(rec.server.as_str(), "/etc/hosts");
810        assert_eq!(rec.status, QDnsQueryRec::Ok);
811
812        assert_eq!(rec.resp.len(), 1);
813        assert_eq!(rec.resp[0].rdata, RecordPTR::wrap(RecordPTR{ fqdn: "dns.google".to_string() }));
814    }
815
816    #[tokio::test]
817    async fn reverse_lookup_hosts_test()
818    {
819        use tokio::time::Instant;
820
821        let ipp: IpAddr = "127.0.0.1".parse().unwrap();
822        //let test = QDnsName::from(&ipp);
823        
824        let now = Instant::now();
825
826        let mut query_setup = QuerySetup::default();
827        query_setup.set_measure_time(true);
828
829        let mut dns_req = 
830            QDns::make_empty(None, query_setup).await.unwrap();
831
832        dns_req.add_request(QType::PTR, ipp);
833
834        let res = dns_req.query().await;
835        
836        let elapsed = now.elapsed();
837        println!("Elapsed: {:.2?}", elapsed);
838
839        println!("{}", res);
840
841        assert_eq!(res.is_empty(), false);
842
843        let recs = res.collect_ok();
844        let rec = &recs[0];
845
846        assert_eq!(rec.server.as_str(), "/etc/hosts");
847        assert_eq!(rec.status, QDnsQueryRec::Ok);
848
849        assert_eq!(rec.resp.len(), 1);
850        assert_eq!(rec.resp[0].rdata, RecordPTR::wrap(RecordPTR{ fqdn: "localhost".to_string() }));
851    }
852
853
854    #[tokio::test]
855    async fn reverse_lookup_a()
856    {
857        use tokio::time::Instant;
858
859       // let test = QDnsName::from("dns.google");
860
861        let mut query_setup = QuerySetup::default();
862        query_setup.set_measure_time(true);
863
864        
865        let res = QDns::make_a_aaaa_request(None, "dns.google", query_setup).await.unwrap();
866        
867        
868        let now = Instant::now();
869        let res = res.query().await;
870        
871
872        let elapsed = now.elapsed();
873        println!("Elapsed: {:.2?}", elapsed);
874
875        println!("{}", res);
876    }
877}