Skip to main content

c_ares/
addrinfo.rs

1use core::ffi::{c_int, c_void};
2use std::fmt;
3use std::net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6};
4
5use bitflags::bitflags;
6use itertools::Itertools;
7
8use crate::error::{Error, Result};
9use crate::panic;
10use crate::types::AddressFamily;
11use crate::utils::{hostname_as_str, ipv4_from_in_addr, ipv6_from_in6_addr, sockaddr_in6_scope_id};
12
13bitflags!(
14    /// Flags that may be passed in `AddrInfoHints`.
15    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16    pub struct AddrInfoFlags: i32 {
17        /// The `ares_addrinfo` structure will return canonical names list.
18        const CANONNAME = c_ares_sys::ARES_AI_CANONNAME;
19
20        /// The `name` parameter is a numeric host address string.
21        const NUMERICHOST = c_ares_sys::ARES_AI_NUMERICHOST;
22
23        /// Return socket addresses suitable for `bind()`.
24        const PASSIVE = c_ares_sys::ARES_AI_PASSIVE;
25
26        /// The `service` field will be treated as a numeric value.
27        const NUMERICSERV = c_ares_sys::ARES_AI_NUMERICSERV;
28
29        /// If `ai_family` is `AF_INET6`, return IPv4-mapped IPv6 addresses when no IPv6 addresses
30        /// are found.
31        const V4MAPPED = c_ares_sys::ARES_AI_V4MAPPED;
32
33        /// Used with `V4MAPPED`. Return both IPv6 and IPv4-mapped IPv6 addresses.
34        const ALL = c_ares_sys::ARES_AI_ALL;
35
36        /// Only return addresses if a non-loopback address of that family is configured.
37        const ADDRCONFIG = c_ares_sys::ARES_AI_ADDRCONFIG;
38
39        /// Result addresses will not be sorted and no connections to resolved addresses will be
40        /// attempted.
41        const NOSORT = c_ares_sys::ARES_AI_NOSORT;
42
43        /// Read hosts file path from the environment variable `CARES_HOSTS`.
44        const ENVHOSTS = c_ares_sys::ARES_AI_ENVHOSTS;
45    }
46);
47
48/// Hints for an `ares_getaddrinfo()` call, controlling the returned results.
49#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
50pub struct AddrInfoHints {
51    /// Flags controlling the query behaviour.
52    pub flags: AddrInfoFlags,
53    /// Desired address family (`INET`, `INET6`, or `UNSPEC`).
54    pub family: Option<AddressFamily>,
55    /// Desired socket type. Common values are `libc::SOCK_STREAM` (TCP) and `libc::SOCK_DGRAM`
56    /// (UDP). `0` means any type.
57    pub socktype: i32,
58    /// Desired protocol number. Common values are `libc::IPPROTO_TCP` and `libc::IPPROTO_UDP`.
59    /// `0` means any protocol.
60    pub protocol: i32,
61}
62
63impl Default for AddrInfoFlags {
64    fn default() -> Self {
65        Self::empty()
66    }
67}
68
69impl From<&AddrInfoHints> for c_ares_sys::ares_addrinfo_hints {
70    fn from(hints: &AddrInfoHints) -> Self {
71        c_ares_sys::ares_addrinfo_hints {
72            ai_flags: hints.flags.bits(),
73            ai_family: hints
74                .family
75                .map_or(c_types::AF_UNSPEC as c_int, |f| f as c_int),
76            ai_socktype: hints.socktype,
77            ai_protocol: hints.protocol,
78        }
79    }
80}
81
82/// The result of a successful `get_addrinfo()` call.
83///
84/// Owns the underlying `ares_addrinfo` pointer and frees it on drop via `ares_freeaddrinfo()`.
85pub struct AddrInfoResults {
86    addrinfo: *mut c_ares_sys::ares_addrinfo,
87}
88
89impl AddrInfoResults {
90    pub(crate) fn new(addrinfo: *mut c_ares_sys::ares_addrinfo) -> Self {
91        AddrInfoResults { addrinfo }
92    }
93
94    /// Returns the official name of the host.
95    pub fn name(&self) -> Option<&str> {
96        unsafe { (*self.addrinfo).name.as_ref() }.map(|name| unsafe { hostname_as_str(name) })
97    }
98
99    /// Returns an iterator over the address nodes in this result.
100    pub fn nodes(&self) -> AddrInfoNodeIter<'_> {
101        AddrInfoNodeIter {
102            next: unsafe { (*self.addrinfo).nodes.as_ref() },
103        }
104    }
105
106    /// Returns an iterator over the CNAME chain in this result.
107    pub fn cnames(&self) -> AddrInfoCNameIter<'_> {
108        AddrInfoCNameIter {
109            next: unsafe { (*self.addrinfo).cnames.as_ref() },
110        }
111    }
112}
113
114impl fmt::Display for AddrInfoResults {
115    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
116        if let Some(name) = self.name() {
117            write!(fmt, "Name: {name}, ")?;
118        }
119        let nodes = self.nodes().format(", ");
120        write!(fmt, "Nodes: [{nodes}]")?;
121        let cnames: Vec<_> = self.cnames().collect();
122        if !cnames.is_empty() {
123            let cnames = cnames.iter().format(", ");
124            write!(fmt, ", CNames: [{cnames}]")?;
125        }
126        Ok(())
127    }
128}
129
130impl fmt::Debug for AddrInfoResults {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        f.debug_struct("AddrInfoResults")
133            .field("name", &self.name())
134            .finish_non_exhaustive()
135    }
136}
137
138impl Drop for AddrInfoResults {
139    fn drop(&mut self) {
140        unsafe {
141            c_ares_sys::ares_freeaddrinfo(self.addrinfo);
142        }
143    }
144}
145
146unsafe impl Send for AddrInfoResults {}
147unsafe impl Sync for AddrInfoResults {}
148
149/// A single address node from an `AddrInfoResults`.
150#[derive(Clone, Copy)]
151pub struct AddrInfoNode<'a> {
152    node: &'a c_ares_sys::ares_addrinfo_node,
153}
154
155impl AddrInfoNode<'_> {
156    /// Returns the TTL for this address record.
157    pub fn ttl(&self) -> i32 {
158        self.node.ai_ttl
159    }
160
161    /// Returns the address family (`AF_INET` or `AF_INET6`).
162    pub fn family(&self) -> AddressFamily {
163        match self.node.ai_family as c_types::ADDRESS_FAMILY {
164            c_types::AF_INET => AddressFamily::INET,
165            c_types::AF_INET6 => AddressFamily::INET6,
166            _ => AddressFamily::UNSPEC,
167        }
168    }
169
170    /// Returns the socket type, e.g. `libc::SOCK_STREAM` (TCP) or `libc::SOCK_DGRAM` (UDP).
171    /// `0` if unspecified.
172    pub fn socktype(&self) -> i32 {
173        self.node.ai_socktype
174    }
175
176    /// Returns the protocol number, e.g. `libc::IPPROTO_TCP` or `libc::IPPROTO_UDP`.
177    /// `0` if unspecified.
178    pub fn protocol(&self) -> i32 {
179        self.node.ai_protocol
180    }
181
182    /// Returns the socket address (IP + port) for this node.
183    pub fn socket_addr(&self) -> Option<SocketAddr> {
184        let addr = self.node.ai_addr;
185        if addr.is_null() {
186            return None;
187        }
188        match self.node.ai_family as c_types::ADDRESS_FAMILY {
189            c_types::AF_INET => {
190                // c-ares allocates the sockaddr with proper alignment for the
191                // address family indicated by `ai_family`.
192                #[allow(clippy::cast_ptr_alignment)]
193                let sa = unsafe { &*(addr.cast::<c_types::sockaddr_in>()) };
194                let ip = ipv4_from_in_addr(sa.sin_addr);
195                let port = u16::from_be(sa.sin_port);
196                Some(SocketAddr::V4(SocketAddrV4::new(ip, port)))
197            }
198            c_types::AF_INET6 => {
199                // c-ares allocates the sockaddr with proper alignment for the
200                // address family indicated by `ai_family`.
201                #[allow(clippy::cast_ptr_alignment)]
202                let sa = unsafe { &*(addr.cast::<c_types::sockaddr_in6>()) };
203                let ip = ipv6_from_in6_addr(sa.sin6_addr);
204                let port = u16::from_be(sa.sin6_port);
205                let scope_id = sockaddr_in6_scope_id(sa);
206                Some(SocketAddr::V6(SocketAddrV6::new(
207                    ip,
208                    port,
209                    sa.sin6_flowinfo,
210                    scope_id,
211                )))
212            }
213            _ => None,
214        }
215    }
216
217    /// Returns the IP address for this node.
218    pub fn ip_addr(&self) -> Option<IpAddr> {
219        self.socket_addr().map(|sa| sa.ip())
220    }
221}
222
223impl fmt::Debug for AddrInfoNode<'_> {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        f.debug_struct("AddrInfoNode")
226            .field("ttl", &self.ttl())
227            .field("family", &self.family())
228            .field("socktype", &self.socktype())
229            .field("protocol", &self.protocol())
230            .field("socket_addr", &self.socket_addr())
231            .finish()
232    }
233}
234
235impl fmt::Display for AddrInfoNode<'_> {
236    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
237        if let Some(addr) = self.ip_addr() {
238            write!(fmt, "{addr}")?;
239        } else {
240            write!(fmt, "<unknown>")?;
241        }
242        write!(fmt, " (ttl: {})", self.ttl())
243    }
244}
245
246unsafe impl Send for AddrInfoNode<'_> {}
247unsafe impl Sync for AddrInfoNode<'_> {}
248
249/// Iterator over the address nodes in an `AddrInfoResults`.
250#[derive(Clone)]
251pub struct AddrInfoNodeIter<'a> {
252    next: Option<&'a c_ares_sys::ares_addrinfo_node>,
253}
254
255impl fmt::Debug for AddrInfoNodeIter<'_> {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        f.debug_struct("AddrInfoNodeIter").finish_non_exhaustive()
258    }
259}
260
261impl<'a> Iterator for AddrInfoNodeIter<'a> {
262    type Item = AddrInfoNode<'a>;
263
264    fn next(&mut self) -> Option<Self::Item> {
265        let opt_node = self.next;
266        self.next = opt_node.and_then(|node| unsafe { node.ai_next.as_ref() });
267        opt_node.map(|node| AddrInfoNode { node })
268    }
269}
270
271impl std::iter::FusedIterator for AddrInfoNodeIter<'_> {}
272
273unsafe impl Send for AddrInfoNodeIter<'_> {}
274unsafe impl Sync for AddrInfoNodeIter<'_> {}
275
276/// A single CNAME record from an `AddrInfoResults`.
277#[derive(Clone, Copy, Debug)]
278pub struct AddrInfoCName<'a> {
279    cname: &'a c_ares_sys::ares_addrinfo_cname,
280}
281
282impl AddrInfoCName<'_> {
283    /// Returns the TTL for this CNAME record.
284    pub fn ttl(&self) -> i32 {
285        self.cname.ttl
286    }
287
288    /// Returns the alias (the label of the CNAME resource record).
289    pub fn alias(&self) -> &str {
290        unsafe { hostname_as_str(self.cname.alias) }
291    }
292
293    /// Returns the canonical name (the value of the CNAME resource record).
294    pub fn name(&self) -> &str {
295        unsafe { hostname_as_str(self.cname.name) }
296    }
297}
298
299impl fmt::Display for AddrInfoCName<'_> {
300    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
301        write!(
302            fmt,
303            "{} -> {} (ttl: {})",
304            self.alias(),
305            self.name(),
306            self.ttl()
307        )
308    }
309}
310
311unsafe impl Send for AddrInfoCName<'_> {}
312unsafe impl Sync for AddrInfoCName<'_> {}
313
314/// Iterator over the CNAME chain in an `AddrInfoResults`.
315#[derive(Clone, Debug)]
316pub struct AddrInfoCNameIter<'a> {
317    next: Option<&'a c_ares_sys::ares_addrinfo_cname>,
318}
319
320impl<'a> Iterator for AddrInfoCNameIter<'a> {
321    type Item = AddrInfoCName<'a>;
322
323    fn next(&mut self) -> Option<Self::Item> {
324        let opt_cname = self.next;
325        self.next = opt_cname.and_then(|cname| unsafe { cname.next.as_ref() });
326        opt_cname.map(|cname| AddrInfoCName { cname })
327    }
328}
329
330impl std::iter::FusedIterator for AddrInfoCNameIter<'_> {}
331
332unsafe impl Send for AddrInfoCNameIter<'_> {}
333unsafe impl Sync for AddrInfoCNameIter<'_> {}
334
335pub(crate) unsafe extern "C" fn get_addrinfo_callback<F>(
336    arg: *mut c_void,
337    status: c_int,
338    _timeouts: c_int,
339    addrinfo: *mut c_ares_sys::ares_addrinfo,
340) where
341    F: FnOnce(Result<AddrInfoResults>) + Send + 'static,
342{
343    let result = if status == c_ares_sys::ares_status_t::ARES_SUCCESS as i32 {
344        Ok(AddrInfoResults::new(addrinfo))
345    } else {
346        Err(Error::from(status))
347    };
348    let handler = unsafe { Box::from_raw(arg.cast::<F>()) };
349    panic::abort_on_panic(|| handler(result));
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn is_send() {
358        fn assert_send<T: Send>() {}
359        assert_send::<AddrInfoResults>();
360        assert_send::<AddrInfoNode<'_>>();
361        assert_send::<AddrInfoNodeIter<'_>>();
362        assert_send::<AddrInfoCName<'_>>();
363        assert_send::<AddrInfoCNameIter<'_>>();
364    }
365
366    #[test]
367    fn is_sync() {
368        fn assert_sync<T: Sync>() {}
369        assert_sync::<AddrInfoResults>();
370        assert_sync::<AddrInfoNode<'_>>();
371        assert_sync::<AddrInfoNodeIter<'_>>();
372        assert_sync::<AddrInfoCName<'_>>();
373        assert_sync::<AddrInfoCNameIter<'_>>();
374    }
375
376    #[test]
377    fn hints_default() {
378        let hints = AddrInfoHints::default();
379        assert_eq!(hints.flags, AddrInfoFlags::empty());
380        assert_eq!(hints.family, None);
381        assert_eq!(hints.socktype, 0);
382        assert_eq!(hints.protocol, 0);
383    }
384
385    #[test]
386    fn hints_into_raw() {
387        let hints = AddrInfoHints {
388            flags: AddrInfoFlags::CANONNAME | AddrInfoFlags::NUMERICSERV,
389            family: Some(AddressFamily::INET),
390            socktype: 1,
391            protocol: 6,
392        };
393        let raw: c_ares_sys::ares_addrinfo_hints = (&hints).into();
394        assert_eq!(
395            raw.ai_flags,
396            (AddrInfoFlags::CANONNAME | AddrInfoFlags::NUMERICSERV).bits()
397        );
398        assert_eq!(raw.ai_family, AddressFamily::INET as c_int);
399        assert_eq!(raw.ai_socktype, 1);
400        assert_eq!(raw.ai_protocol, 6);
401    }
402
403    #[test]
404    fn hints_unspec_family() {
405        let hints = AddrInfoHints::default();
406        let raw: c_ares_sys::ares_addrinfo_hints = (&hints).into();
407        assert_eq!(raw.ai_family, c_types::AF_UNSPEC as c_int);
408    }
409
410    #[test]
411    fn flags_empty() {
412        let flags = AddrInfoFlags::empty();
413        assert!(flags.is_empty());
414    }
415
416    #[test]
417    fn flags_combine() {
418        let flags = AddrInfoFlags::CANONNAME | AddrInfoFlags::NOSORT;
419        assert!(flags.contains(AddrInfoFlags::CANONNAME));
420        assert!(flags.contains(AddrInfoFlags::NOSORT));
421        assert!(!flags.contains(AddrInfoFlags::PASSIVE));
422    }
423
424    #[test]
425    fn flags_debug() {
426        let flags = AddrInfoFlags::CANONNAME | AddrInfoFlags::NUMERICHOST;
427        let debug = format!("{flags:?}");
428        assert!(!debug.is_empty());
429    }
430}