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#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
13pub enum ProtocolMode {
14 OnlyV4,
17 PreferV4,
20 PreferV6,
23 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#[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#[derive(Debug, Clone, Eq, PartialEq, Hash)]
94pub enum ResolutionError {
95 Timeout,
97 RecursionLimit,
99 DuplicateQuestion { question: Question },
101 DeadEnd { question: Question },
103 LocalDelegationMissingNS {
105 apex: DomainName,
106 domain: DomainName,
107 },
108 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#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
137pub struct Nameservers {
138 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
151pub 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}