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;