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;