cdns_rs/
query.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::collections::HashMap;
17use std::convert::{TryFrom, TryInto};
18use std::net::SocketAddr;
19use std::fmt;
20use std::ops::Index;
21use std::time::Duration;
22use std::time::Instant;
23
24
25use crate::error::*;
26use crate::internal_error;
27use crate::query_private::QDnsReq;
28
29use super::common::*;
30
31
32/// A query override instance.
33#[derive(Debug, Clone)]
34pub struct QuerySetup
35{
36    /// Sets the realtime counter to measure delay
37    pub(crate) measure_time: bool, 
38
39    /// Forces to ignore hosts file
40    pub(crate) ign_hosts: bool, 
41
42    /// Overrides the timeout duration for reply awaiting.
43    pub(crate) timeout: Option<u32>,
44}
45
46impl Default for QuerySetup
47{
48    fn default() -> Self 
49    {
50        return Self { measure_time: false, ign_hosts: false, timeout: None };
51    }
52}
53
54impl QuerySetup
55{
56    /// Turns on/off the time measurment whcih measure how many time went
57    /// since query started for each response.
58    pub 
59    fn set_measure_time(&mut self, flag: bool) -> &mut Self
60    {
61        self.measure_time = flag;
62        
63        return self;
64    }
65
66    /// Turns on/off the option which when set is force to ignore lookup in
67    /// /etc/hosts
68    pub 
69    fn set_ign_hosts(&mut self, flag: bool) -> &mut Self
70    {
71        self.ign_hosts = flag;
72
73        return self;
74    }
75
76    /// Overrides the timeout. Can not be 0.
77    pub 
78    fn set_override_timeout(&mut self, timeout: u32) -> &mut Self
79    {
80        if timeout == 0
81        {
82            return self;
83        }
84
85        self.timeout = Some(timeout);
86
87        return self;
88    }
89
90    /// Resets the override timeout.
91    pub 
92    fn reset_override_timeout(&mut self) -> &mut Self
93    {
94        self.timeout = None;
95
96        return self;
97    }
98
99}
100
101/// A [StatusBits] decoded response status.
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub enum QDnsQueryRec
104{
105    /// Response is Ok and
106    Ok,
107    /// Server side error
108    ServFail,
109    /// Query does not exist, but meaningfull when `aa` is true
110    NxDomain,
111    /// A name server refuses to perform the specified operation
112    Refused,
113    /// A name server does not support the requested kind of query
114    NotImpl,
115    /// A message was truncated
116    Truncated, 
117    /// A fromat error
118    FormError,
119}
120
121impl QDnsQueryRec
122{
123    /// Returns true if [QueryMode] is set to continue query other nameservers.  
124    /// Returns false if there is no need to query other nameservers i.e 
125    ///  [QueryMode::DefaultMode] or the response was from authotherative server.
126    pub(crate)
127    fn try_next_nameserver(&self, aa: bool) -> bool
128    {
129        match *self
130        {
131            Self::NxDomain =>
132            {
133                if aa == true
134                {
135                    // the response is from authotherative server
136                    return false;
137                }
138
139                return true;
140            },
141            Self::Truncated =>
142            {
143                return true;
144            },
145            Self::Ok | Self::Refused | Self::ServFail | Self::NotImpl | Self::FormError =>
146            {
147                return true;
148            }
149        }
150    }
151
152    pub(crate)
153    fn should_try_tcp(&self) -> bool
154    {
155        return *self == Self::Truncated || *self == Self::ServFail || *self == Self::NxDomain;
156    }
157}
158
159impl TryFrom<StatusBits> for QDnsQueryRec
160{
161    type Error = CDnsError;
162
163    fn try_from(value: StatusBits) -> Result<Self, Self::Error> 
164    {
165        if value.contains(StatusBits::TRUN_CATION) == true
166        {
167            return Ok(QDnsQueryRec::Truncated);
168        }
169        else if value.contains(StatusBits::RESP_NOERROR) == true
170        {
171            /*let resps: Vec<QueryRec> = 
172                ans.response.into_iter().map(|a| a.into()).collect();
173            */
174            return Ok(QDnsQueryRec::Ok);
175        }
176        else if value.contains(StatusBits::RESP_FORMERR) == true
177        {
178            return Ok(QDnsQueryRec::FormError);
179        }
180        else if value.contains(StatusBits::RESP_NOT_IMPL) == true
181        {
182            return Ok(QDnsQueryRec::NotImpl);
183        }
184        else if value.contains(StatusBits::RESP_NXDOMAIN) == true
185        {
186            return Ok(QDnsQueryRec::NxDomain);
187        }
188        else if value.contains(StatusBits::RESP_REFUSED) == true
189        {
190            return Ok(QDnsQueryRec::Refused);
191        }
192        else if value.contains(StatusBits::RESP_SERVFAIL) == true
193        {
194            return Ok(QDnsQueryRec::ServFail);
195        }
196        else
197        {
198            internal_error!(CDnsErrorType::DnsResponse, "response status bits unknwon result: '{}'", value.bits());
199        };
200    }
201}
202
203
204impl fmt::Display for QDnsQueryRec
205{
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
207    {
208        match *self
209        {
210            Self::Ok => 
211                writeln!(f, "OK"),
212            Self::ServFail =>
213                writeln!(f, "SERVFAIL"),
214            Self::NxDomain =>
215                writeln!(f, "NXDOMAIN"),
216            Self::Refused =>  
217                writeln!(f, "REFUSED"),
218            Self::NotImpl => 
219                writeln!(f, "NOT IMPLEMENTED"),
220            Self::Truncated =>
221                writeln!(f, "TRUNCATED"),
222            Self::FormError =>
223                writeln!(f, "FORMAT ERROR"),
224        }
225    }
226}
227
228/// A results container. Each result is paired with the request.
229#[derive(Debug, Default)]
230pub struct QDnsQueryResult
231{
232    queries: HashMap<QDnsReq, CDnsResult<QDnsQuery>>,
233}
234
235impl IntoIterator for QDnsQueryResult 
236{
237    type Item = (QDnsReq, CDnsResult<QDnsQuery>);
238    type IntoIter = std::collections::hash_map::IntoIter<QDnsReq, CDnsResult<QDnsQuery>>;
239
240    #[inline]
241    fn into_iter(self) -> Self::IntoIter 
242    {
243        return self.queries.into_iter();
244    }
245}
246
247
248impl Index<QDnsReq> for QDnsQueryResult
249{
250    type Output = CDnsResult<QDnsQuery>;
251
252    fn index(&self, index: QDnsReq) -> &Self::Output 
253    {
254        return &self.queries.get(&index).unwrap();
255    }
256}
257
258impl QDnsQueryResult
259{
260    pub(crate) 
261    fn with_capacity(cap: usize) -> Self
262    {
263        return Self{ queries: HashMap::with_capacity(cap) };
264    }
265
266    pub(crate) 
267    fn push(&mut self, req: QDnsReq, resp: CDnsResult<QDnsQuery>)
268    {
269        self.queries.insert(req, resp);
270    }
271
272    pub 
273    fn is_empty(&self) -> bool
274    {
275        return self.queries.is_empty();
276    }
277
278    pub 
279    fn contains_dnsreq(&self, req: &QDnsReq) -> bool
280    {
281        return self.queries.contains_key(req);
282    }
283
284    pub 
285    fn extend(&mut self, other: Self)
286    {
287        self.queries.extend(other.queries);
288    }
289
290    pub 
291    fn list_results(&self) -> std::collections::hash_map::Iter<'_, QDnsReq, Result<QDnsQuery, CDnsError>>
292    {
293        return self.queries.iter();
294    }
295
296    pub 
297    fn get_result(self) -> CDnsResult<Vec<QDnsQuery>>
298    {
299        let ok = self.collect_ok();
300
301        if ok.is_empty() == true
302        {
303            internal_error!(CDnsErrorType::DnsNotAvailable, "network error");
304        }
305
306        return Ok(ok);
307    }
308
309    pub 
310    fn get_ok_or_error(self) ->CDnsResult<Vec<QDnsQuery>>
311    {
312        return
313            self
314                .queries
315                .into_iter()
316                .map(|e| e.1)
317                .collect::<CDnsResult<Vec<QDnsQuery>>>();
318    }
319
320    pub 
321    fn collect_ok(self) -> Vec<QDnsQuery>
322    {
323        return 
324            self
325                .queries
326                .into_iter()
327                .filter(
328                    |(_k, v)| 
329                    v.is_ok()
330                )
331                .map(|(_, v)| v.unwrap())
332                .collect();
333    }
334
335    pub 
336    fn collect_ok_with_answers(self) -> Vec<QDnsQuery>
337    {
338        return 
339            self
340                .queries
341                .into_iter()
342                .filter(
343                    |(_k, v)| 
344                    v.is_ok() && v.as_ref().unwrap().resp.is_empty() == false
345                )
346                .map(|(_, v)| v.unwrap())
347                .collect();
348    }
349
350    // 0 - ok, 1 - err
351    pub 
352    fn collect_split(self) -> (Vec<(QDnsReq, Result<QDnsQuery, CDnsError>)>, Vec<(QDnsReq, Result<QDnsQuery, CDnsError>)>)
353
354    {
355        return 
356            self
357                .queries
358                .into_iter()
359                .partition(|(_, v)|
360                    v.is_ok()
361                );
362    }
363
364}
365
366impl fmt::Display for QDnsQueryResult
367{
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
369    {
370        if self.is_empty() == false
371        {
372            for (req, qr) in self.list_results()
373            {
374                match qr
375                {
376                    Ok(r) => 
377                        write!(f, "{}", r)?,
378                    Err(e) =>
379                        write!(f, "request: {}, error: {}", req, e)?
380                }
381                
382            }
383        }
384        else
385        {
386            write!(f, "No DNS server available")?;
387        }
388
389        return Ok(());
390    }
391}
392
393/// A structure which describes the query properties and contains the
394/// results.
395#[derive(Clone, Debug)]
396pub struct QDnsQuery
397{
398    /// A realtime time elapsed for query
399    pub elapsed: Option<Duration>,
400    /// Server which performed the response and port number
401    pub server: String,
402    /// Flags
403    pub aa: bool,
404    /// Authoratives section
405    pub authoratives: Vec<DnsResponsePayload>,
406    /// Responses
407    pub resp: Vec<DnsResponsePayload>,
408    /// Status
409    pub status: QDnsQueryRec,
410}
411
412impl fmt::Display for QDnsQuery
413{
414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
415    {
416        write!(f, "Source: {} ", self.server)?;
417        if let Some(ref el) = self.elapsed
418        {
419            write!(f, "{:.2?} ", el)?;
420        }
421
422        if self.aa == true
423        {
424            writeln!(f, "Authoritative answer")?;
425        }
426        else
427        {
428            writeln!(f, "Non-Authoritative answer")?;
429        }
430
431
432        writeln!(f, "Authoritatives: {}", self.authoratives.len())?;
433
434        if self.authoratives.len() > 0
435        {
436            for a in self.authoratives.iter()
437            {
438                writeln!(f, "{}", a)?;
439            }
440
441            writeln!(f, "")?;
442        }
443
444        writeln!(f, "Status: {}", self.status)?;
445
446        writeln!(f, "Answers: {}", self.resp.len())?;
447
448        if self.resp.len() > 0
449        {
450            for r in self.resp.iter()
451            {
452                writeln!(f, "{}", r)?;
453            }
454
455            writeln!(f, "")?;
456        }
457
458        return Ok(());
459    }
460}
461
462impl IntoIterator for QDnsQuery
463{
464    type Item = DnsResponsePayload;
465    type IntoIter = std::vec::IntoIter<Self::Item>;
466
467    fn into_iter(self) -> Self::IntoIter 
468    {
469        self.resp.into_iter()
470    }
471}
472 
473impl QDnsQuery
474{
475    /// Returns true if the response is OK
476    pub 
477    fn is_ok(&self) -> bool
478    {
479        return self.status == QDnsQueryRec::Ok;
480    }
481
482    pub 
483    fn is_authorative(&self) -> bool
484    {
485        return self.aa;
486    }
487
488    pub 
489    fn get_elapsed_time(&self) -> Option<&Duration>
490    {
491        return self.elapsed.as_ref();
492    }
493
494    pub 
495    fn get_server(&self) -> &String
496    {
497        return &self.server;
498    }
499
500    /// Returns the authorative server data if any
501    pub 
502    fn get_authoratives(&self) -> &[DnsResponsePayload]
503    {
504        return self.authoratives.as_slice();
505    }
506
507    /// Returns the responses if any
508    pub 
509    fn get_responses(&self) -> &[DnsResponsePayload]
510    {
511        return self.resp.as_slice();
512    }
513
514    /// Moves the responses from structure
515    pub 
516    fn move_responses(self) -> Vec<DnsResponsePayload>
517    {
518        return self.resp;
519    }
520
521    pub 
522    fn get_status(&self) -> QDnsQueryRec
523    {
524        return self.status;
525    }
526
527    pub 
528    fn should_check_next_ns(&self) -> bool
529    {
530        return self.status.try_next_nameserver(self.aa);
531    }
532}
533
534impl QDnsQuery
535{
536    /// Constructs instance like it is from 'local' source but not from DNS server.
537    pub 
538    fn from_local(req_pl: Vec<DnsResponsePayload>, now: Option<&Instant>) -> QDnsQuery
539    {
540        let elapsed = now.map(|n| n.elapsed());
541
542        return 
543            Self
544            {
545                elapsed: elapsed,
546                server: HOST_CFG_PATH.to_string(),
547                aa: true,
548                authoratives: Vec::new(),
549                status: QDnsQueryRec::Ok,
550                resp: req_pl
551            };
552    }
553
554    /// Constructs an instance from the remote response.
555    pub 
556    fn from_response(server: &SocketAddr, ans: DnsRequestAnswer, now: Option<&Instant>) -> CDnsResult<Self>
557    {
558        return Ok(
559            Self
560            {
561                elapsed: now.map_or(None, |n| Some(n.elapsed())),
562                server: server.to_string(),
563                aa: ans.req_header.header.status.contains(StatusBits::AUTH_ANSWER),
564                authoratives: ans.authoratives,
565                status: ans.req_header.header.status.try_into()?,
566                resp: ans.response,
567            }
568        );
569    }
570}