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