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