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}