nrf_modem/dns/
dns_blocking.rs

1use crate::{ip::NrfSockAddr, lte_link::LteLink, CancellationToken, Error};
2use arrayvec::ArrayString;
3use core::net::{IpAddr, SocketAddr};
4use core::str::FromStr;
5
6use super::{AddrType, DnsQuery};
7
8/// Get the IP address that corresponds to the given hostname.
9///
10/// The modem has an internal cache so this process may be really quick.
11/// If the hostname is not known internally or if it has expired, then it has to be requested from a DNS server.
12///
13/// While this function is async, the actual DNS bit is blocking because the modem sadly has no async API for this.
14///
15/// The modem API is capable of setting the dns server, but that's not yet implemented in this wrapper.
16pub async fn get_host_by_name(hostname: &str) -> Result<IpAddr, Error> {
17    resolve_dns(DnsQuery::new(hostname)).await
18}
19
20/// Get the IP address that corresponds to the given hostname.
21///
22/// The modem has an internal cache so this process may be really quick.
23/// If the hostname is not known internally or if it has expired, then it has to be requested from a DNS server.
24///
25/// While this function is async, the actual DNS bit is blocking because the modem sadly has no async API for this.
26///
27/// The modem API is capable of setting the dns server, but that's not yet implemented in this wrapper.
28pub async fn get_host_by_name_with_cancellation(
29    hostname: &str,
30    token: &CancellationToken,
31) -> Result<IpAddr, Error> {
32    resolve_dns_with_cancellation(DnsQuery::new(hostname), token).await
33}
34
35/// Resolve the DNS query.
36///
37/// The modem has an internal cache so this process may be really quick.
38/// If the hostname is not known internally or if it has expired, then it has to be requested from a DNS server.
39///
40/// While this function is async, the actual DNS bit is blocking because the modem sadly has no async API for this.
41///
42/// The modem API is capable of setting the dns server, but that's not yet implemented in this wrapper.
43pub async fn resolve_dns(query: DnsQuery<'_>) -> Result<IpAddr, Error> {
44    resolve_dns_with_cancellation(query, &Default::default()).await
45}
46
47/// Resolve the DNS query.
48///
49/// The modem has an internal cache so this process may be really quick.
50/// If the hostname is not known internally or if it has expired, then it has to be requested from a DNS server.
51///
52/// While this function is async, the actual DNS bit is blocking because the modem sadly has no async API for this.
53///
54/// The modem API is capable of setting the dns server, but that's not yet implemented in this wrapper.
55pub async fn resolve_dns_with_cancellation(
56    query: DnsQuery<'_>,
57    token: &CancellationToken,
58) -> Result<IpAddr, Error> {
59    #[cfg(feature = "defmt")]
60    defmt::debug!("Resolving dns hostname for \"{}\"", query.hostname());
61
62    // If we can parse the hostname as an IP address, then we can save a whole lot of trouble
63    if let Ok(ip) = query.hostname().parse() {
64        // Avoid replying with a different address type than requested.
65        if !query.addr_type().addr_matches(ip) {
66            return Err(Error::AddressNotFound);
67        } else {
68            return Ok(ip);
69        }
70    }
71
72    // The modem only deals with ascii
73    if !query.hostname().is_ascii() {
74        return Err(Error::HostnameNotAscii);
75    }
76
77    token.bind_to_current_task().await;
78
79    // Make sure we have a network connection
80    let link = LteLink::new().await?;
81    link.wait_for_link_with_cancellation(token).await?;
82
83    let mut found_ip = None;
84
85    unsafe {
86        let hints = nrfxlib_sys::nrf_addrinfo {
87            ai_family: match query.addr_type() {
88                AddrType::Any => nrfxlib_sys::NRF_AF_UNSPEC as _,
89                AddrType::V4 => nrfxlib_sys::NRF_AF_INET as _,
90                AddrType::V6 => nrfxlib_sys::NRF_AF_INET6 as _,
91            },
92            ai_socktype: nrfxlib_sys::NRF_SOCK_STREAM as _,
93
94            ai_flags: 0,
95            ai_protocol: 0,
96            ai_addrlen: 0,
97            ai_addr: core::ptr::null_mut(),
98            ai_canonname: core::ptr::null_mut(),
99            ai_next: core::ptr::null_mut(),
100        };
101
102        let mut result: *mut nrfxlib_sys::nrf_addrinfo = core::ptr::null_mut();
103
104        // A hostname should at most be 256 chars, but we have a null char as well, so we add one
105        let mut hostname =
106            ArrayString::<257>::from_str(query.hostname()).map_err(|_| Error::HostnameTooLong)?;
107        hostname.push('\0');
108
109        let err = nrfxlib_sys::nrf_getaddrinfo(
110            hostname.as_ptr() as *const core::ffi::c_char,
111            core::ptr::null(),
112            &hints as *const _,
113            &mut result as *mut *mut _,
114        ) as isize;
115
116        let deactivation_result = link.deactivate().await;
117
118        if err > 0 {
119            return Err(Error::NrfError(err));
120        } else if err == -1 {
121            return Err(Error::NrfError(crate::ffi::get_last_error()));
122        }
123
124        if result.is_null() {
125            return Err(Error::AddressNotFound);
126        }
127
128        if let Err(deactivation_error) = deactivation_result {
129            nrfxlib_sys::nrf_freeaddrinfo(result);
130            return Err(deactivation_error);
131        }
132
133        let mut result_iter = result;
134
135        while !result_iter.is_null() && found_ip.is_none() {
136            let address = (*result_iter).ai_addr;
137
138            if (*address).sa_family == nrfxlib_sys::NRF_AF_INET as u16 {
139                let dns_addr: &nrfxlib_sys::nrf_sockaddr_in =
140                    &*(address as *const nrfxlib_sys::nrf_sockaddr_in);
141
142                let socket_addr: SocketAddr = NrfSockAddr::from(*dns_addr).into();
143                found_ip = Some(socket_addr.ip());
144            } else if (*address).sa_family == nrfxlib_sys::NRF_AF_INET6 as u16 {
145                let dns_addr: &nrfxlib_sys::nrf_sockaddr_in6 =
146                    &*(address as *const nrfxlib_sys::nrf_sockaddr_in6);
147
148                let socket_addr: SocketAddr = NrfSockAddr::from(*dns_addr).into();
149                found_ip = Some(socket_addr.ip());
150            }
151
152            result_iter = (*result_iter).ai_next;
153        }
154
155        // The addrinfo is allocated somewhere so we have to make sure to free it
156        nrfxlib_sys::nrf_freeaddrinfo(result);
157
158        if let Some(found_ip) = found_ip {
159            Ok(found_ip)
160        } else {
161            Err(Error::AddressNotFound)
162        }
163    }
164}