hickory_proto/rr/rdata/soa.rs
1// Copyright 2015-2023 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//! start of authority record defining ownership and defaults for the zone
9
10use std::fmt;
11
12#[cfg(feature = "serde-config")]
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 error::ProtoResult,
17 rr::{domain::Name, RData, RecordData, RecordType},
18 serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder},
19};
20
21/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
22///
23/// ```text
24/// 3.3.13. SOA RDATA format
25///
26/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
27/// / MNAME /
28/// / /
29/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
30/// / RNAME /
31/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
32/// | SERIAL |
33/// | |
34/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
35/// | REFRESH |
36/// | |
37/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
38/// | RETRY |
39/// | |
40/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
41/// | EXPIRE |
42/// | |
43/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
44/// | MINIMUM |
45/// | |
46/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
47///
48/// where:
49///
50/// SOA records cause no additional section processing.
51///
52/// All times are in units of seconds.
53///
54/// Most of these fields are pertinent only for name server maintenance
55/// operations. However, MINIMUM is used in all query operations that
56/// retrieve RRs from a zone. Whenever a RR is sent in a response to a
57/// query, the TTL field is set to the maximum of the TTL field from the RR
58/// and the MINIMUM field in the appropriate SOA. Thus MINIMUM is a lower
59/// bound on the TTL field for all RRs in a zone. Note that this use of
60/// MINIMUM should occur when the RRs are copied into the response and not
61/// when the zone is loaded from a Zone File or via a zone transfer. The
62/// reason for this provision is to allow future dynamic update facilities to
63/// change the SOA RR with known semantics.
64/// ```
65#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
66#[derive(Debug, PartialEq, Eq, Hash, Clone)]
67pub struct SOA {
68 mname: Name,
69 rname: Name,
70 serial: u32,
71 refresh: i32,
72 retry: i32,
73 expire: i32,
74 minimum: u32,
75}
76
77impl SOA {
78 /// Creates a new SOA record data.
79 ///
80 /// # Arguments
81 ///
82 /// * `mname` - the name of the primary or authority for this zone.
83 /// * `rname` - the name of the responsible party for this zone, e.g. an email address.
84 /// * `serial` - the serial number of the zone, used for caching purposes.
85 /// * `refresh` - the amount of time to wait before a zone is resynched.
86 /// * `retry` - the minimum period to wait if there is a failure during refresh.
87 /// * `expire` - the time until this primary is no longer authoritative for the zone.
88 /// * `minimum` - no zone records should have time-to-live values less than this minimum.
89 ///
90 /// # Return value
91 ///
92 /// The newly created SOA record data.
93 pub fn new(
94 mname: Name,
95 rname: Name,
96 serial: u32,
97 refresh: i32,
98 retry: i32,
99 expire: i32,
100 minimum: u32,
101 ) -> Self {
102 Self {
103 mname,
104 rname,
105 serial,
106 refresh,
107 retry,
108 expire,
109 minimum,
110 }
111 }
112
113 /// Increments the serial number by one
114 pub fn increment_serial(&mut self) {
115 self.serial += 1; // TODO: what to do on overflow?
116 }
117
118 /// ```text
119 /// MNAME The <domain-name> of the name server that was the
120 /// original or primary source of data for this zone.
121 /// ```
122 ///
123 /// # Return value
124 ///
125 /// The `domain-name` of the name server that was the original or primary source of data for
126 /// this zone, i.e. the Primary Name Server.
127 pub fn mname(&self) -> &Name {
128 &self.mname
129 }
130
131 /// ```text
132 /// RNAME A <domain-name> which specifies the mailbox of the
133 /// person responsible for this zone.
134 /// ```
135 ///
136 /// # Return value
137 ///
138 /// A `domain-name` which specifies the mailbox of the person responsible for this zone, i.e.
139 /// the responsible name.
140 pub fn rname(&self) -> &Name {
141 &self.rname
142 }
143
144 /// ```text
145 /// SERIAL The unsigned 32 bit version number of the original copy
146 /// of the zone. Zone transfers preserve this value. This
147 /// value wraps and should be compared using sequence space
148 /// arithmetic.
149 /// ```
150 ///
151 /// # Return value
152 ///
153 /// The unsigned 32 bit version number of the original copy of the zone. Zone transfers
154 /// preserve this value. This value wraps and should be compared using sequence space arithmetic.
155 pub fn serial(&self) -> u32 {
156 self.serial
157 }
158
159 /// ```text
160 /// REFRESH A 32 bit time interval before the zone should be
161 /// refreshed.
162 /// ```
163 ///
164 /// # Return value
165 ///
166 /// A 32 bit time interval before the zone should be refreshed, in seconds.
167 pub fn refresh(&self) -> i32 {
168 self.refresh
169 }
170
171 /// ```text
172 /// RETRY A 32 bit time interval that should elapse before a
173 /// failed refresh should be retried.
174 /// ```
175 ///
176 /// # Return value
177 ///
178 /// A 32 bit time interval that should elapse before a failed refresh should be retried,
179 /// in seconds.
180 pub fn retry(&self) -> i32 {
181 self.retry
182 }
183
184 /// ```text
185 /// EXPIRE A 32 bit time value that specifies the upper limit on
186 /// the time interval that can elapse before the zone is no
187 /// longer authoritative.
188 /// ```
189 ///
190 /// # Return value
191 ///
192 /// A 32 bit time value that specifies the upper limit on the time interval that can elapse
193 /// before the zone is no longer authoritative, in seconds
194 pub fn expire(&self) -> i32 {
195 self.expire
196 }
197
198 /// ```text
199 /// MINIMUM The unsigned 32 bit minimum TTL field that should be
200 /// exported with any RR from this zone.
201 /// ```
202 ///
203 /// # Return value
204 ///
205 /// The unsigned 32 bit minimum TTL field that should be exported with any RR from this zone.
206 pub fn minimum(&self) -> u32 {
207 self.minimum
208 }
209}
210
211impl BinEncodable for SOA {
212 /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
213 ///
214 /// This is accurate for all currently known name records.
215 ///
216 /// ```text
217 /// 6.2. Canonical RR Form
218 ///
219 /// For the purposes of DNS security, the canonical form of an RR is the
220 /// wire format of the RR where:
221 ///
222 /// ...
223 ///
224 /// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
225 /// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
226 /// SRV, DNAME, A6, RRSIG, or (rfc6840 removes NSEC), all uppercase
227 /// US-ASCII letters in the DNS names contained within the RDATA are replaced
228 /// by the corresponding lowercase US-ASCII letters;
229 /// ```
230 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
231 let is_canonical_names = encoder.is_canonical_names();
232
233 // to_lowercase for rfc4034 and rfc6840
234 self.mname
235 .emit_with_lowercase(encoder, is_canonical_names)?;
236 self.rname
237 .emit_with_lowercase(encoder, is_canonical_names)?;
238 encoder.emit_u32(self.serial)?;
239 encoder.emit_i32(self.refresh)?;
240 encoder.emit_i32(self.retry)?;
241 encoder.emit_i32(self.expire)?;
242 encoder.emit_u32(self.minimum)?;
243 Ok(())
244 }
245}
246
247impl<'r> BinDecodable<'r> for SOA {
248 fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult<Self> {
249 Ok(Self {
250 mname: Name::read(decoder)?,
251 rname: Name::read(decoder)?,
252 serial: decoder.read_u32()?.unverified(/*any u32 is valid*/),
253 refresh: decoder.read_i32()?.unverified(/*any i32 is valid*/),
254 retry: decoder.read_i32()?.unverified(/*any i32 is valid*/),
255 expire: decoder.read_i32()?.unverified(/*any i32 is valid*/),
256 minimum: decoder.read_u32()?.unverified(/*any u32 is valid*/),
257 })
258 }
259}
260
261impl RecordData for SOA {
262 fn try_from_rdata(data: RData) -> Result<Self, RData> {
263 match data {
264 RData::SOA(soa) => Ok(soa),
265 _ => Err(data),
266 }
267 }
268
269 fn try_borrow(data: &RData) -> Option<&Self> {
270 match data {
271 RData::SOA(soa) => Some(soa),
272 _ => None,
273 }
274 }
275
276 fn record_type(&self) -> RecordType {
277 RecordType::SOA
278 }
279
280 fn into_rdata(self) -> RData {
281 RData::SOA(self)
282 }
283}
284
285/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
286///
287/// ```text
288/// SOA (Start Of Authority)
289///
290/// <name> [<ttl>] [<class>] SOA <origin> <person> (
291/// <serial>
292/// <refresh>
293/// <retry>
294/// <expire>
295/// <minimum> )
296///
297/// The Start Of Authority record designates the start of a zone. The
298/// zone ends at the next SOA record.
299///
300/// <name> is the name of the zone.
301///
302/// <origin> is the name of the host on which the master zone file
303/// resides.
304///
305/// <person> is a mailbox for the person responsible for the zone. It is
306/// formatted like a mailing address but the at-sign that normally
307/// separates the user from the host name is replaced with a dot.
308///
309/// <serial> is the version number of the zone file. It should be
310/// incremented anytime a change is made to data in the zone.
311///
312/// <refresh> is how long, in seconds, a secondary name server is to
313/// check with the primary name server to see if an update is needed. A
314/// good value here would be one hour (3600).
315///
316/// <retry> is how long, in seconds, a secondary name server is to retry
317/// after a failure to check for a refresh. A good value here would be
318/// 10 minutes (600).
319///
320/// <expire> is the upper limit, in seconds, that a secondary name server
321/// is to use the data before it expires for lack of getting a refresh.
322/// You want this to be rather large, and a nice value is 3600000, about
323/// 42 days.
324///
325/// <minimum> is the minimum number of seconds to be used for TTL values
326/// in RRs. A minimum of at least a day is a good value here (86400).
327///
328/// There should only be one SOA record per zone. A sample SOA record
329/// would look something like:
330///
331/// @ IN SOA SRI-NIC.ARPA. HOSTMASTER.SRI-NIC.ARPA. (
332/// 45 ;serial
333/// 3600 ;refresh
334/// 600 ;retry
335/// 3600000 ;expire
336/// 86400 ) ;minimum
337/// ```
338impl fmt::Display for SOA {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
340 write!(
341 f,
342 "{mname} {rname} {serial} {refresh} {retry} {expire} {min}",
343 mname = self.mname,
344 rname = self.rname,
345 serial = self.serial,
346 refresh = self.refresh,
347 retry = self.retry,
348 expire = self.expire,
349 min = self.minimum
350 )
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 #![allow(clippy::dbg_macro, clippy::print_stdout)]
357
358 use crate::{rr::RecordDataDecodable, serialize::binary::Restrict};
359
360 use super::*;
361
362 #[test]
363 fn test() {
364 use std::str::FromStr;
365
366 let rdata = SOA::new(
367 Name::from_str("m.example.com").unwrap(),
368 Name::from_str("r.example.com").unwrap(),
369 1,
370 2,
371 3,
372 4,
373 5,
374 );
375
376 let mut bytes = Vec::new();
377 let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
378 assert!(rdata.emit(&mut encoder).is_ok());
379 let bytes = encoder.into_bytes();
380 let len = bytes.len() as u16;
381
382 println!("bytes: {bytes:?}");
383
384 let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
385 let read_rdata = SOA::read_data(&mut decoder, Restrict::new(len)).expect("Decoding error");
386 assert_eq!(rdata, read_rdata);
387 }
388}