Skip to main content

hickory_resolver/recursor/
error.rs

1// Copyright 2015-2020 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! Error types for the crate
9
10#![deny(missing_docs)]
11
12use std::io;
13use std::sync::Arc;
14
15use thiserror::Error;
16use tracing::warn;
17
18use crate::{
19    net::{DnsError, ForwardNSData, NetError, NoRecords},
20    proto::{
21        ProtoError,
22        op::Query,
23        op::ResponseCode,
24        rr::{Name, Record, RecordType, rdata::SOA},
25    },
26};
27
28/// The error kind for errors that get returned in the crate
29#[derive(Debug, Error)]
30#[non_exhaustive]
31pub enum RecursorError {
32    /// Maximum record limit was exceeded
33    #[error("maximum record limit for {record_type} exceeded: {count} records")]
34    MaxRecordLimitExceeded {
35        /// number of records
36        count: usize,
37        /// The record type that triggered the error.
38        record_type: RecordType,
39    },
40
41    /// An error with an arbitrary message, referenced as &'static str
42    #[error("{0}")]
43    Message(&'static str),
44
45    /// An error with an arbitrary message, stored as String
46    #[error("{0}")]
47    Msg(String),
48
49    /// Upstream DNS authority returned an empty RRset
50    #[error("negative response")]
51    Negative(AuthorityData),
52
53    /// Upstream DNS authority returned a referral to another set of nameservers in the form of
54    /// additional NS records.
55    #[error("forward NS Response")]
56    ForwardNS(Arc<[ForwardNSData]>),
57
58    /// An error got returned from IO
59    #[error("io error: {0}")]
60    Io(#[from] io::Error),
61
62    /// An error got returned by the hickory-proto crate
63    #[error("net error: {0}")]
64    Net(NetError),
65
66    /// A request timed out
67    #[error("request timed out")]
68    Timeout,
69
70    /// Could not fetch all records because a recursion limit was exceeded
71    #[error("maximum recursion limit exceeded: {count} queries")]
72    RecursionLimitExceeded {
73        /// Number of queries that were made
74        count: usize,
75    },
76}
77
78impl RecursorError {
79    /// Test if the recursion depth has been exceeded, and return an error if it has.
80    pub fn recursion_exceeded(limit: u8, depth: u8, name: &Name) -> Result<(), Self> {
81        if depth < limit {
82            return Ok(());
83        }
84
85        warn!("recursion depth exceeded for {name}");
86        Err(Self::RecursionLimitExceeded {
87            count: depth as usize,
88        })
89    }
90
91    /// Returns the SOA record, if the error contains one
92    pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
93        match self {
94            Self::Net(net) => net.into_soa(),
95            Self::Negative(fwd) => fwd.soa,
96            _ => None,
97        }
98    }
99
100    /// Returns true if no records were returned
101    pub fn is_no_records_found(&self) -> bool {
102        match self {
103            Self::Net(net) => net.is_no_records_found(),
104            Self::Negative(fwd) => fwd.is_no_records_found(),
105            _ => false,
106        }
107    }
108
109    /// Returns true if the domain does not exist
110    pub fn is_nx_domain(&self) -> bool {
111        match self {
112            Self::Net(net) => net.is_nx_domain(),
113            Self::Negative(fwd) => fwd.is_nx_domain(),
114            _ => false,
115        }
116    }
117
118    /// Returns true if a query timed out
119    pub fn is_timeout(&self) -> bool {
120        match self {
121            Self::Net(net) => matches!(net, NetError::Timeout),
122            _ => false,
123        }
124    }
125}
126
127impl From<NetError> for RecursorError {
128    fn from(e: NetError) -> Self {
129        let NetError::Dns(DnsError::NoRecordsFound(no_records)) = e else {
130            return Self::Net(e);
131        };
132
133        if let Some(ns) = no_records.ns {
134            Self::ForwardNS(ns)
135        } else {
136            Self::Negative(AuthorityData::new(
137                no_records.query,
138                no_records.soa,
139                true,
140                matches!(no_records.response_code, ResponseCode::NXDomain),
141                no_records.authorities,
142            ))
143        }
144    }
145}
146
147impl From<RecursorError> for NetError {
148    fn from(e: RecursorError) -> Self {
149        match e {
150            RecursorError::Negative(fwd) => DnsError::NoRecordsFound(fwd.into()).into(),
151            _ => Self::from(e.to_string()),
152        }
153    }
154}
155
156impl From<ProtoError> for RecursorError {
157    fn from(e: ProtoError) -> Self {
158        NetError::from(e).into()
159    }
160}
161
162impl From<String> for RecursorError {
163    fn from(msg: String) -> Self {
164        Self::Msg(msg)
165    }
166}
167
168impl From<&'static str> for RecursorError {
169    fn from(msg: &'static str) -> Self {
170        Self::Message(msg)
171    }
172}
173
174impl Clone for RecursorError {
175    fn clone(&self) -> Self {
176        use self::RecursorError::*;
177        match self {
178            MaxRecordLimitExceeded { count, record_type } => MaxRecordLimitExceeded {
179                count: *count,
180                record_type: *record_type,
181            },
182            Message(msg) => Message(msg),
183            Msg(msg) => Msg(msg.clone()),
184            Negative(ns) => Negative(ns.clone()),
185            ForwardNS(ns) => ForwardNS(ns.clone()),
186            Io(io) => Io(io::Error::from(io.kind())),
187            Net(net) => Net(net.clone()),
188            Timeout => Self::Timeout,
189            RecursionLimitExceeded { count } => RecursionLimitExceeded { count: *count },
190        }
191    }
192}
193
194/// Data from the authority section of a response.
195#[derive(Clone, Debug)]
196pub struct AuthorityData {
197    /// Query
198    pub query: Box<Query>,
199    /// SOA
200    pub soa: Option<Box<Record<SOA>>>,
201    /// No records found?
202    no_records_found: bool,
203    /// IS nx domain?
204    nx_domain: bool,
205    /// Authority records
206    pub authorities: Option<Arc<[Record]>>,
207}
208
209impl AuthorityData {
210    /// Construct a new AuthorityData
211    pub fn new(
212        query: Box<Query>,
213        soa: Option<Box<Record<SOA>>>,
214        no_records_found: bool,
215        nx_domain: bool,
216        authorities: Option<Arc<[Record]>>,
217    ) -> Self {
218        Self {
219            query,
220            soa,
221            no_records_found,
222            nx_domain,
223            authorities,
224        }
225    }
226
227    /// are there records?
228    pub fn is_no_records_found(&self) -> bool {
229        self.no_records_found
230    }
231
232    /// is this nxdomain?
233    pub fn is_nx_domain(&self) -> bool {
234        self.nx_domain
235    }
236}
237
238impl From<AuthorityData> for NoRecords {
239    fn from(data: AuthorityData) -> Self {
240        let response_code = match data.is_nx_domain() {
241            true => ResponseCode::NXDomain,
242            false => ResponseCode::NoError,
243        };
244
245        let mut new = Self::new(data.query, response_code);
246        new.soa = data.soa;
247        new.authorities = data.authorities;
248        new
249    }
250}