diff --git a/CHANGELOG.md b/CHANGELOG.md index 43df741..c679723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ This document describes the changes to smoltcp-nal between releases. +# [Unreleased] + +## Added +* Added support for `embedded_nal::Dns` traits + # [0.4.0] - 2023-07-21 ## Added diff --git a/Cargo.toml b/Cargo.toml index 3817073..8bf7320 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ features = ["wyrand"] [dependencies.smoltcp] version = "0.10" -features = ["medium-ethernet", "proto-ipv6", "socket-tcp", "socket-dhcpv4", "socket-udp"] +features = ["medium-ethernet", "proto-ipv6", "socket-tcp", "socket-dns", "socket-dhcpv4", "socket-udp"] default-features = false [dependencies.shared-bus] diff --git a/src/lib.rs b/src/lib.rs index 1100d07..d648d44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,8 @@ pub enum SmoltcpError { #[derive(Debug, Copy, Clone)] pub enum NetworkError { NoSocket, + DnsStart(smoltcp::socket::dns::StartQueryError), + DnsFailure, UdpConnectionFailure(smoltcp::socket::udp::BindError), TcpConnectionFailure(smoltcp::socket::tcp::ConnectError), TcpReadFailure(smoltcp::socket::tcp::RecvError), @@ -113,6 +115,8 @@ where device: Device, sockets: smoltcp::iface::SocketSet<'a>, dhcp_handle: Option, + dns_handle: Option, + dns_lookups: heapless::LinearMap, smoltcp::socket::dns::QueryHandle, 2>, unused_tcp_handles: Vec, unused_udp_handles: Vec, clock: Clock, @@ -152,6 +156,7 @@ where let mut unused_tcp_handles: Vec = Vec::new(); let mut unused_udp_handles: Vec = Vec::new(); let mut dhcp_handle: Option = None; + let mut dns_handle: Option = None; for (handle, socket) in sockets.iter() { match socket { @@ -164,6 +169,9 @@ where smoltcp::socket::Socket::Dhcpv4(_) => { dhcp_handle.replace(handle); } + smoltcp::socket::Socket::Dns(_) => { + dns_handle.replace(handle); + } // This branch may be enabled through cargo feature unification (e.g. if an // application enables raw-sockets). To accomodate this, we provide a default match @@ -178,9 +186,11 @@ where sockets, device, dhcp_handle, + dns_handle, unused_tcp_handles, unused_udp_handles, last_poll: None, + dns_lookups: heapless::LinearMap::new(), clock, stack_time: smoltcp::time::Instant::from_secs(0), rand: WyRand::new_seed(0), @@ -234,6 +244,7 @@ where // Service the DHCP client. if let Some(handle) = self.dhcp_handle { let mut close_sockets = false; + let mut dns_server = None; if let Some(event) = self.sockets.get_mut::(handle).poll() { match event { @@ -246,6 +257,15 @@ where Self::set_ipv4_addr(&mut self.network_interface, config.address); } + if let Some(server) = config + .dns_servers + .iter() + .next() + .map(|ipv4| smoltcp::wire::IpAddress::Ipv4(*ipv4)) + { + dns_server.replace(server); + } + if let Some(route) = config.router { // Note: If the user did not provide enough route storage, we may not be // able to store the gateway. @@ -274,6 +294,18 @@ where if close_sockets { self.close_sockets(); } + + if let Some((server, handle)) = dns_server.zip(self.dns_handle) { + let dns = self.sockets.get_mut::(handle); + + // Clear out all pending DNS queries now that we have a new server. + for (_query, handle) in self.dns_lookups.iter() { + dns.cancel_query(*handle); + } + self.dns_lookups.clear(); + + dns.update_servers(&[server]); + } } Ok(updated) @@ -649,3 +681,58 @@ where .map_err(|e| embedded_nal::nb::Error::Other(NetworkError::UdpWriteFailure(e))) } } + +impl<'a, Device, Clock> embedded_nal::Dns for NetworkStack<'a, Device, Clock> +where + Device: smoltcp::phy::Device, + Clock: embedded_time::Clock, + u32: From, +{ + type Error = NetworkError; + fn get_host_by_name( + &mut self, + hostname: &str, + _addr_type: embedded_nal::AddrType, + ) -> embedded_nal::nb::Result { + let handle = self.dns_handle.ok_or(NetworkError::Unsupported)?; + let dns_socket: &mut smoltcp::socket::dns::Socket = self.sockets.get_mut(handle); + let context = self.network_interface.context(); + let key = heapless::String::try_from(hostname).map_err(|_| NetworkError::Unsupported)?; + + if let Some(handle) = self.dns_lookups.get(&key) { + match dns_socket.get_query_result(*handle) { + Ok(addrs) => { + self.dns_lookups.remove(&key); + let addr = addrs.iter().next().ok_or(NetworkError::DnsFailure)?; + let smoltcp::wire::IpAddress::Ipv4(addr) = addr else { + panic!("Unexpected address return type"); + }; + return Ok(embedded_nal::IpAddr::V4(addr.0.into())); + } + Err(smoltcp::socket::dns::GetQueryResultError::Pending) => {} + Err(smoltcp::socket::dns::GetQueryResultError::Failed) => { + self.dns_lookups.remove(&key); + return Err(embedded_nal::nb::Error::Other(NetworkError::DnsFailure)); + } + } + } else { + // Note: We only support A types because we are an Ipv4-only stack + let dns_query = dns_socket + .start_query(context, hostname, smoltcp::wire::DnsQueryType::A) + .map_err(NetworkError::DnsStart)?; + if self.dns_lookups.insert(key, dns_query).is_err() { + dns_socket.cancel_query(dns_query); + return Err(embedded_nal::nb::Error::Other(NetworkError::Unsupported)); + } + } + + Err(embedded_nal::nb::Error::WouldBlock) + } + + fn get_host_by_address( + &mut self, + _addr: embedded_nal::IpAddr, + ) -> embedded_nal::nb::Result, Self::Error> { + unimplemented!() + } +} diff --git a/src/shared.rs b/src/shared.rs index eae84ef..8172ec1 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -92,6 +92,15 @@ where forward! {send_to(socket: &mut S::UdpSocket, remote: embedded_nal::SocketAddr, buffer: &[u8]) -> embedded_nal::nb::Result<(), S::Error>} forward! {bind(socket: &mut S::UdpSocket, local_port: u16) -> Result<(), S::Error>} } +impl<'a, S> embedded_nal::Dns for NetworkStackProxy<'a, S> +where + S: embedded_nal::Dns, +{ + type Error = S::Error; + + forward! {get_host_by_name(hostname: &str, addr_type: embedded_nal::AddrType) -> embedded_nal::nb::Result} + forward! {get_host_by_address(addr: embedded_nal::IpAddr) -> embedded_nal::nb::Result, Self::Error>} +} impl<'a, Device, Clock> NetworkManager<'a, Device, Clock> where