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