Skip to main content

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