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}