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;