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/// * `use_edns` - if true, EDNS options will be added to the request
167///
168/// The update must go to a zone authority (i.e. the server used in the ClientConnection)
169#[cfg(any(feature = "std", feature = "no-std-rand"))]
170pub fn create(rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message {
171    // TODO: assert non-empty rrset?
172    assert!(zone_origin.zone_of(rrset.name()));
173
174    // for updates, the query section is used for the zone
175    let mut zone: Query = Query::new();
176    zone.set_name(zone_origin)
177        .set_query_class(rrset.dns_class())
178        .set_query_type(RecordType::SOA);
179
180    // build the message
181    let mut message: Message = Message::new();
182    message
183        .set_id(random())
184        .set_message_type(MessageType::Query)
185        .set_op_code(OpCode::Update)
186        .set_recursion_desired(false);
187    message.add_zone(zone);
188
189    let mut prerequisite = Record::update0(rrset.name().clone(), 0, rrset.record_type());
190    prerequisite.set_dns_class(DNSClass::NONE);
191    message.add_pre_requisite(prerequisite.into_record_of_rdata());
192    message.add_updates(rrset);
193
194    // Extended dns
195    if use_edns {
196        message
197            .extensions_mut()
198            .get_or_insert_with(Edns::new)
199            .set_max_payload(MAX_PAYLOAD_LEN)
200            .set_version(0);
201    }
202
203    message
204}
205
206/// Appends a record to an existing rrset, optionally require the rrset to exist (atomicity
207///  depends on the server)
208///
209/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
210///
211/// ```text
212/// 2.4.1 - RRset Exists (Value Independent)
213///
214///   At least one RR with a specified NAME and TYPE (in the zone and class
215///   specified in the Zone Section) must exist.
216///
217///   For this prerequisite, a requestor adds to the section a single RR
218///   whose NAME and TYPE are equal to that of the zone RRset whose
219///   existence is required.  RDLENGTH is zero and RDATA is therefore
220///   empty.  CLASS must be specified as ANY to differentiate this
221///   condition from that of an actual RR whose RDLENGTH is naturally zero
222///   (0) (e.g., NULL).  TTL is specified as zero (0).
223///
224/// 2.5.1 - Add To An RRset
225///
226///    RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH
227///    and RDATA are those being added, and CLASS is the same as the zone
228///    class.  Any duplicate RRs will be silently ignored by the Primary
229///    Zone Server.
230/// ```
231///
232/// # Arguments
233///
234/// * `rrset` - the record(s) to append to an RRSet
235/// * `zone_origin` - the zone name to update, i.e. SOA name
236/// * `must_exist` - if true, the request will fail if the record does not exist
237/// * `use_edns` - if true, EDNS options will be added to the request
238///
239/// The update must go to a zone authority (i.e. the server used in the ClientConnection). If
240/// the rrset does not exist and must_exist is false, then the RRSet will be created.
241#[cfg(any(feature = "std", feature = "no-std-rand"))]
242pub fn append(rrset: RecordSet, zone_origin: Name, must_exist: bool, use_edns: bool) -> Message {
243    assert!(zone_origin.zone_of(rrset.name()));
244
245    // for updates, the query section is used for the zone
246    let mut zone: Query = Query::new();
247    zone.set_name(zone_origin)
248        .set_query_class(rrset.dns_class())
249        .set_query_type(RecordType::SOA);
250
251    // build the message
252    let mut message: Message = Message::new();
253    message
254        .set_id(random())
255        .set_message_type(MessageType::Query)
256        .set_op_code(OpCode::Update)
257        .set_recursion_desired(false);
258    message.add_zone(zone);
259
260    if must_exist {
261        let mut prerequisite = Record::update0(rrset.name().clone(), 0, rrset.record_type());
262        prerequisite.set_dns_class(DNSClass::ANY);
263        message.add_pre_requisite(prerequisite.into_record_of_rdata());
264    }
265
266    message.add_updates(rrset);
267
268    // Extended dns
269    if use_edns {
270        message
271            .extensions_mut()
272            .get_or_insert_with(Edns::new)
273            .set_max_payload(MAX_PAYLOAD_LEN)
274            .set_version(0);
275    }
276
277    message
278}
279
280/// Compares and if it matches, swaps it for the new value (atomicity depends on the server)
281///
282/// ```text
283///  2.4.2 - RRset Exists (Value Dependent)
284///
285///   A set of RRs with a specified NAME and TYPE exists and has the same
286///   members with the same RDATAs as the RRset specified here in this
287///   section.  While RRset ordering is undefined and therefore not
288///   significant to this comparison, the sets be identical in their
289///   extent.
290///
291///   For this prerequisite, a requestor adds to the section an entire
292///   RRset whose preexistence is required.  NAME and TYPE are that of the
293///   RRset being denoted.  CLASS is that of the zone.  TTL must be
294///   specified as zero (0) and is ignored when comparing RRsets for
295///   identity.
296///
297///  2.5.4 - Delete An RR From An RRset
298///
299///   RRs to be deleted are added to the Update Section.  The NAME, TYPE,
300///   RDLENGTH and RDATA must match the RR being deleted.  TTL must be
301///   specified as zero (0) and will otherwise be ignored by the Primary
302///   Zone Server.  CLASS must be specified as NONE to distinguish this from an
303///   RR addition.  If no such RRs exist, then this Update RR will be
304///   silently ignored by the Primary Zone Server.
305///
306///  2.5.1 - Add To An RRset
307///
308///   RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH
309///   and RDATA are those being added, and CLASS is the same as the zone
310///   class.  Any duplicate RRs will be silently ignored by the Primary
311///   Zone Server.
312/// ```
313///
314/// # Arguments
315///
316/// * `current` - the current rrset which must exist for the swap to complete
317/// * `new` - the new rrset with which to replace the current rrset
318/// * `zone_origin` - the zone name to update, i.e. SOA name
319/// * `use_edns` - if true, EDNS options will be added to the request
320///
321/// The update must go to a zone authority (i.e. the server used in the ClientConnection).
322#[cfg(any(feature = "std", feature = "no-std-rand"))]
323pub fn compare_and_swap(
324    current: RecordSet,
325    new: RecordSet,
326    zone_origin: Name,
327    use_edns: bool,
328) -> Message {
329    assert!(zone_origin.zone_of(current.name()));
330    assert!(zone_origin.zone_of(new.name()));
331
332    // for updates, the query section is used for the zone
333    let mut zone: Query = Query::new();
334    zone.set_name(zone_origin)
335        .set_query_class(new.dns_class())
336        .set_query_type(RecordType::SOA);
337
338    // build the message
339    let mut message: Message = Message::new();
340    message
341        .set_id(random())
342        .set_message_type(MessageType::Query)
343        .set_op_code(OpCode::Update)
344        .set_recursion_desired(false);
345    message.add_zone(zone);
346
347    // make sure the record is what is expected
348    let mut prerequisite = current.clone();
349    prerequisite.set_ttl(0);
350    message.add_pre_requisites(prerequisite);
351
352    // add the delete for the old record
353    let mut delete = current;
354    // the class must be none for delete
355    delete.set_dns_class(DNSClass::NONE);
356    // the TTL should be 0
357    delete.set_ttl(0);
358    message.add_updates(delete);
359
360    // insert the new record...
361    message.add_updates(new);
362
363    // Extended dns
364    if use_edns {
365        message
366            .extensions_mut()
367            .get_or_insert_with(Edns::new)
368            .set_max_payload(MAX_PAYLOAD_LEN)
369            .set_version(0);
370    }
371
372    message
373}
374
375/// Deletes a record (by rdata) from an rrset
376///
377/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
378///
379/// ```text
380/// 2.5.4 - Delete An RR From An RRset
381///
382///   RRs to be deleted are added to the Update Section.  The NAME, TYPE,
383///   RDLENGTH and RDATA must match the RR being deleted.  TTL must be
384///   specified as zero (0) and will otherwise be ignored by the Primary
385///   Zone Server.  CLASS must be specified as NONE to distinguish this from an
386///   RR addition.  If no such RRs exist, then this Update RR will be
387///   silently ignored by the Primary Zone Server.
388/// ```
389///
390/// # Arguments
391///
392/// * `rrset` - the record(s) to delete from a RRSet, the name, type and rdata must match the
393///   record to delete
394/// * `zone_origin` - the zone name to update, i.e. SOA name
395/// * `use_edns` - if true, EDNS options will be added to the request
396///
397/// The update must go to a zone authority (i.e. the server used in the ClientConnection).
398/// If no such RRs exist, then this Update RR will be silently ignored by the Primary Zone Server.
399#[cfg(any(feature = "std", feature = "no-std-rand"))]
400pub fn delete_by_rdata(mut rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message {
401    assert!(zone_origin.zone_of(rrset.name()));
402
403    // for updates, the query section is used for the zone
404    let mut zone: Query = Query::new();
405    zone.set_name(zone_origin)
406        .set_query_class(rrset.dns_class())
407        .set_query_type(RecordType::SOA);
408
409    // build the message
410    let mut message: Message = Message::new();
411    message
412        .set_id(random())
413        .set_message_type(MessageType::Query)
414        .set_op_code(OpCode::Update)
415        .set_recursion_desired(false);
416    message.add_zone(zone);
417
418    // the class must be none to delete a record
419    rrset.set_dns_class(DNSClass::NONE);
420    // the TTL should be 0
421    rrset.set_ttl(0);
422    message.add_updates(rrset);
423
424    // Extended dns
425    if use_edns {
426        message
427            .extensions_mut()
428            .get_or_insert(Edns::new())
429            .set_max_payload(MAX_PAYLOAD_LEN)
430            .set_version(0);
431    }
432
433    message
434}
435
436/// Deletes an entire rrset
437///
438/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
439///
440/// ```text
441/// 2.5.2 - Delete An RRset
442///
443///   One RR is added to the Update Section whose NAME and TYPE are those
444///   of the RRset to be deleted.  TTL must be specified as zero (0) and is
445///   otherwise not used by the Primary Zone Server.  CLASS must be specified as
446///   ANY.  RDLENGTH must be zero (0) and RDATA must therefore be empty.
447///   If no such RRset exists, then this Update RR will be silently ignored
448///   by the Primary Zone Server.
449/// ```
450///
451/// # Arguments
452///
453/// * `record` - The name, class and record_type will be used to match and delete the RecordSet
454/// * `zone_origin` - the zone name to update, i.e. SOA name
455/// * `use_edns` - if true, EDNS options will be added to the request
456///
457/// The update must go to a zone authority (i.e. the server used in the ClientConnection).
458/// If no such RRset exists, then this Update RR will be silently ignored by the Primary Zone Server.
459#[cfg(any(feature = "std", feature = "no-std-rand"))]
460pub fn delete_rrset(mut record: Record, zone_origin: Name, use_edns: bool) -> Message {
461    assert!(zone_origin.zone_of(record.name()));
462
463    // for updates, the query section is used for the zone
464    let mut zone: Query = Query::new();
465    zone.set_name(zone_origin)
466        .set_query_class(record.dns_class())
467        .set_query_type(RecordType::SOA);
468
469    // build the message
470    let mut message: Message = Message::new();
471    message
472        .set_id(random())
473        .set_message_type(MessageType::Query)
474        .set_op_code(OpCode::Update)
475        .set_recursion_desired(false);
476    message.add_zone(zone);
477
478    // the class must be any to delete an rrset
479    record.set_dns_class(DNSClass::ANY);
480    // the TTL should be 0
481    record.set_ttl(0);
482    // the rdata must be null to delete an rrset
483    record.set_data(RData::Update0(record.record_type()));
484    message.add_update(record);
485
486    // Extended dns
487    if use_edns {
488        message
489            .extensions_mut()
490            .get_or_insert_with(Edns::new)
491            .set_max_payload(MAX_PAYLOAD_LEN)
492            .set_version(0);
493    }
494
495    message
496}
497
498/// Deletes all rrsets at the specified name
499///
500/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
501///
502/// ```text
503/// 2.5.3 - Delete All RRsets From A Name
504///
505///   One RR is added to the Update Section whose NAME is that of the name
506///   to be cleansed of RRsets.  TYPE must be specified as ANY.  TTL must
507///   be specified as zero (0) and is otherwise not used by the Primary
508///   Zone Server.  CLASS must be specified as ANY.  RDLENGTH must be zero (0)
509///   and RDATA must therefore be empty.  If no such RRsets exist, then
510///   this Update RR will be silently ignored by the Primary Zone Server.
511/// ```
512///
513/// # Arguments
514///
515/// * `name_of_records` - the name of all the record sets to delete
516/// * `zone_origin` - the zone name to update, i.e. SOA name
517/// * `dns_class` - the class of the SOA
518/// * `use_edns` - if true, EDNS options will be added to the request
519///
520/// The update must go to a zone authority (i.e. the server used in the ClientConnection). This
521/// operation attempts to delete all resource record sets at the specified name, regardless of
522/// the record type.
523#[cfg(any(feature = "std", feature = "no-std-rand"))]
524pub fn delete_all(
525    name_of_records: Name,
526    zone_origin: Name,
527    dns_class: DNSClass,
528    use_edns: bool,
529) -> Message {
530    assert!(zone_origin.zone_of(&name_of_records));
531
532    // for updates, the query section is used for the zone
533    let mut zone: Query = Query::new();
534    zone.set_name(zone_origin)
535        .set_query_class(dns_class)
536        .set_query_type(RecordType::SOA);
537
538    // build the message
539    let mut message: Message = Message::new();
540    message
541        .set_id(random())
542        .set_message_type(MessageType::Query)
543        .set_op_code(OpCode::Update)
544        .set_recursion_desired(false);
545    message.add_zone(zone);
546
547    // the TTL should be 0
548    // the rdata must be null to delete all rrsets
549    // the record type must be any
550    let mut record = Record::update0(name_of_records, 0, RecordType::ANY);
551
552    // the class must be any to delete all rrsets
553    record.set_dns_class(DNSClass::ANY);
554
555    message.add_update(record.into_record_of_rdata());
556
557    // Extended dns
558    if use_edns {
559        message
560            .extensions_mut()
561            .get_or_insert_with(Edns::new)
562            .set_max_payload(MAX_PAYLOAD_LEN)
563            .set_version(0);
564    }
565
566    message
567}
568
569// not an update per-se, but it fits nicely with other functions here
570/// Download all records from a zone, or all records modified since given SOA was observed.
571/// The request will either be a AXFR Query (ask for full zone transfer) if a SOA was not
572/// provided, or a IXFR Query (incremental zone transfer) if a SOA was provided.
573///
574/// # Arguments
575/// * `zone_origin` - the zone name to update, i.e. SOA name
576/// * `last_soa` - the last SOA known, if any. If provided, name must match `zone_origin`
577#[cfg(any(feature = "std", feature = "no-std-rand"))]
578pub fn zone_transfer(zone_origin: Name, last_soa: Option<SOA>) -> Message {
579    if let Some(soa) = &last_soa {
580        assert_eq!(&zone_origin, soa.mname());
581    }
582
583    let mut zone: Query = Query::new();
584    zone.set_name(zone_origin).set_query_class(DNSClass::IN);
585    if last_soa.is_some() {
586        zone.set_query_type(RecordType::IXFR);
587    } else {
588        zone.set_query_type(RecordType::AXFR);
589    }
590
591    // build the message
592    let mut message: Message = Message::new();
593    message
594        .set_id(random())
595        .set_message_type(MessageType::Query)
596        .set_recursion_desired(false);
597    message.add_zone(zone);
598
599    if let Some(soa) = last_soa {
600        // for IXFR, old SOA is put as authority to indicate last known version
601        let record = Record::from_rdata(soa.mname().clone(), 0, RData::SOA(soa));
602        message.add_name_server(record);
603    }
604
605    // Extended dns
606    {
607        message
608            .extensions_mut()
609            .get_or_insert_with(Edns::new)
610            .set_max_payload(MAX_PAYLOAD_LEN)
611            .set_version(0);
612    }
613
614    message
615}
616
617// TODO: this should be configurable
618// > An EDNS buffer size of 1232 bytes will avoid fragmentation on nearly all current networks.
619// https://dnsflagday.net/2020/
620/// Maximum payload length for EDNS update messages
621pub const MAX_PAYLOAD_LEN: u16 = 1232;