Skip to main content

c_ares/channel/
options.rs

1use core::ffi::c_int;
2use std::ffi::CString;
3use std::fmt;
4use std::mem;
5use std::sync::Arc;
6use std::time::Duration;
7
8use crate::Flags;
9use crate::error::{Error, Result};
10#[cfg(cares1_26)]
11use crate::types::EventSys;
12use crate::types::Socket;
13
14pub(crate) type SocketStateCallback = dyn Fn(Socket, bool, bool) + Send + Sync + 'static;
15
16/// Server failover options.
17///
18/// When a DNS server fails to respond to a query, c-ares will deprioritize the server.  On
19/// subsequent queries, servers with fewer consecutive failures will be selected in preference.
20/// However, in order to detect when such a server has recovered, c-ares will occasionally retry
21/// failed servers.  `ServerFailoverOptions` contains options to control this behaviour.
22#[cfg(cares1_29)]
23#[derive(Debug)]
24pub struct ServerFailoverOptions {
25    retry_chance: u16,
26    retry_delay: Duration,
27}
28
29#[cfg(cares1_29)]
30impl Default for ServerFailoverOptions {
31    fn default() -> Self {
32        Self {
33            retry_chance: 10,
34            retry_delay: Duration::from_secs(5),
35        }
36    }
37}
38
39#[cfg(cares1_29)]
40impl ServerFailoverOptions {
41    /// Returns a new `ServerFailoverOptions`.
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// The `retry_chance` sets the probability (1/N) of retrying a failed server on any given
47    /// query.  Setting to a value of 0 disables retries.
48    pub fn set_retry_chance(&mut self, retry_chance: u16) -> &mut Self {
49        self.retry_chance = retry_chance;
50        self
51    }
52
53    /// The `retry_delay` sets the minimum delay that c-ares will wait before
54    /// retrying a specific failed server.
55    pub fn set_retry_delay(&mut self, retry_delay: Duration) -> &mut Self {
56        self.retry_delay = retry_delay;
57        self
58    }
59}
60
61/// Used to configure the behaviour of the name resolver.
62pub struct Options {
63    // The `ares_` prefix mirrors the FFI type name; the apparent name overlap
64    // with the struct is deliberate.
65    #[allow(clippy::struct_field_names)]
66    pub(super) ares_options: c_ares_sys::ares_options,
67    pub(super) optmask: c_int,
68    pub(super) domains: Vec<CString>,
69    pub(super) lookups: Option<CString>,
70    pub(super) resolvconf_path: Option<CString>,
71    #[cfg(cares1_19)]
72    pub(super) hosts_path: Option<CString>,
73    pub(super) socket_state_callback: Option<Arc<SocketStateCallback>>,
74}
75
76impl fmt::Debug for Options {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        f.debug_struct("Options").finish_non_exhaustive()
79    }
80}
81
82impl Default for Options {
83    fn default() -> Self {
84        Self {
85            ares_options: unsafe { mem::MaybeUninit::zeroed().assume_init() },
86            optmask: 0,
87            domains: vec![],
88            lookups: None,
89            resolvconf_path: None,
90            #[cfg(cares1_19)]
91            hosts_path: None,
92            socket_state_callback: None,
93        }
94    }
95}
96
97impl Options {
98    /// Returns a fresh `Options`, on which no values are set.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use std::time::Duration;
104    /// use c_ares::{Options, Flags};
105    ///
106    /// let mut options = Options::new();
107    /// options.set_flags(Flags::STAYOPEN | Flags::EDNS)
108    ///        .set_timeout(Duration::from_secs(5))
109    ///        .set_tries(3);
110    /// ```
111    pub fn new() -> Self {
112        Self::default()
113    }
114
115    /// Set flags controlling the behaviour of the resolver.  The available flags are documented
116    /// [here](flags/index.html).
117    pub fn set_flags(&mut self, flags: Flags) -> &mut Self {
118        self.ares_options.flags = flags.bits();
119        self.optmask |= c_ares_sys::ARES_OPT_FLAGS;
120        self
121    }
122
123    /// Set the number of milliseconds each name server is given to respond to a query on the first
124    /// try.  (After the first try, the timeout algorithm becomes more complicated, but scales
125    /// linearly with the value of timeout).  The default is 2000ms.
126    pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self {
127        self.ares_options.timeout = c_int::try_from(timeout.as_millis()).unwrap_or(c_int::MAX);
128        self.optmask |= c_ares_sys::ARES_OPT_TIMEOUTMS;
129        self
130    }
131
132    /// Set the number of tries the resolver will try contacting each name server before giving up.
133    /// The default is three tries.
134    pub fn set_tries(&mut self, tries: u32) -> &mut Self {
135        self.ares_options.tries = c_int::try_from(tries).unwrap_or(c_int::MAX);
136        self.optmask |= c_ares_sys::ARES_OPT_TRIES;
137        self
138    }
139
140    /// Set the number of dots which must be present in a domain name for it to be queried for "as
141    /// is" prior to querying for it with the default domain extensions appended.  The default
142    /// value is 1 unless set otherwise by resolv.conf or the RES_OPTIONS environment variable.
143    pub fn set_ndots(&mut self, ndots: u32) -> &mut Self {
144        self.ares_options.ndots = c_int::try_from(ndots).unwrap_or(c_int::MAX);
145        self.optmask |= c_ares_sys::ARES_OPT_NDOTS;
146        self
147    }
148
149    /// Set the UDP port to use for queries.  The default value is 53, the standard name service
150    /// port.
151    pub fn set_udp_port(&mut self, udp_port: u16) -> &mut Self {
152        self.ares_options.udp_port = udp_port;
153        self.optmask |= c_ares_sys::ARES_OPT_UDP_PORT;
154        self
155    }
156
157    /// Set the TCP port to use for queries.  The default value is 53, the standard name service
158    /// port.
159    pub fn set_tcp_port(&mut self, tcp_port: u16) -> &mut Self {
160        self.ares_options.tcp_port = tcp_port;
161        self.optmask |= c_ares_sys::ARES_OPT_TCP_PORT;
162        self
163    }
164
165    /// Set the domains to search, instead of the domains specified in resolv.conf or the domain
166    /// derived from the kernel hostname variable.
167    pub fn set_domains<I, S>(&mut self, domains: I) -> Result<&mut Self>
168    where
169        I: IntoIterator<Item = S>,
170        S: AsRef<str>,
171    {
172        self.domains = domains
173            .into_iter()
174            .map(|s| CString::new(s.as_ref()).map_err(|_| Error::EBADSTR))
175            .collect::<Result<Vec<_>>>()?;
176        self.optmask |= c_ares_sys::ARES_OPT_DOMAINS;
177        Ok(self)
178    }
179
180    /// Set the lookups to perform for host queries. `lookups` should be set to a string of the
181    /// characters "b" or "f", where "b" indicates a DNS lookup and "f" indicates a lookup in the
182    /// hosts file.
183    pub fn set_lookups(&mut self, lookups: &str) -> Result<&mut Self> {
184        let c_lookups = CString::new(lookups).map_err(|_| Error::EBADSTR)?;
185        self.lookups = Some(c_lookups);
186        self.optmask |= c_ares_sys::ARES_OPT_LOOKUPS;
187        Ok(self)
188    }
189
190    /// Set the callback function to be invoked when a socket changes state.
191    ///
192    /// `callback(socket, read, write)` will be called when a socket changes state:
193    ///
194    /// - `read` is set to true if the socket should listen for read events
195    /// - `write` is set to true if the socket should listen for write events.
196    pub fn set_socket_state_callback<F>(&mut self, callback: F) -> &mut Self
197    where
198        F: Fn(Socket, bool, bool) + Send + Sync + 'static,
199    {
200        let boxed_callback = Arc::new(callback);
201        self.ares_options.sock_state_cb = Some(super::socket_state_callback::<F>);
202        self.ares_options.sock_state_cb_data = Arc::as_ptr(&boxed_callback).cast_mut().cast();
203        self.socket_state_callback = Some(boxed_callback);
204        self.optmask |= c_ares_sys::ARES_OPT_SOCK_STATE_CB;
205        self
206    }
207
208    /// Set the socket send buffer size.
209    pub fn set_sock_send_buffer_size(&mut self, size: u32) -> &mut Self {
210        self.ares_options.socket_send_buffer_size = c_int::try_from(size).unwrap_or(c_int::MAX);
211        self.optmask |= c_ares_sys::ARES_OPT_SOCK_SNDBUF;
212        self
213    }
214
215    /// Set the socket receive buffer size.
216    pub fn set_sock_receive_buffer_size(&mut self, size: u32) -> &mut Self {
217        self.ares_options.socket_receive_buffer_size = c_int::try_from(size).unwrap_or(c_int::MAX);
218        self.optmask |= c_ares_sys::ARES_OPT_SOCK_RCVBUF;
219        self
220    }
221
222    /// Configure round robin selection of nameservers.
223    pub fn set_rotate(&mut self) -> &mut Self {
224        self.optmask &= !c_ares_sys::ARES_OPT_NOROTATE;
225        self.optmask |= c_ares_sys::ARES_OPT_ROTATE;
226        self
227    }
228
229    /// Prevent round robin selection of nameservers.
230    pub fn set_no_rotate(&mut self) -> &mut Self {
231        self.optmask &= !c_ares_sys::ARES_OPT_ROTATE;
232        self.optmask |= c_ares_sys::ARES_OPT_NOROTATE;
233        self
234    }
235
236    /// Set the EDNS packet size.
237    pub fn set_ednspsz(&mut self, size: u32) -> &mut Self {
238        self.ares_options.ednspsz = c_int::try_from(size).unwrap_or(c_int::MAX);
239        self.optmask |= c_ares_sys::ARES_OPT_EDNSPSZ;
240        self
241    }
242
243    /// Set the path to use for reading the resolv.conf file.  The `resolvconf_path` should be set
244    /// to a path string, and will be honoured on *nix like systems.  The default is
245    /// /etc/resolv.conf.
246    pub fn set_resolvconf_path(&mut self, resolvconf_path: &str) -> Result<&mut Self> {
247        let c_resolvconf_path = CString::new(resolvconf_path).map_err(|_| Error::EBADSTR)?;
248        self.resolvconf_path = Some(c_resolvconf_path);
249        self.optmask |= c_ares_sys::ARES_OPT_RESOLVCONF;
250        Ok(self)
251    }
252
253    /// Set the path to use for reading the hosts file.  The `hosts_path` should be set to a path
254    /// string, and will be honoured on *nix like systems.  The default is /etc/hosts.
255    #[cfg(cares1_19)]
256    pub fn set_hosts_path(&mut self, hosts_path: &str) -> Result<&mut Self> {
257        let c_hosts_path = CString::new(hosts_path).map_err(|_| Error::EBADSTR)?;
258        self.hosts_path = Some(c_hosts_path);
259        self.optmask |= c_ares_sys::ARES_OPT_HOSTS_FILE;
260        Ok(self)
261    }
262
263    /// Set the maximum number of UDP queries that can be sent on a single ephemeral port to a
264    /// given DNS server before a new ephemeral port is assigned.
265    ///
266    /// Pass `None` for unlimited (the default).
267    #[cfg(cares1_20)]
268    pub fn set_udp_max_queries(&mut self, udp_max_queries: Option<u32>) -> &mut Self {
269        self.ares_options.udp_max_queries = match udp_max_queries {
270            None => 0,
271            Some(n) => c_int::try_from(n).unwrap_or(c_int::MAX),
272        };
273        self.optmask |= c_ares_sys::ARES_OPT_UDP_MAX_QUERIES;
274        self
275    }
276
277    /// Set the upper bound for timeout between sequential retry attempts.  When retrying queries,
278    /// the timeout is increased from the requested timeout parameter, this caps the value.
279    #[cfg(cares1_22)]
280    pub fn set_max_timeout(&mut self, max_timeout: Duration) -> &mut Self {
281        self.ares_options.maxtimeout =
282            c_int::try_from(max_timeout.as_millis()).unwrap_or(c_int::MAX);
283        self.optmask |= c_ares_sys::ARES_OPT_MAXTIMEOUTMS;
284        self
285    }
286
287    /// Enable the built-in query cache.  Will cache queries based on the returned TTL in the DNS
288    /// message.  Only fully successful and NXDOMAIN query results will be cached.
289    ///
290    /// The provided value is the maximum number of seconds a query result may be cached; this will
291    /// override a larger TTL in the response message. This must be a non-zero value otherwise the
292    /// cache will be disabled.
293    #[cfg(cares1_23)]
294    pub fn set_query_cache_max_ttl(&mut self, qcache_max_ttl: u32) -> &mut Self {
295        self.ares_options.qcache_max_ttl = qcache_max_ttl;
296        self.optmask |= c_ares_sys::ARES_OPT_QUERY_CACHE;
297        self
298    }
299
300    /// Set server failover options.
301    ///
302    /// If this option is not specified then c-ares will use a probability of 10% and a minimum
303    /// delay of 5 seconds.
304    #[cfg(cares1_29)]
305    pub fn set_server_failover_options(
306        &mut self,
307        server_failover_options: &ServerFailoverOptions,
308    ) -> &mut Self {
309        self.ares_options.server_failover_opts.retry_chance = server_failover_options.retry_chance;
310        self.ares_options.server_failover_opts.retry_delay =
311            server_failover_options.retry_delay.as_millis() as usize;
312        self.optmask |= c_ares_sys::ARES_OPT_SERVER_FAILOVER;
313        self
314    }
315
316    /// Enable the c-ares built-in event thread.
317    ///
318    /// When enabled, c-ares manages its own event loop internally.  The caller does not need to
319    /// monitor sockets or call `process_fd()`.  Requires that c-ares was built with thread safety
320    /// (`ares_threadsafety()` returns true); otherwise channel initialisation will fail.
321    ///
322    /// `evsys` selects the I/O backend; use [`EventSys::Default`] to let c-ares choose the best
323    /// option for the platform.
324    #[cfg(cares1_26)]
325    pub fn set_event_thread(&mut self, evsys: EventSys) -> &mut Self {
326        self.ares_options.evsys = evsys.into();
327        self.optmask |= c_ares_sys::ARES_OPT_EVENT_THREAD;
328        self
329    }
330}
331
332unsafe impl Send for Options {}
333unsafe impl Sync for Options {}
334
335#[cfg(test)]
336mod tests {
337    use super::super::Channel;
338    use super::*;
339
340    #[test]
341    fn options_default() {
342        let _options = Options::new();
343    }
344
345    #[test]
346    fn options_set_flags() {
347        let mut options = Options::new();
348        options.set_flags(Flags::USEVC | Flags::STAYOPEN);
349    }
350
351    #[test]
352    fn options_set_timeout() {
353        let mut options = Options::new();
354        options.set_timeout(Duration::from_secs(5));
355    }
356
357    #[test]
358    fn options_set_tries() {
359        let mut options = Options::new();
360        options.set_tries(5);
361    }
362
363    #[test]
364    fn options_set_ndots() {
365        let mut options = Options::new();
366        options.set_ndots(2);
367    }
368
369    #[test]
370    fn options_set_ports() {
371        let mut options = Options::new();
372        options.set_udp_port(53);
373        options.set_tcp_port(53);
374    }
375
376    #[test]
377    fn options_set_domains() {
378        let mut options = Options::new();
379        options.set_domains(["example.com", "test.local"]).unwrap();
380    }
381
382    #[test]
383    fn options_set_domains_owned_strings() {
384        let mut options = Options::new();
385        let domains: Vec<String> = vec!["example.com".into(), "test.local".into()];
386        options.set_domains(domains).unwrap();
387    }
388
389    #[test]
390    fn options_set_lookups() {
391        let mut options = Options::new();
392        options.set_lookups("bf").unwrap();
393    }
394
395    #[test]
396    fn options_set_socket_state_callback() {
397        let mut options = Options::new();
398        options.set_socket_state_callback(|_socket, _read, _write| {});
399    }
400
401    #[test]
402    fn options_set_sock_buffer_sizes() {
403        let mut options = Options::new();
404        options.set_sock_send_buffer_size(65536);
405        options.set_sock_receive_buffer_size(65536);
406    }
407
408    #[test]
409    fn options_set_rotate() {
410        let mut options = Options::new();
411        options.set_rotate();
412    }
413
414    #[test]
415    fn options_set_no_rotate() {
416        let mut options = Options::new();
417        options.set_no_rotate();
418    }
419
420    #[test]
421    fn options_set_ednspsz() {
422        let mut options = Options::new();
423        options.set_ednspsz(4096);
424    }
425
426    #[test]
427    fn options_set_resolvconf_path() {
428        let mut options = Options::new();
429        options.set_resolvconf_path("/etc/resolv.conf").unwrap();
430    }
431
432    #[cfg(cares1_19)]
433    #[test]
434    fn options_set_hosts_path() {
435        let mut options = Options::new();
436        options.set_hosts_path("/etc/hosts").unwrap();
437    }
438
439    #[cfg(cares1_20)]
440    #[test]
441    fn options_set_udp_max_queries() {
442        let mut options = Options::new();
443        options.set_udp_max_queries(Some(100));
444    }
445
446    #[cfg(cares1_20)]
447    #[test]
448    fn options_set_udp_max_queries_none() {
449        let mut options = Options::new();
450        options.set_udp_max_queries(None);
451    }
452
453    #[cfg(cares1_22)]
454    #[test]
455    fn options_set_max_timeout() {
456        let mut options = Options::new();
457        options.set_max_timeout(Duration::from_secs(30));
458    }
459
460    #[cfg(cares1_23)]
461    #[test]
462    fn options_set_query_cache_max_ttl() {
463        let mut options = Options::new();
464        options.set_query_cache_max_ttl(3600);
465    }
466
467    #[cfg(cares1_29)]
468    #[test]
469    fn options_set_server_failover_options() {
470        let mut options = Options::new();
471        let mut failover_opts = ServerFailoverOptions::new();
472        failover_opts
473            .set_retry_chance(5)
474            .set_retry_delay(Duration::from_secs(10));
475        options.set_server_failover_options(&failover_opts);
476    }
477
478    #[test]
479    fn options_builder_chain() {
480        let mut options = Options::new();
481        options
482            .set_flags(Flags::USEVC)
483            .set_timeout(Duration::from_secs(3))
484            .set_tries(2)
485            .set_ndots(1)
486            .set_udp_port(53)
487            .set_tcp_port(53)
488            .set_lookups("b")
489            .unwrap();
490    }
491
492    #[test]
493    fn options_full_builder_chain() {
494        let mut options = Options::new();
495        options
496            .set_flags(Flags::USEVC | Flags::STAYOPEN)
497            .set_timeout(Duration::from_secs(2))
498            .set_tries(3)
499            .set_ndots(2)
500            .set_udp_port(53)
501            .set_tcp_port(53)
502            .set_domains(["example.com"])
503            .unwrap()
504            .set_lookups("b")
505            .unwrap()
506            .set_sock_send_buffer_size(32768)
507            .set_sock_receive_buffer_size(32768)
508            .set_rotate()
509            .set_ednspsz(4096)
510            .set_resolvconf_path("/etc/resolv.conf")
511            .unwrap();
512        let channel = Channel::with_options(options);
513        assert!(channel.is_ok());
514    }
515
516    #[test]
517    fn options_is_send() {
518        fn assert_send<T: Send>() {}
519        assert_send::<Options>();
520    }
521
522    #[test]
523    fn options_is_sync() {
524        fn assert_sync<T: Sync>() {}
525        assert_sync::<Options>();
526    }
527
528    #[test]
529    fn options_with_socket_callback_creates_channel() {
530        let mut options = Options::new();
531        options.set_socket_state_callback(|_socket, _read, _write| {
532            // This callback would be invoked when socket state changes
533        });
534        let channel = Channel::with_options(options);
535        assert!(channel.is_ok());
536    }
537
538    #[cfg(cares1_19)]
539    #[test]
540    fn options_hosts_path_creates_channel() {
541        let mut options = Options::new();
542        options.set_hosts_path("/etc/hosts").unwrap();
543        let channel = Channel::with_options(options);
544        assert!(channel.is_ok());
545    }
546
547    #[test]
548    fn debug_options() {
549        let options = Options::new();
550        let debug = format!("{options:?}");
551        assert!(debug.contains("Options"));
552    }
553
554    #[test]
555    #[cfg(cares1_29)]
556    fn debug_server_failover_options() {
557        let opts = ServerFailoverOptions::new();
558        let debug = format!("{opts:?}");
559        assert!(debug.contains("ServerFailoverOptions"));
560    }
561
562    #[cfg(cares1_26)]
563    #[test]
564    fn options_set_event_thread() {
565        let mut options = Options::new();
566        options.set_event_thread(EventSys::Default);
567    }
568
569    #[cfg(cares1_26)]
570    #[test]
571    fn options_set_event_thread_creates_channel() {
572        // Only works if c-ares was built with thread safety.
573        if !crate::thread_safety() {
574            return;
575        }
576        let mut options = Options::new();
577        options.set_event_thread(EventSys::Default);
578        let channel = Channel::with_options(options);
579        assert!(channel.is_ok());
580    }
581}