hickory_proto/op/
update_message.rs

1// Copyright 2015-2017 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//! Update related operations for Messages
9
10use core::fmt::Debug;
11
12#[cfg(any(feature = "std", feature = "no-std-rand"))]
13use crate::{
14    op::{Edns, MessageType, OpCode},
15    random,
16    rr::{DNSClass, Name, RData, RecordSet, RecordType, rdata::SOA},
17};
18use crate::{
19    op::{Message, Query},
20    rr::Record,
21};
22
23/// To reduce errors in using the Message struct as an Update, this will do the call throughs
24///   to properly do that.
25///
26/// Generally rather than constructing this by hand, see the update methods on `Client`
27pub trait UpdateMessage: Debug {
28    /// see `Header::id`
29    fn id(&self) -> u16;
30
31    /// Adds the zone section, i.e. name.example.com would be example.com
32    fn add_zone(&mut self, query: Query);
33
34    /// Add the pre-requisite records
35    ///
36    /// These must exist, or not, for the Update request to go through.
37    fn add_pre_requisite(&mut self, record: Record);
38
39    /// Add all the Records from the Iterator to the pre-requisites section
40    fn add_pre_requisites<R, I>(&mut self, records: R)
41    where
42        R: IntoIterator<Item = Record, IntoIter = I>,
43        I: Iterator<Item = Record>;
44
45    /// Add the Record to be updated
46    fn add_update(&mut self, record: Record);
47
48    /// Add the Records from the Iterator to the updates section
49    fn add_updates<R, I>(&mut self, records: R)
50    where
51        R: IntoIterator<Item = Record, IntoIter = I>,
52        I: Iterator<Item = Record>;
53
54    /// Add Records to the additional Section of the UpdateMessage
55    fn add_additional(&mut self, record: Record);
56
57    /// Returns the Zones to be updated, generally should only be one.
58    fn zones(&self) -> &[Query];
59
60    /// Returns the pre-requisites
61    fn prerequisites(&self) -> &[Record];
62
63    /// Returns the records to be updated
64    fn updates(&self) -> &[Record];
65
66    /// Returns the additional records
67    fn additionals(&self) -> &[Record];
68
69    /// This is used to authenticate update messages.
70    ///
71    /// see `Message::sig0()` for more information.
72    fn sig0(&self) -> &[Record];
73}
74
75/// to reduce errors in using the Message struct as an Update, this will do the call throughs
76///   to properly do that.
77impl UpdateMessage for Message {
78    fn id(&self) -> u16 {
79        self.id()
80    }
81
82    fn add_zone(&mut self, query: Query) {
83        self.add_query(query);
84    }
85
86    fn add_pre_requisite(&mut self, record: Record) {
87        self.add_answer(record);
88    }
89
90    fn add_pre_requisites<R, I>(&mut self, records: R)
91    where
92        R: IntoIterator<Item = Record, IntoIter = I>,
93        I: Iterator<Item = Record>,
94    {
95        self.add_answers(records);
96    }
97
98    fn add_update(&mut self, record: Record) {
99        self.add_name_server(record);
100    }
101
102    fn add_updates<R, I>(&mut self, records: R)
103    where
104        R: IntoIterator<Item = Record, IntoIter = I>,
105        I: Iterator<Item = Record>,
106    {
107        self.add_name_servers(records);
108    }
109
110    fn add_additional(&mut self, record: Record) {
111        self.add_additional(record);
112    }
113
114    fn zones(&self) -> &[Query] {
115        self.queries()
116    }
117
118    fn prerequisites(&self) -> &[Record] {
119        self.answers()
120    }
121
122    fn updates(&self) -> &[Record] {
123        self.name_servers()
124    }
125
126    fn additionals(&self) -> &[Record] {
127        self.additionals()
128    }
129
130    fn sig0(&self) -> &[Record] {
131        self.sig0()
132    }
133}
134
135/// Sends a record to create on the server, this will fail if the record exists (atomicity
136///  depends on the server)
137///
138/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
139///
140/// ```text
141///  2.4.3 - RRset Does Not Exist
142///
143///   No RRs with a specified NAME and TYPE (in the zone and class denoted
144///   by the Zone Section) can exist.
145///
146///   For this prerequisite, a requestor adds to the section a single RR
147///   whose NAME and TYPE are equal to that of the RRset whose nonexistence
148///   is required.  The RDLENGTH of this record is zero (0), and RDATA
149///   field is therefore empty.  CLASS must be specified as NONE in order
150///   to distinguish this condition from a valid RR whose RDLENGTH is
151///   naturally zero (0) (for example, the NULL RR).  TTL must be specified
152///   as zero (0).
153///
154/// 2.5.1 - Add To An RRset
155///
156///    RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH
157///    and RDATA are those being added, and CLASS is the same as the zone
158///    class.  Any duplicate RRs will be silently ignored by the Primary
159///    Zone Server.
160/// ```
161///
162/// # Arguments
163///
164/// * `rrset` - the record(s) to create
165/// * `zone_origin` - the zone name to update, i.e. SOA name
166///
167/// The update must go to a zone authority (i.e. the server used in the ClientConnection)
168#[cfg(any(feature = "std", feature = "no-std-rand"))]
169pub fn create(rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message {
170    // TODO: assert non-empty rrset?
171    assert!(zone_origin.zone_of(rrset.name()));
172
173    // for updates, the query section is used for the zone
174    let mut zone: Query = Query::new();
175    zone.set_name(zone_origin)
176        .set_query_class(rrset.dns_class())
177        .set_query_type(RecordType::SOA);
178
179    // build the message
180    let mut message: Message = Message::new();
181    message
182        .set_id(random())
183        .set_message_type(MessageType::Query)
184        .set_op_code(OpCode::Update)
185        .set_recursion_desired(false);
186    message.add_zone(zone);
187
188    let mut prerequisite = Record::update0(rrset.name().clone(), 0, rrset.record_type());
189    prerequisite.set_dns_class(DNSClass::NONE);
190    message.add_pre_requisite(prerequisite.into_record_of_rdata());
191    message.add_updates(rrset);
192
193    // Extended dns
194    if use_edns {
195        message
196            .extensions_mut()
197            .get_or_insert_with(Edns::new)
198            .set_max_payload(MAX_PAYLOAD_LEN)
199            .set_version(0);
200    }
201
202    message
203}
204
205/// Appends a record to an existing rrset, optionally require the rrset to exist (atomicity
206///  depends on the server)
207///
208/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
209///
210/// ```text
211/// 2.4.1 - RRset Exists (Value Independent)
212///
213///   At least one RR with a specified NAME and TYPE (in the zone and class
214///   specified in the Zone Section) must exist.
215///
216///   For this prerequisite, a requestor adds to the section a single RR
217///   whose NAME and TYPE are equal to that of the zone RRset whose
218///   existence is required.  RDLENGTH is zero and RDATA is therefore
219///   empty.  CLASS must be specified as ANY to differentiate this
220///   condition from that of an actual RR whose RDLENGTH is naturally zero
221///   (0) (e.g., NULL).  TTL is specified as zero (0).
222///
223/// 2.5.1 - Add To An RRset
224///
225///    RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH
226///    and RDATA are those being added, and CLASS is the same as the zone
227///    class.  Any duplicate RRs will be silently ignored by the Primary
228///    Zone Server.
229/// ```
230///
231/// # Arguments
232///
233/// * `rrset` - the record(s) to append to an RRSet
234/// * `zone_origin` - the zone name to update, i.e. SOA name
235/// * `must_exist` - if true, the request will fail if the record does not exist
236///
237/// The update must go to a zone authority (i.e. the server used in the ClientConnection). If
238/// the rrset does not exist and must_exist is false, then the RRSet will be created.
239#[cfg(any(feature = "std", feature = "no-std-rand"))]
240pub fn append(rrset: RecordSet, zone_origin: Name, must_exist: bool, use_edns: bool) -> Message {
241    assert!(zone_origin.zone_of(rrset.name()));
242
243    // for updates, the query section is used for the zone
244    let mut zone: Query = Query::new();
245    zone.set_name(zone_origin)
246        .set_query_class(rrset.dns_class())
247        .set_query_type(RecordType::SOA);
248
249    // build the message
250    let mut message: Message = Message::new();
251    message
252        .set_id(random())
253        .set_message_type(MessageType::Query)
254        .set_op_code(OpCode::Update)
255        .set_recursion_desired(false);
256    message.add_zone(zone);
257
258    if must_exist {
259        let mut prerequisite = Record::update0(rrset.name().clone(), 0, rrset.record_type());
260        prerequisite.set_dns_class(DNSClass::ANY);
261        message.add_pre_requisite(prerequisite.into_record_of_rdata());
262    }
263
264    message.add_updates(rrset);
265
266    // Extended dns
267    if use_edns {
268        message
269            .extensions_mut()
270            .get_or_insert_with(Edns::new)
271            .set_max_payload(MAX_PAYLOAD_LEN)
272            .set_version(0);
273    }
274
275    message
276}
277
278/// Compares and if it matches, swaps it for the new value (atomicity depends on the server)
279///
280/// ```text
281///  2.4.2 - RRset Exists (Value Dependent)
282///
283///   A set of RRs with a specified NAME and TYPE exists and has the same
284///   members with the same RDATAs as the RRset specified here in this
285///   section.  While RRset ordering is undefined and therefore not
286///   significant to this comparison, the sets be identical in their
287///   extent.
288///
289///   For this prerequisite, a requestor adds to the section an entire
290///   RRset whose preexistence is required.  NAME and TYPE are that of the
291///   RRset being denoted.  CLASS is that of the zone.  TTL must be
292///   specified as zero (0) and is ignored when comparing RRsets for
293///   identity.
294///
295///  2.5.4 - Delete An RR From An RRset
296///
297///   RRs to be deleted are added to the Update Section.  The NAME, TYPE,
298///   RDLENGTH and RDATA must match the RR being deleted.  TTL must be
299///   specified as zero (0) and will otherwise be ignored by the Primary
300///   Zone Server.  CLASS must be specified as NONE to distinguish this from an
301///   RR addition.  If no such RRs exist, then this Update RR will be
302///   silently ignored by the Primary Zone Server.
303///
304///  2.5.1 - Add To An RRset
305///
306///   RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH
307///   and RDATA are those being added, and CLASS is the same as the zone
308///   class.  Any duplicate RRs will be silently ignored by the Primary
309///   Zone Server.
310/// ```
311///
312/// # Arguments
313///
314/// * `current` - the current rrset which must exist for the swap to complete
315/// * `new` - the new rrset with which to replace the current rrset
316/// * `zone_origin` - the zone name to update, i.e. SOA name
317///
318/// The update must go to a zone authority (i.e. the server used in the ClientConnection).
319#[cfg(any(feature = "std", feature = "no-std-rand"))]
320pub fn compare_and_swap(
321    current: RecordSet,
322    new: RecordSet,
323    zone_origin: Name,
324    use_edns: bool,
325) -> Message {
326    assert!(zone_origin.zone_of(current.name()));
327    assert!(zone_origin.zone_of(new.name()));
328
329    // for updates, the query section is used for the zone
330    let mut zone: Query = Query::new();
331    zone.set_name(zone_origin)
332        .set_query_class(new.dns_class())
333        .set_query_type(RecordType::SOA);
334
335    // build the message
336    let mut message: Message = Message::new();
337    message
338        .set_id(random())
339        .set_message_type(MessageType::Query)
340        .set_op_code(OpCode::Update)
341        .set_recursion_desired(false);
342    message.add_zone(zone);
343
344    // make sure the record is what is expected
345    let mut prerequisite = current.clone();
346    prerequisite.set_ttl(0);
347    message.add_pre_requisites(prerequisite);
348
349    // add the delete for the old record
350    let mut delete = current;
351    // the class must be none for delete
352    delete.set_dns_class(DNSClass::NONE);
353    // the TTL should be 0
354    delete.set_ttl(0);
355    message.add_updates(delete);
356
357    // insert the new record...
358    message.add_updates(new);
359
360    // Extended dns
361    if use_edns {
362        message
363            .extensions_mut()
364            .get_or_insert_with(Edns::new)
365            .set_max_payload(MAX_PAYLOAD_LEN)
366            .set_version(0);
367    }
368
369    message
370}
371
372/// Deletes a record (by rdata) from an rrset, optionally require the rrset to exist.
373///
374/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
375///
376/// ```text
377/// 2.4.1 - RRset Exists (Value Independent)
378///
379///   At least one RR with a specified NAME and TYPE (in the zone and class
380///   specified in the Zone Section) must exist.
381///
382///   For this prerequisite, a requestor adds to the section a single RR
383///   whose NAME and TYPE are equal to that of the zone RRset whose
384///   existence is required.  RDLENGTH is zero and RDATA is therefore
385///   empty.  CLASS must be specified as ANY to differentiate this
386///   condition from that of an actual RR whose RDLENGTH is naturally zero
387///   (0) (e.g., NULL).  TTL is specified as zero (0).
388///
389/// 2.5.4 - Delete An RR From An RRset
390///
391///   RRs to be deleted are added to the Update Section.  The NAME, TYPE,
392///   RDLENGTH and RDATA must match the RR being deleted.  TTL must be
393///   specified as zero (0) and will otherwise be ignored by the Primary
394///   Zone Server.  CLASS must be specified as NONE to distinguish this from an
395///   RR addition.  If no such RRs exist, then this Update RR will be
396///   silently ignored by the Primary Zone Server.
397/// ```
398///
399/// # Arguments
400///
401/// * `rrset` - the record(s) to delete from a RRSet, the name, type and rdata must match the
402///              record to delete
403/// * `zone_origin` - the zone name to update, i.e. SOA name
404/// * `signer` - the signer, with private key, to use to sign the request
405///
406/// The update must go to a zone authority (i.e. the server used in the ClientConnection). If
407/// the rrset does not exist and must_exist is false, then the RRSet will be deleted.
408#[cfg(any(feature = "std", feature = "no-std-rand"))]
409pub fn delete_by_rdata(mut rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message {
410    assert!(zone_origin.zone_of(rrset.name()));
411
412    // for updates, the query section is used for the zone
413    let mut zone: Query = Query::new();
414    zone.set_name(zone_origin)
415        .set_query_class(rrset.dns_class())
416        .set_query_type(RecordType::SOA);
417
418    // build the message
419    let mut message: Message = Message::new();
420    message
421        .set_id(random())
422        .set_message_type(MessageType::Query)
423        .set_op_code(OpCode::Update)
424        .set_recursion_desired(false);
425    message.add_zone(zone);
426
427    // the class must be none for delete
428    rrset.set_dns_class(DNSClass::NONE);
429    // the TTL should be 0
430    rrset.set_ttl(0);
431    message.add_updates(rrset);
432
433    // Extended dns
434    if use_edns {
435        message
436            .extensions_mut()
437            .get_or_insert(Edns::new())
438            .set_max_payload(MAX_PAYLOAD_LEN)
439            .set_version(0);
440    }
441
442    message
443}
444
445/// Deletes an entire rrset, optionally require the rrset to exist.
446///
447/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
448///
449/// ```text
450/// 2.4.1 - RRset Exists (Value Independent)
451///
452///   At least one RR with a specified NAME and TYPE (in the zone and class
453///   specified in the Zone Section) must exist.
454///
455///   For this prerequisite, a requestor adds to the section a single RR
456///   whose NAME and TYPE are equal to that of the zone RRset whose
457///   existence is required.  RDLENGTH is zero and RDATA is therefore
458///   empty.  CLASS must be specified as ANY to differentiate this
459///   condition from that of an actual RR whose RDLENGTH is naturally zero
460///   (0) (e.g., NULL).  TTL is specified as zero (0).
461///
462/// 2.5.2 - Delete An RRset
463///
464///   One RR is added to the Update Section whose NAME and TYPE are those
465///   of the RRset to be deleted.  TTL must be specified as zero (0) and is
466///   otherwise not used by the Primary Zone Server.  CLASS must be specified as
467///   ANY.  RDLENGTH must be zero (0) and RDATA must therefore be empty.
468///   If no such RRset exists, then this Update RR will be silently ignored
469///   by the Primary Zone Server.
470/// ```
471///
472/// # Arguments
473///
474/// * `record` - The name, class and record_type will be used to match and delete the RecordSet
475/// * `zone_origin` - the zone name to update, i.e. SOA name
476///
477/// The update must go to a zone authority (i.e. the server used in the ClientConnection). If
478/// the rrset does not exist and must_exist is false, then the RRSet will be deleted.
479#[cfg(any(feature = "std", feature = "no-std-rand"))]
480pub fn delete_rrset(mut record: Record, zone_origin: Name, use_edns: bool) -> Message {
481    assert!(zone_origin.zone_of(record.name()));
482
483    // for updates, the query section is used for the zone
484    let mut zone: Query = Query::new();
485    zone.set_name(zone_origin)
486        .set_query_class(record.dns_class())
487        .set_query_type(RecordType::SOA);
488
489    // build the message
490    let mut message: Message = Message::new();
491    message
492        .set_id(random())
493        .set_message_type(MessageType::Query)
494        .set_op_code(OpCode::Update)
495        .set_recursion_desired(false);
496    message.add_zone(zone);
497
498    // the class must be none for an rrset delete
499    record.set_dns_class(DNSClass::ANY);
500    // the TTL should be 0
501    record.set_ttl(0);
502    // the rdata must be null to delete all rrsets
503    record.set_data(RData::Update0(record.record_type()));
504    message.add_update(record);
505
506    // Extended dns
507    if use_edns {
508        message
509            .extensions_mut()
510            .get_or_insert_with(Edns::new)
511            .set_max_payload(MAX_PAYLOAD_LEN)
512            .set_version(0);
513    }
514
515    message
516}
517
518/// Deletes all records at the specified name
519///
520/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
521///
522/// ```text
523/// 2.5.3 - Delete All RRsets From A Name
524///
525///   One RR is added to the Update Section whose NAME is that of the name
526///   to be cleansed of RRsets.  TYPE must be specified as ANY.  TTL must
527///   be specified as zero (0) and is otherwise not used by the Primary
528///   Zone Server.  CLASS must be specified as ANY.  RDLENGTH must be zero (0)
529///   and RDATA must therefore be empty.  If no such RRsets exist, then
530///   this Update RR will be silently ignored by the Primary Zone Server.
531/// ```
532///
533/// # Arguments
534///
535/// * `name_of_records` - the name of all the record sets to delete
536/// * `zone_origin` - the zone name to update, i.e. SOA name
537/// * `dns_class` - the class of the SOA
538///
539/// The update must go to a zone authority (i.e. the server used in the ClientConnection). This
540/// operation attempts to delete all resource record sets the specified name regardless of
541/// the record type.
542#[cfg(any(feature = "std", feature = "no-std-rand"))]
543pub fn delete_all(
544    name_of_records: Name,
545    zone_origin: Name,
546    dns_class: DNSClass,
547    use_edns: bool,
548) -> Message {
549    assert!(zone_origin.zone_of(&name_of_records));
550
551    // for updates, the query section is used for the zone
552    let mut zone: Query = Query::new();
553    zone.set_name(zone_origin)
554        .set_query_class(dns_class)
555        .set_query_type(RecordType::SOA);
556
557    // build the message
558    let mut message: Message = Message::new();
559    message
560        .set_id(random())
561        .set_message_type(MessageType::Query)
562        .set_op_code(OpCode::Update)
563        .set_recursion_desired(false);
564    message.add_zone(zone);
565
566    // the TTL should be 0
567    // the rdata must be null to delete all rrsets
568    // the record type must be any
569    let mut record = Record::update0(name_of_records, 0, RecordType::ANY);
570
571    // the class must be none for an rrset delete
572    record.set_dns_class(DNSClass::ANY);
573
574    message.add_update(record.into_record_of_rdata());
575
576    // Extended dns
577    if use_edns {
578        message
579            .extensions_mut()
580            .get_or_insert_with(Edns::new)
581            .set_max_payload(MAX_PAYLOAD_LEN)
582            .set_version(0);
583    }
584
585    message
586}
587
588// not an update per-se, but it fits nicely with other functions here
589/// Download all records from a zone, or all records modified since given SOA was observed.
590/// The request will either be a AXFR Query (ask for full zone transfer) if a SOA was not
591/// provided, or a IXFR Query (incremental zone transfer) if a SOA was provided.
592///
593/// # Arguments
594/// * `zone_origin` - the zone name to update, i.e. SOA name
595/// * `last_soa` - the last SOA known, if any. If provided, name must match `zone_origin`
596#[cfg(any(feature = "std", feature = "no-std-rand"))]
597pub fn zone_transfer(zone_origin: Name, last_soa: Option<SOA>) -> Message {
598    if let Some(soa) = &last_soa {
599        assert_eq!(&zone_origin, soa.mname());
600    }
601
602    let mut zone: Query = Query::new();
603    zone.set_name(zone_origin).set_query_class(DNSClass::IN);
604    if last_soa.is_some() {
605        zone.set_query_type(RecordType::IXFR);
606    } else {
607        zone.set_query_type(RecordType::AXFR);
608    }
609
610    // build the message
611    let mut message: Message = Message::new();
612    message
613        .set_id(random())
614        .set_message_type(MessageType::Query)
615        .set_recursion_desired(false);
616    message.add_zone(zone);
617
618    if let Some(soa) = last_soa {
619        // for IXFR, old SOA is put as authority to indicate last known version
620        let record = Record::from_rdata(soa.mname().clone(), 0, RData::SOA(soa));
621        message.add_name_server(record);
622    }
623
624    // Extended dns
625    {
626        message
627            .extensions_mut()
628            .get_or_insert_with(Edns::new)
629            .set_max_payload(MAX_PAYLOAD_LEN)
630            .set_version(0);
631    }
632
633    message
634}
635
636// TODO: this should be configurable
637// > An EDNS buffer size of 1232 bytes will avoid fragmentation on nearly all current networks.
638// https://dnsflagday.net/2020/
639/// Maximum payload length for EDNS update messages
640pub const MAX_PAYLOAD_LEN: u16 = 1232;