dns_resolver/util/
types.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::str::FromStr;
4
5use dns_types::protocol::types::*;
6
7pub const CANNOT_PARSE_PROTOCOL_MODE: &str =
8    "expected one of 'only-v4', 'prefer-v4', 'prefer-v6', 'only'v6'";
9
10/// How the recursive resolver should choose which IP address to try for
11/// upstream nameservers.
12#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
13pub enum ProtocolMode {
14    /// Only use IPv4 (e.g. this is an IPv4-only network), rejecting a
15    /// nameserver if it is only available over IPv6.
16    OnlyV4,
17    /// If a nameserver is only available over IPv6, use that; but if it is
18    /// available over both IPv4 and IPv6 (or only IPv4), use the IPv4 address.
19    PreferV4,
20    /// If a nameserver is only available over IPv4, use that; but if it is
21    /// available over both IPv4 and IPv6 (or only IPv6), use the IPv6 address.
22    PreferV6,
23    /// Only use IPv6 (e.g. this is an IPv6-only network), rejecting a
24    /// nameserver if it is only available over IPv4.
25    OnlyV6,
26}
27
28impl fmt::Display for ProtocolMode {
29    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30        match self {
31            ProtocolMode::OnlyV4 => write!(f, "only-v4"),
32            ProtocolMode::PreferV4 => write!(f, "prefer-v4"),
33            ProtocolMode::PreferV6 => write!(f, "prefer-v6"),
34            ProtocolMode::OnlyV6 => write!(f, "only-v6"),
35        }
36    }
37}
38
39impl FromStr for ProtocolMode {
40    type Err = &'static str;
41
42    fn from_str(s: &str) -> Result<Self, Self::Err> {
43        match s {
44            "only-v4" => Ok(ProtocolMode::OnlyV4),
45            "prefer-v4" => Ok(ProtocolMode::PreferV4),
46            "prefer-v6" => Ok(ProtocolMode::PreferV6),
47            "only-v6" => Ok(ProtocolMode::OnlyV6),
48            _ => Err(CANNOT_PARSE_PROTOCOL_MODE),
49        }
50    }
51}
52
53/// The result of a name resolution attempt.
54///
55/// If this is a `CNAME`, it should be added to the answer section of
56/// the response message, and resolution repeated for the CNAME.  This
57/// may build up a chain of `CNAME`s for some names.
58///
59#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
60pub enum ResolvedRecord {
61    Authoritative {
62        rrs: Vec<ResourceRecord>,
63        soa_rr: ResourceRecord,
64    },
65    AuthoritativeNameError {
66        soa_rr: ResourceRecord,
67    },
68    NonAuthoritative {
69        rrs: Vec<ResourceRecord>,
70        soa_rr: Option<ResourceRecord>,
71    },
72}
73
74impl ResolvedRecord {
75    pub fn rrs(self) -> Vec<ResourceRecord> {
76        match self {
77            ResolvedRecord::Authoritative { rrs, .. } => rrs,
78            ResolvedRecord::AuthoritativeNameError { .. } => Vec::new(),
79            ResolvedRecord::NonAuthoritative { rrs, .. } => rrs,
80        }
81    }
82
83    pub fn soa_rr(&self) -> Option<&ResourceRecord> {
84        match self {
85            ResolvedRecord::Authoritative { soa_rr, .. } => Some(soa_rr),
86            ResolvedRecord::AuthoritativeNameError { soa_rr } => Some(soa_rr),
87            ResolvedRecord::NonAuthoritative { soa_rr, .. } => soa_rr.into(),
88        }
89    }
90}
91
92/// An error that can occur when trying to resolve a domain.
93#[derive(Debug, Clone, Eq, PartialEq, Hash)]
94pub enum ResolutionError {
95    /// Recursive or forwarding resolution timed out and was aborted.
96    Timeout,
97    /// Hit the recursion limit while following CNAMEs.
98    RecursionLimit,
99    /// Tried to resolve a question while resolving the same question.
100    DuplicateQuestion { question: Question },
101    /// Was unable to resolve a necessary record.
102    DeadEnd { question: Question },
103    /// Configuration error: a local zone delegates without defining NS records.
104    LocalDelegationMissingNS {
105        apex: DomainName,
106        domain: DomainName,
107    },
108    /// Internal error: got a result from the cache which doesn't match the
109    /// querytype.
110    CacheTypeMismatch {
111        query: QueryType,
112        result: RecordType,
113    },
114}
115
116impl std::fmt::Display for ResolutionError {
117    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
118        match self {
119            ResolutionError::Timeout => write!(f, "timed out"),
120            ResolutionError::RecursionLimit => write!(f, "CNAME chain too long"),
121            ResolutionError::DuplicateQuestion{question} => write!(f, "loop when answering '{} {} {}'", question.name, question.qclass, question.qtype),
122            ResolutionError::DeadEnd{question} => write!(f, "unable to answer '{} {} {}'", question.name, question.qclass, question.qtype),
123            ResolutionError::LocalDelegationMissingNS{apex,domain} => write!(f, "configuration error: got delegation for domain '{domain}' from zone '{apex}', but there are no NS records"),
124            ResolutionError::CacheTypeMismatch{query,result} => write!(f, "internal error (bug): tried to fetch '{query}' from cache but got '{result}' instead"),
125        }
126    }
127}
128
129impl std::error::Error for ResolutionError {
130    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
131        None
132    }
133}
134
135/// A set of nameservers for a domain
136#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
137pub struct Nameservers {
138    /// Guaranteed to be non-empty.
139    ///
140    /// TODO: find a non-empty-vec type
141    pub hostnames: Vec<DomainName>,
142    pub name: DomainName,
143}
144
145impl Nameservers {
146    pub fn match_count(&self) -> usize {
147        self.name.labels.len()
148    }
149}
150
151/// Merge two sets of RRs, where records from the second set are
152/// included if and only if there are no records of matching (name,
153/// type) in the first set.
154///
155/// For example, if the first set is:
156///
157/// ```text
158/// example.com. 300 IN A 1.1.1.1
159/// example.com. 300 IN A 2.2.2.2
160/// ```
161///
162/// And the second set is:
163///
164/// ```text
165/// example.com. 300 IN A 3.3.3.3
166/// example.net. 300 IN A 3.3.3.3
167/// example.com. 300 IN MX mail.example.com.
168/// ```
169///
170/// Then the output will be:
171///
172/// ```text
173/// example.com. 300 IN A 1.1.1.1
174/// example.com. 300 IN A 2.2.2.2
175/// example.net. 300 IN A 3.3.3.3
176/// example.com. 300 IN MX mail.example.com.
177/// ```
178///
179/// Where the A records for `example.com.` have been dropped.  The
180/// first set acts as an override of the second.
181pub fn prioritising_merge(priority: &mut Vec<ResourceRecord>, new: Vec<ResourceRecord>) {
182    let mut seen = HashSet::new();
183
184    for rr in &*priority {
185        seen.insert((rr.name.clone(), rr.rtype_with_data.rtype()));
186    }
187
188    for rr in new {
189        if !seen.contains(&(rr.name.clone(), rr.rtype_with_data.rtype())) {
190            priority.push(rr);
191        }
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use std::net::Ipv4Addr;
198
199    use dns_types::protocol::types::test_util::*;
200
201    use super::*;
202
203    #[test]
204    fn prioritised_merge_prioritises_by_name_and_type() {
205        let mut priority = vec![
206            a_record("www.example.com.", Ipv4Addr::new(1, 1, 1, 1)),
207            a_record("www.example.com.", Ipv4Addr::new(2, 2, 2, 2)),
208            cname_record("www.example.com.", "target.example.com."),
209        ];
210        let new = vec![
211            a_record("www.example.com.", Ipv4Addr::new(3, 3, 3, 3)),
212            a_record("www.example.net.", Ipv4Addr::new(4, 4, 4, 4)),
213            cname_record("www.example.com.", "other-target.example.com."),
214            ns_record("www.example.com.", "ns1.example.com."),
215            ns_record("www.example.com.", "ns2.example.com."),
216        ];
217
218        prioritising_merge(&mut priority, new);
219        priority.sort();
220
221        let mut expected = vec![
222            a_record("www.example.com.", Ipv4Addr::new(1, 1, 1, 1)),
223            a_record("www.example.com.", Ipv4Addr::new(2, 2, 2, 2)),
224            cname_record("www.example.com.", "target.example.com."),
225            a_record("www.example.net.", Ipv4Addr::new(4, 4, 4, 4)),
226            ns_record("www.example.com.", "ns1.example.com."),
227            ns_record("www.example.com.", "ns2.example.com."),
228        ];
229        expected.sort();
230
231        assert_eq!(expected, priority);
232    }
233}