Skip to main content

c_ares_resolver/
blockingresolver.rs

1use std::fmt;
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
3
4use crate::error::Error;
5use crate::host::HostResults;
6use crate::nameinfo::NameInfoResult;
7use crate::resolver::{Options, Resolver};
8
9#[cfg(cares1_29)]
10use c_ares::ServerStateFlags;
11
12use std::sync::mpsc;
13
14/// A blocking DNS resolver.
15///
16/// The `c-ares` library returns results via callbacks, and some of those callbacks receive
17/// borrowed data.  To return owned results, this resolver must clone or copy where the
18/// callback-based `Resolver` need not.
19///
20/// Therefore: if you are trying very hard to avoid unnecessary allocations - prefer the
21/// `Resolver`.
22pub struct BlockingResolver {
23    inner: Resolver,
24}
25
26impl fmt::Debug for BlockingResolver {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.debug_struct("BlockingResolver").finish_non_exhaustive()
29    }
30}
31
32// Most query implementations follow the same pattern: call through to the `Resolver`, arranging
33// that the callback sends the result down a channel.
34macro_rules! blockify {
35    ($resolver:expr, $query:ident, $($arg:expr),+ $(,)?) => {{
36        let (tx, rx) = mpsc::sync_channel(1);
37        $resolver.$query($($arg,)+ move |result| {
38            let _ = tx.send(result);
39        });
40        rx.recv().unwrap()
41    }};
42}
43
44impl BlockingResolver {
45    /// Create a new `BlockingResolver`, using default `Options`.
46    ///
47    /// # Examples
48    ///
49    /// ```no_run
50    /// let resolver = c_ares_resolver::BlockingResolver::new().unwrap();
51    /// match resolver.query_a("example.com") {
52    ///     Ok(results) => {
53    ///         for result in &results {
54    ///             println!("{}: TTL {}", result.ipv4(), result.ttl());
55    ///         }
56    ///     }
57    ///     Err(e) => eprintln!("Query failed: {e}"),
58    /// }
59    /// ```
60    pub fn new() -> Result<Self, Error> {
61        let options = Options::default();
62        Self::with_options(options)
63    }
64
65    /// Create a new `BlockingResolver`, with the given `Options`.
66    pub fn with_options(options: Options) -> Result<Self, Error> {
67        let inner = Resolver::with_options(options)?;
68        let resolver = Self { inner };
69        Ok(resolver)
70    }
71
72    /// Reinitialize a channel from system configuration.
73    #[cfg(cares1_22)]
74    pub fn reinit(&self) -> c_ares::Result<&Self> {
75        self.inner.reinit()?;
76        Ok(self)
77    }
78
79    /// Set the list of servers to contact, instead of the servers specified in resolv.conf or the
80    /// local named.
81    ///
82    /// String format is `host[:port]`.  IPv6 addresses with ports require square brackets eg
83    /// `[2001:4860:4860::8888]:53`.
84    pub fn set_servers<I, S>(&self, servers: I) -> c_ares::Result<&Self>
85    where
86        I: IntoIterator<Item = S>,
87        S: AsRef<str>,
88    {
89        self.inner.set_servers(servers)?;
90        Ok(self)
91    }
92
93    /// Retrieves the list of configured servers.
94    ///
95    /// Each entry is in `host[:port]` format, matching what [`set_servers`](Self::set_servers)
96    /// accepts.
97    #[cfg(cares1_24)]
98    pub fn servers(&self) -> Vec<String> {
99        self.inner.servers()
100    }
101
102    /// Set the local IPv4 address from which to make queries.
103    pub fn set_local_ipv4(&self, ipv4: Ipv4Addr) -> &Self {
104        self.inner.set_local_ipv4(ipv4);
105        self
106    }
107
108    /// Set the local IPv6 address from which to make queries.
109    pub fn set_local_ipv6(&self, ipv6: Ipv6Addr) -> &Self {
110        self.inner.set_local_ipv6(ipv6);
111        self
112    }
113
114    /// Set the local device from which to make queries.
115    pub fn set_local_device(&self, device: &str) -> c_ares::Result<&Self> {
116        self.inner.set_local_device(device)?;
117        Ok(self)
118    }
119
120    /// Initializes an address sortlist configuration, so that addresses returned by
121    /// `get_host_by_name()` are sorted according to the sortlist.
122    ///
123    /// Each element of the sortlist holds an IP-address/netmask pair. The netmask is optional but
124    /// follows the address after a slash if present. For example: "130.155.160.0/255.255.240.0",
125    /// or "130.155.0.0".
126    pub fn set_sortlist<I, S>(&self, sortlist: I) -> c_ares::Result<&Self>
127    where
128        I: IntoIterator<Item = S>,
129        S: AsRef<str>,
130    {
131        self.inner.set_sortlist(sortlist)?;
132        Ok(self)
133    }
134
135    /// Set a callback function to be invoked whenever a query on the channel completes.
136    ///
137    /// `callback(server, success, flags)` will be called when a query completes.
138    ///
139    /// - `server` indicates the DNS server that was used for the query.
140    /// - `success` indicates whether the query succeeded or not.
141    /// - `flags` is a bitmask of flags describing various aspects of the query.
142    #[cfg(cares1_29)]
143    pub fn set_server_state_callback<F>(&self, callback: F) -> &Self
144    where
145        F: Fn(&str, bool, ServerStateFlags) + Send + Sync + 'static,
146    {
147        self.inner.set_server_state_callback(callback);
148        self
149    }
150
151    /// Look up the A records associated with `name`.
152    pub fn query_a(&self, name: &str) -> c_ares::Result<c_ares::AResults> {
153        blockify!(self.inner, query_a, name)
154    }
155
156    /// Search for the A records associated with `name`.
157    pub fn search_a(&self, name: &str) -> c_ares::Result<c_ares::AResults> {
158        blockify!(self.inner, search_a, name)
159    }
160
161    /// Look up the AAAA records associated with `name`.
162    pub fn query_aaaa(&self, name: &str) -> c_ares::Result<c_ares::AAAAResults> {
163        blockify!(self.inner, query_aaaa, name)
164    }
165
166    /// Search for the AAAA records associated with `name`.
167    pub fn search_aaaa(&self, name: &str) -> c_ares::Result<c_ares::AAAAResults> {
168        blockify!(self.inner, search_aaaa, name)
169    }
170
171    /// Look up the CAA records associated with `name`.
172    pub fn query_caa(&self, name: &str) -> c_ares::Result<c_ares::CAAResults> {
173        blockify!(self.inner, query_caa, name)
174    }
175
176    /// Search for the CAA records associated with `name`.
177    pub fn search_caa(&self, name: &str) -> c_ares::Result<c_ares::CAAResults> {
178        blockify!(self.inner, search_caa, name)
179    }
180
181    /// Look up the CNAME records associated with `name`.
182    pub fn query_cname(&self, name: &str) -> c_ares::Result<c_ares::CNameResults> {
183        blockify!(self.inner, query_cname, name)
184    }
185
186    /// Search for the CNAME records associated with `name`.
187    pub fn search_cname(&self, name: &str) -> c_ares::Result<c_ares::CNameResults> {
188        blockify!(self.inner, search_cname, name)
189    }
190
191    /// Look up the MX records associated with `name`.
192    pub fn query_mx(&self, name: &str) -> c_ares::Result<c_ares::MXResults> {
193        blockify!(self.inner, query_mx, name)
194    }
195
196    /// Search for the MX records associated with `name`.
197    pub fn search_mx(&self, name: &str) -> c_ares::Result<c_ares::MXResults> {
198        blockify!(self.inner, search_mx, name)
199    }
200
201    /// Look up the NAPTR records associated with `name`.
202    pub fn query_naptr(&self, name: &str) -> c_ares::Result<c_ares::NAPTRResults> {
203        blockify!(self.inner, query_naptr, name)
204    }
205
206    /// Search for the NAPTR records associated with `name`.
207    pub fn search_naptr(&self, name: &str) -> c_ares::Result<c_ares::NAPTRResults> {
208        blockify!(self.inner, search_naptr, name)
209    }
210
211    /// Look up the NS records associated with `name`.
212    pub fn query_ns(&self, name: &str) -> c_ares::Result<c_ares::NSResults> {
213        blockify!(self.inner, query_ns, name)
214    }
215
216    /// Search for the NS records associated with `name`.
217    pub fn search_ns(&self, name: &str) -> c_ares::Result<c_ares::NSResults> {
218        blockify!(self.inner, search_ns, name)
219    }
220
221    /// Look up the PTR records associated with `name`.
222    pub fn query_ptr(&self, name: &str) -> c_ares::Result<c_ares::PTRResults> {
223        blockify!(self.inner, query_ptr, name)
224    }
225
226    /// Search for the PTR records associated with `name`.
227    pub fn search_ptr(&self, name: &str) -> c_ares::Result<c_ares::PTRResults> {
228        blockify!(self.inner, search_ptr, name)
229    }
230
231    /// Look up the SOA records associated with `name`.
232    pub fn query_soa(&self, name: &str) -> c_ares::Result<c_ares::SOAResult> {
233        blockify!(self.inner, query_soa, name)
234    }
235
236    /// Search for the SOA records associated with `name`.
237    pub fn search_soa(&self, name: &str) -> c_ares::Result<c_ares::SOAResult> {
238        blockify!(self.inner, search_soa, name)
239    }
240
241    /// Look up the SRV records associated with `name`.
242    pub fn query_srv(&self, name: &str) -> c_ares::Result<c_ares::SRVResults> {
243        blockify!(self.inner, query_srv, name)
244    }
245
246    /// Search for the SRV records associated with `name`.
247    pub fn search_srv(&self, name: &str) -> c_ares::Result<c_ares::SRVResults> {
248        blockify!(self.inner, search_srv, name)
249    }
250
251    /// Look up the TXT records associated with `name`.
252    pub fn query_txt(&self, name: &str) -> c_ares::Result<c_ares::TXTResults> {
253        blockify!(self.inner, query_txt, name)
254    }
255
256    /// Search for the TXT records associated with `name`.
257    pub fn search_txt(&self, name: &str) -> c_ares::Result<c_ares::TXTResults> {
258        blockify!(self.inner, search_txt, name)
259    }
260
261    /// Look up the URI records associated with `name`.
262    pub fn query_uri(&self, name: &str) -> c_ares::Result<c_ares::URIResults> {
263        blockify!(self.inner, query_uri, name)
264    }
265
266    /// Search for the URI records associated with `name`.
267    pub fn search_uri(&self, name: &str) -> c_ares::Result<c_ares::URIResults> {
268        blockify!(self.inner, search_uri, name)
269    }
270
271    /// Perform a host query by address.
272    pub fn get_host_by_address(&self, address: &IpAddr) -> c_ares::Result<HostResults> {
273        let (tx, rx) = mpsc::sync_channel(1);
274        self.inner.get_host_by_address(address, move |result| {
275            let _ = tx.send(result.map(Into::into));
276        });
277        rx.recv().unwrap()
278    }
279
280    /// Perform a host query by name.
281    pub fn get_host_by_name(
282        &self,
283        name: &str,
284        family: c_ares::AddressFamily,
285    ) -> c_ares::Result<HostResults> {
286        let (tx, rx) = mpsc::sync_channel(1);
287        self.inner.get_host_by_name(name, family, move |result| {
288            let _ = tx.send(result.map(Into::into));
289        });
290        rx.recv().unwrap()
291    }
292
293    /// Address-to-nodename translation in protocol-independent manner.
294    pub fn get_name_info(
295        &self,
296        address: &SocketAddr,
297        flags: c_ares::NIFlags,
298    ) -> c_ares::Result<NameInfoResult> {
299        let (tx, rx) = mpsc::sync_channel(1);
300        self.inner.get_name_info(address, flags, move |result| {
301            let _ = tx.send(result.map(Into::into));
302        });
303        rx.recv().unwrap()
304    }
305
306    /// Initiate a host query by name and service.
307    pub fn get_addrinfo(
308        &self,
309        name: &str,
310        service: Option<&str>,
311        hints: &c_ares::AddrInfoHints,
312    ) -> c_ares::Result<c_ares::AddrInfoResults> {
313        blockify!(self.inner, get_addrinfo, name, service, hints)
314    }
315
316    /// Initiate a single-question DNS query for `name`.  The class and type of the query are per
317    /// the provided parameters, taking values as defined in `arpa/nameser.h`.
318    ///
319    /// This method is provided so that users can query DNS types for which `c-ares` does not
320    /// provide a parser; or in case a third-party parser is preferred.  Usually, if a suitable
321    /// `query_xxx()` is available, that should be used.
322    pub fn query(&self, name: &str, dns_class: u16, query_type: u16) -> c_ares::Result<Vec<u8>> {
323        let (tx, rx) = mpsc::sync_channel(1);
324        self.inner
325            .query(name, dns_class, query_type, move |result| {
326                let _ = tx.send(result.map(std::borrow::ToOwned::to_owned));
327            });
328        rx.recv().unwrap()
329    }
330
331    /// Initiate a series of single-question DNS queries for `name`.  The class and type of the
332    /// query are per the provided parameters, taking values as defined in `arpa/nameser.h`.
333    ///
334    /// This method is provided so that users can search DNS types for which `c-ares` does not
335    /// provide a parser; or in case a third-party parser is preferred.  Usually, if a suitable
336    /// `search_xxx()` is available, that should be used.
337    pub fn search(&self, name: &str, dns_class: u16, query_type: u16) -> c_ares::Result<Vec<u8>> {
338        let (tx, rx) = mpsc::sync_channel(1);
339        self.inner
340            .search(name, dns_class, query_type, move |result| {
341                let _ = tx.send(result.map(std::borrow::ToOwned::to_owned));
342            });
343        rx.recv().unwrap()
344    }
345
346    /// Send a DNS query using a pre-built [`c_ares::DnsRecord`].
347    ///
348    /// # Examples
349    ///
350    /// ```no_run
351    /// use c_ares::*;
352    ///
353    /// let resolver = c_ares_resolver::BlockingResolver::new().unwrap();
354    /// let mut query = DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).unwrap();
355    /// query.query_add("example.com", DnsRecordType::A, DnsCls::IN).unwrap();
356    /// let response = resolver.send_dnsrec(&query).unwrap();
357    /// for rr in response.rrs(DnsSection::Answer) {
358    ///     if let Some(addr) = rr.get_addr(DnsRrKey::A_ADDR) {
359    ///         println!("address: {addr}");
360    ///     }
361    /// }
362    /// ```
363    #[cfg(cares1_28)]
364    pub fn send_dnsrec(&self, dnsrec: &c_ares::DnsRecord) -> c_ares::Result<c_ares::DnsRecord> {
365        let (tx, rx) = mpsc::sync_channel(1);
366        self.inner.send_dnsrec(dnsrec, move |result| {
367            let _ = tx.send(result.and_then(c_ares::DnsRecord::try_clone));
368        })?;
369        rx.recv().unwrap()
370    }
371
372    /// Initiate a DNS query for `name` with the given class and type, receiving a parsed
373    /// [`c_ares::DnsRecord`].
374    ///
375    /// # Examples
376    ///
377    /// ```no_run
378    /// use c_ares::{DnsCls, DnsRecordType, DnsRrKey, DnsSection};
379    ///
380    /// let resolver = c_ares_resolver::BlockingResolver::new().unwrap();
381    /// let record = resolver
382    ///     .query_dnsrec("example.com", DnsCls::IN, DnsRecordType::A)
383    ///     .unwrap();
384    /// for rr in record.rrs(DnsSection::Answer) {
385    ///     if let Some(addr) = rr.get_addr(DnsRrKey::A_ADDR) {
386    ///         println!("address: {addr}");
387    ///     }
388    /// }
389    /// ```
390    #[cfg(cares1_28)]
391    pub fn query_dnsrec(
392        &self,
393        name: &str,
394        dns_class: c_ares::DnsCls,
395        query_type: c_ares::DnsRecordType,
396    ) -> c_ares::Result<c_ares::DnsRecord> {
397        let (tx, rx) = mpsc::sync_channel(1);
398        self.inner
399            .query_dnsrec(name, dns_class, query_type, move |result| {
400                let _ = tx.send(result.and_then(c_ares::DnsRecord::try_clone));
401            })?;
402        rx.recv().unwrap()
403    }
404
405    /// Initiate a series of DNS queries using a pre-built [`c_ares::DnsRecord`], receiving a
406    /// parsed [`c_ares::DnsRecord`].
407    ///
408    /// # Examples
409    ///
410    /// ```no_run
411    /// use c_ares::*;
412    ///
413    /// let resolver = c_ares_resolver::BlockingResolver::new().unwrap();
414    /// let mut query = DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).unwrap();
415    /// query.query_add("example.com", DnsRecordType::A, DnsCls::IN).unwrap();
416    /// let response = resolver.search_dnsrec(&query).unwrap();
417    /// for rr in response.rrs(DnsSection::Answer) {
418    ///     if let Some(addr) = rr.get_addr(DnsRrKey::A_ADDR) {
419    ///         println!("address: {addr}");
420    ///     }
421    /// }
422    /// ```
423    #[cfg(cares1_28)]
424    pub fn search_dnsrec(&self, dnsrec: &c_ares::DnsRecord) -> c_ares::Result<c_ares::DnsRecord> {
425        let (tx, rx) = mpsc::sync_channel(1);
426        self.inner.search_dnsrec(dnsrec, move |result| {
427            let _ = tx.send(result.and_then(c_ares::DnsRecord::try_clone));
428        })?;
429        rx.recv().unwrap()
430    }
431
432    /// Block until notified that there are no longer any queries in queue, or the specified
433    /// timeout has expired.
434    ///
435    /// Pass `None` to wait indefinitely.
436    #[cfg(cares1_27)]
437    pub fn queue_wait_empty(&self, timeout: Option<std::time::Duration>) -> c_ares::Result<()> {
438        self.inner.queue_wait_empty(timeout)
439    }
440
441    /// Retrieve the total number of active queries pending answers from servers.
442    #[cfg(cares1_27)]
443    pub fn queue_active_queries(&self) -> usize {
444        self.inner.queue_active_queries()
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use super::*;
451    use std::time::Duration;
452
453    fn assert_send<T: Send>() {}
454    fn assert_sync<T: Sync>() {}
455
456    #[test]
457    fn blocking_resolver_is_send() {
458        assert_send::<BlockingResolver>();
459    }
460
461    #[test]
462    fn blocking_resolver_is_sync() {
463        assert_sync::<BlockingResolver>();
464    }
465
466    #[test]
467    fn blocking_resolver_new() {
468        let resolver = BlockingResolver::new();
469        assert!(resolver.is_ok());
470    }
471
472    #[test]
473    fn blocking_resolver_with_options() {
474        let options = Options::new();
475        let resolver = BlockingResolver::with_options(options);
476        assert!(resolver.is_ok());
477    }
478
479    #[test]
480    fn blocking_resolver_with_custom_options() {
481        let mut options = Options::new();
482        options.set_timeout(Duration::from_secs(2)).set_tries(2);
483        let resolver = BlockingResolver::with_options(options);
484        assert!(resolver.is_ok());
485    }
486
487    #[test]
488    fn blocking_resolver_set_local_ipv4() {
489        let resolver = BlockingResolver::new().unwrap();
490        let result = resolver.set_local_ipv4(Ipv4Addr::LOCALHOST);
491        assert!(std::ptr::eq(result, &raw const resolver));
492    }
493
494    #[test]
495    fn blocking_resolver_set_local_ipv6() {
496        let resolver = BlockingResolver::new().unwrap();
497        let ipv6 = Ipv6Addr::LOCALHOST;
498        let result = resolver.set_local_ipv6(ipv6);
499        assert!(std::ptr::eq(result, &raw const resolver));
500    }
501
502    #[test]
503    fn blocking_resolver_set_local_device() {
504        let resolver = BlockingResolver::new().unwrap();
505        let result = resolver.set_local_device("lo").unwrap();
506        assert!(std::ptr::eq(result, &raw const resolver));
507    }
508
509    #[test]
510    fn blocking_resolver_set_servers_valid() {
511        let resolver = BlockingResolver::new().unwrap();
512        let result = resolver.set_servers(["8.8.8.8", "8.8.4.4"]);
513        assert!(result.is_ok());
514    }
515
516    #[test]
517    fn blocking_resolver_set_sortlist_valid() {
518        let resolver = BlockingResolver::new().unwrap();
519        let result = resolver.set_sortlist(["130.155.160.0/255.255.240.0"]);
520        assert!(result.is_ok());
521    }
522
523    #[test]
524    #[cfg(cares1_22)]
525    fn blocking_resolver_reinit() {
526        let resolver = BlockingResolver::new().unwrap();
527        let result = resolver.reinit();
528        assert!(result.is_ok());
529    }
530
531    #[test]
532    #[cfg(cares1_24)]
533    fn blocking_resolver_servers() {
534        let resolver = BlockingResolver::new().unwrap();
535        let _ = resolver.set_servers(["8.8.8.8"]);
536        let servers = resolver.servers();
537        assert!(!servers.is_empty());
538    }
539
540    #[test]
541    #[cfg(cares1_27)]
542    fn blocking_resolver_queue_active_queries() {
543        let resolver = BlockingResolver::new().unwrap();
544        assert_eq!(resolver.queue_active_queries(), 0);
545    }
546
547    #[test]
548    #[cfg(cares1_27)]
549    fn blocking_resolver_queue_wait_empty() {
550        let resolver = BlockingResolver::new().unwrap();
551        let result = resolver.queue_wait_empty(Some(std::time::Duration::ZERO));
552        assert!(result.is_ok() || result == Err(c_ares::Error::ENOTIMP));
553    }
554
555    #[test]
556    #[cfg(cares1_29)]
557    fn blocking_resolver_set_server_state_callback() {
558        let resolver = BlockingResolver::new().unwrap();
559        let result = resolver.set_server_state_callback(|_server, _success, _flags| {});
560        assert!(std::ptr::eq(result, &raw const resolver));
561    }
562
563    #[test]
564    fn debug_blocking_resolver() {
565        let resolver = BlockingResolver::new().unwrap();
566        let debug = format!("{resolver:?}");
567        assert!(debug.contains("BlockingResolver"));
568    }
569}