Skip to main content

hickory_proto/op/
dns_request.rs

1// Copyright 2015-2018 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//! `DnsRequest` wraps a `Message` and associates a set of `DnsRequestOptions` for specifying different transfer options.
9
10use core::ops::{Deref, DerefMut};
11#[cfg(feature = "std")]
12use core::time::Duration;
13
14#[cfg(feature = "std")]
15use super::{DEFAULT_RETRY_FLOOR, Edns};
16use super::{Message, Query, edns::DEFAULT_MAX_PAYLOAD_LEN};
17
18/// A set of options for expressing options to how requests should be treated
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20#[non_exhaustive]
21pub struct DnsRequestOptions {
22    // TODO: add EDNS options here?
23    /// When true, will add EDNS options to the request.
24    pub use_edns: bool,
25    /// EDNS UDP payload size.
26    ///
27    /// Sets the requestor's UDP payload size in the EDNS(0) OPT pseudo-RR in outgoing requests.
28    /// This tells other servers when they need to truncate their responses. Smaller payload sizes
29    /// require more queries with large responses to be retried over TCP, while larger payload sizes
30    /// lead to large responses being fragmented or dropped if they exceed the MTU of a network.
31    ///
32    /// See <https://www.dnsflagday.net/2020/> and
33    /// [RFC 9715](https://www.rfc-editor.org/rfc/rfc9715.html) for discussion.
34    pub edns_payload_len: u16,
35    /// When true, sets the DO bit in the EDNS options
36    pub edns_set_dnssec_ok: bool,
37    /// Specifies maximum request depth for DNSSEC validation.
38    pub max_request_depth: usize,
39    /// set recursion desired (or not) for any requests
40    pub recursion_desired: bool,
41    /// Randomize case of query name, and check that the response matches, for spoofing resistance.
42    #[cfg(feature = "std")]
43    pub case_randomization: bool,
44    /// Retry interval for unreliable transport protocols (plain UDP). Any value lower than the
45    /// retry_interval_floor value set by a protocol implementer will effectively
46    /// be ignored, but higher values will result in less frequent retries.
47    #[cfg(feature = "std")]
48    pub retry_interval: Duration,
49}
50
51impl Default for DnsRequestOptions {
52    fn default() -> Self {
53        Self {
54            max_request_depth: 26,
55            use_edns: true,
56            edns_payload_len: DEFAULT_MAX_PAYLOAD_LEN,
57            edns_set_dnssec_ok: false,
58            recursion_desired: true,
59            #[cfg(feature = "std")]
60            case_randomization: false,
61            // We use the default value for the retry interval floor here as a good starting point.
62            #[cfg(feature = "std")]
63            retry_interval: DEFAULT_RETRY_FLOOR,
64        }
65    }
66}
67
68/// A DNS request object
69///
70/// This wraps a DNS Message for requests. It also has request options associated for controlling certain features of the DNS protocol handlers.
71#[derive(Clone, PartialEq, Eq)]
72pub struct DnsRequest {
73    message: Message,
74    options: DnsRequestOptions,
75    /// If case randomization was replied to the request, this holds the original query.
76    original_query: Option<Query>,
77}
78
79impl DnsRequest {
80    /// Build a new `DnsRequest` from a `Query` and `DnsRequestOptions`.
81    #[cfg(feature = "std")]
82    pub fn from_query(mut query: Query, options: DnsRequestOptions) -> Self {
83        let mut message = Message::query();
84        let mut original_query = None;
85
86        if options.case_randomization {
87            original_query = Some(query.clone());
88            query.name.randomize_label_case();
89        }
90
91        message.queries.push(query);
92        message.metadata.recursion_desired = options.recursion_desired;
93
94        if options.use_edns {
95            message
96                .edns
97                .get_or_insert_with(Edns::new)
98                .set_max_payload(options.edns_payload_len)
99                .set_dnssec_ok(options.edns_set_dnssec_ok);
100        }
101
102        Self::new(message, options).with_original_query(original_query)
103    }
104
105    /// Returns a new DnsRequest object
106    pub fn new(message: Message, options: DnsRequestOptions) -> Self {
107        Self {
108            message,
109            options,
110            original_query: None,
111        }
112    }
113
114    /// Add the original query
115    pub fn with_original_query(mut self, original_query: Option<Query>) -> Self {
116        self.original_query = original_query;
117        self
118    }
119
120    /// Get the set of request options associated with this request
121    pub fn options(&self) -> &DnsRequestOptions {
122        &self.options
123    }
124
125    /// Get a mutable reference to the request options associated with this request
126    pub fn options_mut(&mut self) -> &mut DnsRequestOptions {
127        &mut self.options
128    }
129
130    /// Unwraps the raw message
131    pub fn into_parts(self) -> (Message, DnsRequestOptions) {
132        (self.message, self.options)
133    }
134
135    /// Get the request's original query
136    pub fn original_query(&self) -> Option<&Query> {
137        self.original_query.as_ref()
138    }
139}
140
141impl Deref for DnsRequest {
142    type Target = Message;
143    fn deref(&self) -> &Self::Target {
144        &self.message
145    }
146}
147
148impl DerefMut for DnsRequest {
149    fn deref_mut(&mut self) -> &mut Self::Target {
150        &mut self.message
151    }
152}
153
154impl From<Message> for DnsRequest {
155    fn from(message: Message) -> Self {
156        Self::new(message, DnsRequestOptions::default())
157    }
158}
159
160#[cfg(all(test, feature = "std"))]
161mod tests {
162    use super::*;
163    use crate::rr::{Name, RecordType};
164
165    #[test]
166    fn from_query_default_includes_edns() {
167        let query = Query::query(Name::from_ascii("example.com.").unwrap(), RecordType::A);
168        let request = DnsRequest::from_query(query, DnsRequestOptions::default());
169        assert!(request.edns.is_some());
170        assert_eq!(request.max_payload(), DEFAULT_MAX_PAYLOAD_LEN);
171    }
172
173    #[test]
174    fn from_query_edns_disabled_no_opt() {
175        let query = Query::query(Name::from_ascii("example.com.").unwrap(), RecordType::A);
176        let request = DnsRequest::from_query(
177            query,
178            DnsRequestOptions {
179                use_edns: false,
180                ..DnsRequestOptions::default()
181            },
182        );
183
184        assert!(request.edns.is_none());
185        assert_eq!(request.max_payload(), 512);
186    }
187}