914 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			914 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| #![cfg_attr(not(feature = "std"), no_std)]
 | |
| #![allow(async_fn_in_trait)]
 | |
| #![warn(missing_docs)]
 | |
| #![doc = include_str!("../README.md")]
 | |
| 
 | |
| //! ## Feature flags
 | |
| #![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
 | |
| 
 | |
| #[cfg(not(any(feature = "proto-ipv4", feature = "proto-ipv6")))]
 | |
| compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6");
 | |
| 
 | |
| // This mod MUST go first, so that the others see its macros.
 | |
| pub(crate) mod fmt;
 | |
| 
 | |
| mod device;
 | |
| #[cfg(feature = "dns")]
 | |
| pub mod dns;
 | |
| #[cfg(feature = "tcp")]
 | |
| pub mod tcp;
 | |
| mod time;
 | |
| #[cfg(feature = "udp")]
 | |
| pub mod udp;
 | |
| 
 | |
| use core::cell::RefCell;
 | |
| use core::future::{poll_fn, Future};
 | |
| use core::task::{Context, Poll};
 | |
| 
 | |
| pub use embassy_net_driver as driver;
 | |
| use embassy_net_driver::{Driver, LinkState};
 | |
| use embassy_sync::waitqueue::WakerRegistration;
 | |
| use embassy_time::{Instant, Timer};
 | |
| use futures::pin_mut;
 | |
| #[allow(unused_imports)]
 | |
| use heapless::Vec;
 | |
| #[cfg(feature = "igmp")]
 | |
| pub use smoltcp::iface::MulticastError;
 | |
| #[allow(unused_imports)]
 | |
| use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
 | |
| use smoltcp::phy::Medium;
 | |
| #[cfg(feature = "dhcpv4")]
 | |
| use smoltcp::socket::dhcpv4::{self, RetryConfig};
 | |
| #[cfg(feature = "medium-ethernet")]
 | |
| pub use smoltcp::wire::EthernetAddress;
 | |
| #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154", feature = "medium-ip"))]
 | |
| pub use smoltcp::wire::HardwareAddress;
 | |
| #[cfg(any(feature = "udp", feature = "tcp"))]
 | |
| pub use smoltcp::wire::IpListenEndpoint;
 | |
| #[cfg(feature = "medium-ieee802154")]
 | |
| pub use smoltcp::wire::{Ieee802154Address, Ieee802154Frame};
 | |
| pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint};
 | |
| #[cfg(feature = "proto-ipv4")]
 | |
| pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
 | |
| #[cfg(feature = "proto-ipv6")]
 | |
| pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
 | |
| 
 | |
| use crate::device::DriverAdapter;
 | |
| use crate::time::{instant_from_smoltcp, instant_to_smoltcp};
 | |
| 
 | |
| const LOCAL_PORT_MIN: u16 = 1025;
 | |
| const LOCAL_PORT_MAX: u16 = 65535;
 | |
| #[cfg(feature = "dns")]
 | |
| const MAX_QUERIES: usize = 4;
 | |
| #[cfg(feature = "dhcpv4-hostname")]
 | |
| const MAX_HOSTNAME_LEN: usize = 32;
 | |
| 
 | |
| /// Memory resources needed for a network stack.
 | |
| pub struct StackResources<const SOCK: usize> {
 | |
|     sockets: [SocketStorage<'static>; SOCK],
 | |
|     #[cfg(feature = "dns")]
 | |
|     queries: [Option<dns::DnsQuery>; MAX_QUERIES],
 | |
|     #[cfg(feature = "dhcpv4-hostname")]
 | |
|     hostname: core::cell::UnsafeCell<HostnameResources>,
 | |
| }
 | |
| 
 | |
| #[cfg(feature = "dhcpv4-hostname")]
 | |
| struct HostnameResources {
 | |
|     option: smoltcp::wire::DhcpOption<'static>,
 | |
|     data: [u8; MAX_HOSTNAME_LEN],
 | |
| }
 | |
| 
 | |
| impl<const SOCK: usize> StackResources<SOCK> {
 | |
|     /// Create a new set of stack resources.
 | |
|     pub const fn new() -> Self {
 | |
|         #[cfg(feature = "dns")]
 | |
|         const INIT: Option<dns::DnsQuery> = None;
 | |
|         Self {
 | |
|             sockets: [SocketStorage::EMPTY; SOCK],
 | |
|             #[cfg(feature = "dns")]
 | |
|             queries: [INIT; MAX_QUERIES],
 | |
|             #[cfg(feature = "dhcpv4-hostname")]
 | |
|             hostname: core::cell::UnsafeCell::new(HostnameResources {
 | |
|                 option: smoltcp::wire::DhcpOption { kind: 0, data: &[] },
 | |
|                 data: [0; MAX_HOSTNAME_LEN],
 | |
|             }),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Static IP address configuration.
 | |
| #[cfg(feature = "proto-ipv4")]
 | |
| #[derive(Debug, Clone, PartialEq, Eq)]
 | |
| pub struct StaticConfigV4 {
 | |
|     /// IP address and subnet mask.
 | |
|     pub address: Ipv4Cidr,
 | |
|     /// Default gateway.
 | |
|     pub gateway: Option<Ipv4Address>,
 | |
|     /// DNS servers.
 | |
|     pub dns_servers: Vec<Ipv4Address, 3>,
 | |
| }
 | |
| 
 | |
| /// Static IPv6 address configuration
 | |
| #[cfg(feature = "proto-ipv6")]
 | |
| #[derive(Debug, Clone, PartialEq, Eq)]
 | |
| pub struct StaticConfigV6 {
 | |
|     /// IP address and subnet mask.
 | |
|     pub address: Ipv6Cidr,
 | |
|     /// Default gateway.
 | |
|     pub gateway: Option<Ipv6Address>,
 | |
|     /// DNS servers.
 | |
|     pub dns_servers: Vec<Ipv6Address, 3>,
 | |
| }
 | |
| 
 | |
| /// DHCP configuration.
 | |
| #[cfg(feature = "dhcpv4")]
 | |
| #[derive(Debug, Clone, PartialEq, Eq)]
 | |
| #[non_exhaustive]
 | |
| pub struct DhcpConfig {
 | |
|     /// Maximum lease duration.
 | |
|     ///
 | |
|     /// If not set, the lease duration specified by the server will be used.
 | |
|     /// If set, the lease duration will be capped at this value.
 | |
|     pub max_lease_duration: Option<embassy_time::Duration>,
 | |
|     /// Retry configuration.
 | |
|     pub retry_config: RetryConfig,
 | |
|     /// Ignore NAKs from DHCP servers.
 | |
|     ///
 | |
|     /// This is not compliant with the DHCP RFCs, since theoretically we must stop using the assigned IP when receiving a NAK. This can increase reliability on broken networks with buggy routers or rogue DHCP servers, however.
 | |
|     pub ignore_naks: bool,
 | |
|     /// Server port. This is almost always 67. Do not change unless you know what you're doing.
 | |
|     pub server_port: u16,
 | |
|     /// Client port. This is almost always 68. Do not change unless you know what you're doing.
 | |
|     pub client_port: u16,
 | |
|     /// Our hostname. This will be sent to the DHCP server as Option 12.
 | |
|     #[cfg(feature = "dhcpv4-hostname")]
 | |
|     pub hostname: Option<heapless::String<MAX_HOSTNAME_LEN>>,
 | |
| }
 | |
| 
 | |
| #[cfg(feature = "dhcpv4")]
 | |
| impl Default for DhcpConfig {
 | |
|     fn default() -> Self {
 | |
|         Self {
 | |
|             max_lease_duration: Default::default(),
 | |
|             retry_config: Default::default(),
 | |
|             ignore_naks: Default::default(),
 | |
|             server_port: smoltcp::wire::DHCP_SERVER_PORT,
 | |
|             client_port: smoltcp::wire::DHCP_CLIENT_PORT,
 | |
|             #[cfg(feature = "dhcpv4-hostname")]
 | |
|             hostname: None,
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Network stack configuration.
 | |
| #[derive(Debug, Clone, Default)]
 | |
| #[non_exhaustive]
 | |
| pub struct Config {
 | |
|     /// IPv4 configuration
 | |
|     #[cfg(feature = "proto-ipv4")]
 | |
|     pub ipv4: ConfigV4,
 | |
|     /// IPv6 configuration
 | |
|     #[cfg(feature = "proto-ipv6")]
 | |
|     pub ipv6: ConfigV6,
 | |
| }
 | |
| 
 | |
| impl Config {
 | |
|     /// IPv4 configuration with static addressing.
 | |
|     #[cfg(feature = "proto-ipv4")]
 | |
|     pub fn ipv4_static(config: StaticConfigV4) -> Self {
 | |
|         Self {
 | |
|             ipv4: ConfigV4::Static(config),
 | |
|             #[cfg(feature = "proto-ipv6")]
 | |
|             ipv6: ConfigV6::None,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// IPv6 configuration with static addressing.
 | |
|     #[cfg(feature = "proto-ipv6")]
 | |
|     pub fn ipv6_static(config: StaticConfigV6) -> Self {
 | |
|         Self {
 | |
|             #[cfg(feature = "proto-ipv4")]
 | |
|             ipv4: ConfigV4::None,
 | |
|             ipv6: ConfigV6::Static(config),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// IPv4 configuration with dynamic addressing.
 | |
|     ///
 | |
|     /// # Example
 | |
|     /// ```rust
 | |
|     /// # use embassy_net::Config;
 | |
|     /// let _cfg = Config::dhcpv4(Default::default());
 | |
|     /// ```
 | |
|     #[cfg(feature = "dhcpv4")]
 | |
|     pub fn dhcpv4(config: DhcpConfig) -> Self {
 | |
|         Self {
 | |
|             ipv4: ConfigV4::Dhcp(config),
 | |
|             #[cfg(feature = "proto-ipv6")]
 | |
|             ipv6: ConfigV6::None,
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Network stack IPv4 configuration.
 | |
| #[cfg(feature = "proto-ipv4")]
 | |
| #[derive(Debug, Clone, Default)]
 | |
| pub enum ConfigV4 {
 | |
|     /// Do not configure IPv4.
 | |
|     #[default]
 | |
|     None,
 | |
|     /// Use a static IPv4 address configuration.
 | |
|     Static(StaticConfigV4),
 | |
|     /// Use DHCP to obtain an IP address configuration.
 | |
|     #[cfg(feature = "dhcpv4")]
 | |
|     Dhcp(DhcpConfig),
 | |
| }
 | |
| 
 | |
| /// Network stack IPv6 configuration.
 | |
| #[cfg(feature = "proto-ipv6")]
 | |
| #[derive(Debug, Clone, Default)]
 | |
| pub enum ConfigV6 {
 | |
|     /// Do not configure IPv6.
 | |
|     #[default]
 | |
|     None,
 | |
|     /// Use a static IPv6 address configuration.
 | |
|     Static(StaticConfigV6),
 | |
| }
 | |
| 
 | |
| /// A network stack.
 | |
| ///
 | |
| /// This is the main entry point for the network stack.
 | |
| pub struct Stack<D: Driver> {
 | |
|     pub(crate) socket: RefCell<SocketStack>,
 | |
|     inner: RefCell<Inner<D>>,
 | |
| }
 | |
| 
 | |
| struct Inner<D: Driver> {
 | |
|     device: D,
 | |
|     link_up: bool,
 | |
|     #[cfg(feature = "proto-ipv4")]
 | |
|     static_v4: Option<StaticConfigV4>,
 | |
|     #[cfg(feature = "proto-ipv6")]
 | |
|     static_v6: Option<StaticConfigV6>,
 | |
|     #[cfg(feature = "dhcpv4")]
 | |
|     dhcp_socket: Option<SocketHandle>,
 | |
|     config_waker: WakerRegistration,
 | |
|     #[cfg(feature = "dns")]
 | |
|     dns_socket: SocketHandle,
 | |
|     #[cfg(feature = "dns")]
 | |
|     dns_waker: WakerRegistration,
 | |
|     #[cfg(feature = "dhcpv4-hostname")]
 | |
|     hostname: &'static mut core::cell::UnsafeCell<HostnameResources>,
 | |
| }
 | |
| 
 | |
| pub(crate) struct SocketStack {
 | |
|     pub(crate) sockets: SocketSet<'static>,
 | |
|     pub(crate) iface: Interface,
 | |
|     pub(crate) waker: WakerRegistration,
 | |
|     next_local_port: u16,
 | |
| }
 | |
| 
 | |
| fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) {
 | |
|     match addr {
 | |
|         #[cfg(feature = "medium-ethernet")]
 | |
|         driver::HardwareAddress::Ethernet(eth) => (HardwareAddress::Ethernet(EthernetAddress(eth)), Medium::Ethernet),
 | |
|         #[cfg(feature = "medium-ieee802154")]
 | |
|         driver::HardwareAddress::Ieee802154(ieee) => (
 | |
|             HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
 | |
|             Medium::Ieee802154,
 | |
|         ),
 | |
|         #[cfg(feature = "medium-ip")]
 | |
|         driver::HardwareAddress::Ip => (HardwareAddress::Ip, Medium::Ip),
 | |
| 
 | |
|         #[allow(unreachable_patterns)]
 | |
|         _ => panic!(
 | |
|             "Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.",
 | |
|             addr
 | |
|         ),
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<D: Driver> Stack<D> {
 | |
|     /// Create a new network stack.
 | |
|     pub fn new<const SOCK: usize>(
 | |
|         mut device: D,
 | |
|         config: Config,
 | |
|         resources: &'static mut StackResources<SOCK>,
 | |
|         random_seed: u64,
 | |
|     ) -> Self {
 | |
|         let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address());
 | |
|         let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
 | |
|         iface_cfg.random_seed = random_seed;
 | |
| 
 | |
|         let iface = Interface::new(
 | |
|             iface_cfg,
 | |
|             &mut DriverAdapter {
 | |
|                 inner: &mut device,
 | |
|                 cx: None,
 | |
|                 medium,
 | |
|             },
 | |
|             instant_to_smoltcp(Instant::now()),
 | |
|         );
 | |
| 
 | |
|         let sockets = SocketSet::new(&mut resources.sockets[..]);
 | |
| 
 | |
|         let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
 | |
| 
 | |
|         #[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
 | |
|         let mut socket = SocketStack {
 | |
|             sockets,
 | |
|             iface,
 | |
|             waker: WakerRegistration::new(),
 | |
|             next_local_port,
 | |
|         };
 | |
| 
 | |
|         let mut inner = Inner {
 | |
|             device,
 | |
|             link_up: false,
 | |
|             #[cfg(feature = "proto-ipv4")]
 | |
|             static_v4: None,
 | |
|             #[cfg(feature = "proto-ipv6")]
 | |
|             static_v6: None,
 | |
|             #[cfg(feature = "dhcpv4")]
 | |
|             dhcp_socket: None,
 | |
|             config_waker: WakerRegistration::new(),
 | |
|             #[cfg(feature = "dns")]
 | |
|             dns_socket: socket.sockets.add(dns::Socket::new(
 | |
|                 &[],
 | |
|                 managed::ManagedSlice::Borrowed(&mut resources.queries),
 | |
|             )),
 | |
|             #[cfg(feature = "dns")]
 | |
|             dns_waker: WakerRegistration::new(),
 | |
|             #[cfg(feature = "dhcpv4-hostname")]
 | |
|             hostname: &mut resources.hostname,
 | |
|         };
 | |
| 
 | |
|         #[cfg(feature = "proto-ipv4")]
 | |
|         inner.set_config_v4(&mut socket, config.ipv4);
 | |
|         #[cfg(feature = "proto-ipv6")]
 | |
|         inner.set_config_v6(&mut socket, config.ipv6);
 | |
|         inner.apply_static_config(&mut socket);
 | |
| 
 | |
|         Self {
 | |
|             socket: RefCell::new(socket),
 | |
|             inner: RefCell::new(inner),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
 | |
|         f(&*self.socket.borrow(), &*self.inner.borrow())
 | |
|     }
 | |
| 
 | |
|     fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
 | |
|         f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
 | |
|     }
 | |
| 
 | |
|     /// Get the hardware address of the network interface.
 | |
|     pub fn hardware_address(&self) -> HardwareAddress {
 | |
|         self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()).0)
 | |
|     }
 | |
| 
 | |
|     /// Get whether the link is up.
 | |
|     pub fn is_link_up(&self) -> bool {
 | |
|         self.with(|_s, i| i.link_up)
 | |
|     }
 | |
| 
 | |
|     /// Get whether the network stack has a valid IP configuration.
 | |
|     /// This is true if the network stack has a static IP configuration or if DHCP has completed
 | |
|     pub fn is_config_up(&self) -> bool {
 | |
|         let v4_up;
 | |
|         let v6_up;
 | |
| 
 | |
|         #[cfg(feature = "proto-ipv4")]
 | |
|         {
 | |
|             v4_up = self.config_v4().is_some();
 | |
|         }
 | |
|         #[cfg(not(feature = "proto-ipv4"))]
 | |
|         {
 | |
|             v4_up = false;
 | |
|         }
 | |
| 
 | |
|         #[cfg(feature = "proto-ipv6")]
 | |
|         {
 | |
|             v6_up = self.config_v6().is_some();
 | |
|         }
 | |
|         #[cfg(not(feature = "proto-ipv6"))]
 | |
|         {
 | |
|             v6_up = false;
 | |
|         }
 | |
| 
 | |
|         v4_up || v6_up
 | |
|     }
 | |
| 
 | |
|     /// Wait for the network stack to obtain a valid IP configuration.
 | |
|     ///
 | |
|     /// ## Notes:
 | |
|     /// - Ensure [`Stack::run`] has been called before using this function.
 | |
|     ///
 | |
|     /// - This function may never return (e.g. if no configuration is obtained through DHCP).
 | |
|     /// The caller is supposed to handle a timeout for this case.
 | |
|     ///
 | |
|     /// ## Example
 | |
|     /// ```ignore
 | |
|     /// let config = embassy_net::Config::dhcpv4(Default::default());
 | |
|     ///// Init network stack
 | |
|     /// static RESOURCES: StaticCell<embassy_net::StackResources<2> = StaticCell::new();
 | |
|     /// static STACK: StaticCell<embassy_net::Stack> = StaticCell::new();
 | |
|     /// let stack = &*STACK.init(embassy_net::Stack::new(
 | |
|     ///    device,
 | |
|     ///    config,
 | |
|     ///    RESOURCES.init(embassy_net::StackResources::new()),
 | |
|     ///    seed
 | |
|     /// ));
 | |
|     /// // Launch network task that runs `stack.run().await`
 | |
|     /// spawner.spawn(net_task(stack)).unwrap();
 | |
|     /// // Wait for DHCP config
 | |
|     /// stack.wait_config_up().await;
 | |
|     /// // use the network stack
 | |
|     /// // ...
 | |
|     /// ```
 | |
|     pub async fn wait_config_up(&self) {
 | |
|         // If the config is up already, we can return immediately.
 | |
|         if self.is_config_up() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         poll_fn(|cx| {
 | |
|             if self.is_config_up() {
 | |
|                 Poll::Ready(())
 | |
|             } else {
 | |
|                 // If the config is not up, we register a waker that is woken up
 | |
|                 // when a config is applied (static or DHCP).
 | |
|                 trace!("Waiting for config up");
 | |
| 
 | |
|                 self.with_mut(|_, i| {
 | |
|                     i.config_waker.register(cx.waker());
 | |
|                 });
 | |
| 
 | |
|                 Poll::Pending
 | |
|             }
 | |
|         })
 | |
|         .await;
 | |
|     }
 | |
| 
 | |
|     /// Get the current IPv4 configuration.
 | |
|     ///
 | |
|     /// If using DHCP, this will be None if DHCP hasn't been able to
 | |
|     /// acquire an IP address, or Some if it has.
 | |
|     #[cfg(feature = "proto-ipv4")]
 | |
|     pub fn config_v4(&self) -> Option<StaticConfigV4> {
 | |
|         self.with(|_, i| i.static_v4.clone())
 | |
|     }
 | |
| 
 | |
|     /// Get the current IPv6 configuration.
 | |
|     #[cfg(feature = "proto-ipv6")]
 | |
|     pub fn config_v6(&self) -> Option<StaticConfigV6> {
 | |
|         self.with(|_, i| i.static_v6.clone())
 | |
|     }
 | |
| 
 | |
|     /// Set the IPv4 configuration.
 | |
|     #[cfg(feature = "proto-ipv4")]
 | |
|     pub fn set_config_v4(&self, config: ConfigV4) {
 | |
|         self.with_mut(|s, i| {
 | |
|             i.set_config_v4(s, config);
 | |
|             i.apply_static_config(s);
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Set the IPv6 configuration.
 | |
|     #[cfg(feature = "proto-ipv6")]
 | |
|     pub fn set_config_v6(&self, config: ConfigV6) {
 | |
|         self.with_mut(|s, i| {
 | |
|             i.set_config_v6(s, config);
 | |
|             i.apply_static_config(s);
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Run the network stack.
 | |
|     ///
 | |
|     /// You must call this in a background task, to process network events.
 | |
|     pub async fn run(&self) -> ! {
 | |
|         poll_fn(|cx| {
 | |
|             self.with_mut(|s, i| i.poll(cx, s));
 | |
|             Poll::<()>::Pending
 | |
|         })
 | |
|         .await;
 | |
|         unreachable!()
 | |
|     }
 | |
| 
 | |
|     /// Make a query for a given name and return the corresponding IP addresses.
 | |
|     #[cfg(feature = "dns")]
 | |
|     pub async fn dns_query(
 | |
|         &self,
 | |
|         name: &str,
 | |
|         qtype: dns::DnsQueryType,
 | |
|     ) -> Result<Vec<IpAddress, { smoltcp::config::DNS_MAX_RESULT_COUNT }>, dns::Error> {
 | |
|         // For A and AAAA queries we try detect whether `name` is just an IP address
 | |
|         match qtype {
 | |
|             #[cfg(feature = "proto-ipv4")]
 | |
|             dns::DnsQueryType::A => {
 | |
|                 if let Ok(ip) = name.parse().map(IpAddress::Ipv4) {
 | |
|                     return Ok([ip].into_iter().collect());
 | |
|                 }
 | |
|             }
 | |
|             #[cfg(feature = "proto-ipv6")]
 | |
|             dns::DnsQueryType::Aaaa => {
 | |
|                 if let Ok(ip) = name.parse().map(IpAddress::Ipv6) {
 | |
|                     return Ok([ip].into_iter().collect());
 | |
|                 }
 | |
|             }
 | |
|             _ => {}
 | |
|         }
 | |
| 
 | |
|         let query = poll_fn(|cx| {
 | |
|             self.with_mut(|s, i| {
 | |
|                 let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
 | |
|                 match socket.start_query(s.iface.context(), name, qtype) {
 | |
|                     Ok(handle) => {
 | |
|                         s.waker.wake();
 | |
|                         Poll::Ready(Ok(handle))
 | |
|                     }
 | |
|                     Err(dns::StartQueryError::NoFreeSlot) => {
 | |
|                         i.dns_waker.register(cx.waker());
 | |
|                         Poll::Pending
 | |
|                     }
 | |
|                     Err(e) => Poll::Ready(Err(e)),
 | |
|                 }
 | |
|             })
 | |
|         })
 | |
|         .await?;
 | |
| 
 | |
|         #[must_use = "to delay the drop handler invocation to the end of the scope"]
 | |
|         struct OnDrop<F: FnOnce()> {
 | |
|             f: core::mem::MaybeUninit<F>,
 | |
|         }
 | |
| 
 | |
|         impl<F: FnOnce()> OnDrop<F> {
 | |
|             fn new(f: F) -> Self {
 | |
|                 Self {
 | |
|                     f: core::mem::MaybeUninit::new(f),
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             fn defuse(self) {
 | |
|                 core::mem::forget(self)
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         impl<F: FnOnce()> Drop for OnDrop<F> {
 | |
|             fn drop(&mut self) {
 | |
|                 unsafe { self.f.as_ptr().read()() }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let drop = OnDrop::new(|| {
 | |
|             self.with_mut(|s, i| {
 | |
|                 let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
 | |
|                 socket.cancel_query(query);
 | |
|                 s.waker.wake();
 | |
|                 i.dns_waker.wake();
 | |
|             })
 | |
|         });
 | |
| 
 | |
|         let res = poll_fn(|cx| {
 | |
|             self.with_mut(|s, i| {
 | |
|                 let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
 | |
|                 match socket.get_query_result(query) {
 | |
|                     Ok(addrs) => {
 | |
|                         i.dns_waker.wake();
 | |
|                         Poll::Ready(Ok(addrs))
 | |
|                     }
 | |
|                     Err(dns::GetQueryResultError::Pending) => {
 | |
|                         socket.register_query_waker(query, cx.waker());
 | |
|                         Poll::Pending
 | |
|                     }
 | |
|                     Err(e) => {
 | |
|                         i.dns_waker.wake();
 | |
|                         Poll::Ready(Err(e.into()))
 | |
|                     }
 | |
|                 }
 | |
|             })
 | |
|         })
 | |
|         .await;
 | |
| 
 | |
|         drop.defuse();
 | |
| 
 | |
|         res
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[cfg(feature = "igmp")]
 | |
| impl<D: Driver> Stack<D> {
 | |
|     /// Join a multicast group.
 | |
|     pub async fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
 | |
|     where
 | |
|         T: Into<IpAddress>,
 | |
|     {
 | |
|         let addr = addr.into();
 | |
| 
 | |
|         poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await
 | |
|     }
 | |
| 
 | |
|     /// Join a multicast group.
 | |
|     ///
 | |
|     /// When the send queue is full, this method will return `Poll::Pending`
 | |
|     /// and register the current task to be notified when the queue has space available.
 | |
|     pub fn poll_join_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
 | |
|     where
 | |
|         T: Into<IpAddress>,
 | |
|     {
 | |
|         let addr = addr.into();
 | |
| 
 | |
|         self.with_mut(|s, i| {
 | |
|             let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
 | |
|             let mut smoldev = DriverAdapter {
 | |
|                 cx: Some(cx),
 | |
|                 inner: &mut i.device,
 | |
|                 medium,
 | |
|             };
 | |
| 
 | |
|             match s
 | |
|                 .iface
 | |
|                 .join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
 | |
|             {
 | |
|                 Ok(announce_sent) => Poll::Ready(Ok(announce_sent)),
 | |
|                 Err(MulticastError::Exhausted) => Poll::Pending,
 | |
|                 Err(other) => Poll::Ready(Err(other)),
 | |
|             }
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Leave a multicast group.
 | |
|     pub async fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
 | |
|     where
 | |
|         T: Into<IpAddress>,
 | |
|     {
 | |
|         let addr = addr.into();
 | |
| 
 | |
|         poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await
 | |
|     }
 | |
| 
 | |
|     /// Leave a multicast group.
 | |
|     ///
 | |
|     /// When the send queue is full, this method will return `Poll::Pending`
 | |
|     /// and register the current task to be notified when the queue has space available.
 | |
|     pub fn poll_leave_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
 | |
|     where
 | |
|         T: Into<IpAddress>,
 | |
|     {
 | |
|         let addr = addr.into();
 | |
| 
 | |
|         self.with_mut(|s, i| {
 | |
|             let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
 | |
|             let mut smoldev = DriverAdapter {
 | |
|                 cx: Some(cx),
 | |
|                 inner: &mut i.device,
 | |
|                 medium,
 | |
|             };
 | |
| 
 | |
|             match s
 | |
|                 .iface
 | |
|                 .leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
 | |
|             {
 | |
|                 Ok(leave_sent) => Poll::Ready(Ok(leave_sent)),
 | |
|                 Err(MulticastError::Exhausted) => Poll::Pending,
 | |
|                 Err(other) => Poll::Ready(Err(other)),
 | |
|             }
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Get whether the network stack has joined the given multicast group.
 | |
|     pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
 | |
|         self.socket.borrow().iface.has_multicast_group(addr)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl SocketStack {
 | |
|     #[allow(clippy::absurd_extreme_comparisons, dead_code)]
 | |
|     pub fn get_local_port(&mut self) -> u16 {
 | |
|         let res = self.next_local_port;
 | |
|         self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
 | |
|         res
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<D: Driver> Inner<D> {
 | |
|     #[cfg(feature = "proto-ipv4")]
 | |
|     pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) {
 | |
|         // Handle static config.
 | |
|         self.static_v4 = match config.clone() {
 | |
|             ConfigV4::None => None,
 | |
|             #[cfg(feature = "dhcpv4")]
 | |
|             ConfigV4::Dhcp(_) => None,
 | |
|             ConfigV4::Static(c) => Some(c),
 | |
|         };
 | |
| 
 | |
|         // Handle DHCP config.
 | |
|         #[cfg(feature = "dhcpv4")]
 | |
|         match config {
 | |
|             ConfigV4::Dhcp(c) => {
 | |
|                 // Create the socket if it doesn't exist.
 | |
|                 if self.dhcp_socket.is_none() {
 | |
|                     let socket = smoltcp::socket::dhcpv4::Socket::new();
 | |
|                     let handle = _s.sockets.add(socket);
 | |
|                     self.dhcp_socket = Some(handle);
 | |
|                 }
 | |
| 
 | |
|                 // Configure it
 | |
|                 let socket = _s.sockets.get_mut::<dhcpv4::Socket>(unwrap!(self.dhcp_socket));
 | |
|                 socket.set_ignore_naks(c.ignore_naks);
 | |
|                 socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp));
 | |
|                 socket.set_ports(c.server_port, c.client_port);
 | |
|                 socket.set_retry_config(c.retry_config);
 | |
| 
 | |
|                 socket.set_outgoing_options(&[]);
 | |
|                 #[cfg(feature = "dhcpv4-hostname")]
 | |
|                 if let Some(h) = c.hostname {
 | |
|                     // safety: we just did set_outgoing_options([]) so we know the socket is no longer holding a reference.
 | |
|                     let hostname = unsafe { &mut *self.hostname.get() };
 | |
| 
 | |
|                     // create data
 | |
|                     // safety: we know the buffer lives forever, new borrows the StackResources for 'static.
 | |
|                     // also we won't modify it until next call to this function.
 | |
|                     hostname.data[..h.len()].copy_from_slice(h.as_bytes());
 | |
|                     let data: &[u8] = &hostname.data[..h.len()];
 | |
|                     let data: &'static [u8] = unsafe { core::mem::transmute(data) };
 | |
| 
 | |
|                     // set the option.
 | |
|                     hostname.option = smoltcp::wire::DhcpOption { data, kind: 12 };
 | |
|                     socket.set_outgoing_options(core::slice::from_ref(&hostname.option));
 | |
|                 }
 | |
| 
 | |
|                 socket.reset();
 | |
|             }
 | |
|             _ => {
 | |
|                 // Remove DHCP socket if any.
 | |
|                 if let Some(socket) = self.dhcp_socket {
 | |
|                     _s.sockets.remove(socket);
 | |
|                     self.dhcp_socket = None;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(feature = "proto-ipv6")]
 | |
|     pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) {
 | |
|         self.static_v6 = match config {
 | |
|             ConfigV6::None => None,
 | |
|             ConfigV6::Static(c) => Some(c),
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     fn apply_static_config(&mut self, s: &mut SocketStack) {
 | |
|         let mut addrs = Vec::new();
 | |
|         #[cfg(feature = "dns")]
 | |
|         let mut dns_servers: Vec<_, 6> = Vec::new();
 | |
|         #[cfg(feature = "proto-ipv4")]
 | |
|         let mut gateway_v4 = None;
 | |
|         #[cfg(feature = "proto-ipv6")]
 | |
|         let mut gateway_v6 = None;
 | |
| 
 | |
|         #[cfg(feature = "proto-ipv4")]
 | |
|         if let Some(config) = &self.static_v4 {
 | |
|             debug!("IPv4: UP");
 | |
|             debug!("   IP address:      {:?}", config.address);
 | |
|             debug!("   Default gateway: {:?}", config.gateway);
 | |
| 
 | |
|             unwrap!(addrs.push(IpCidr::Ipv4(config.address)).ok());
 | |
|             gateway_v4 = config.gateway.into();
 | |
|             #[cfg(feature = "dns")]
 | |
|             for s in &config.dns_servers {
 | |
|                 debug!("   DNS server:      {:?}", s);
 | |
|                 unwrap!(dns_servers.push(s.clone().into()).ok());
 | |
|             }
 | |
|         } else {
 | |
|             info!("IPv4: DOWN");
 | |
|         }
 | |
| 
 | |
|         #[cfg(feature = "proto-ipv6")]
 | |
|         if let Some(config) = &self.static_v6 {
 | |
|             debug!("IPv6: UP");
 | |
|             debug!("   IP address:      {:?}", config.address);
 | |
|             debug!("   Default gateway: {:?}", config.gateway);
 | |
| 
 | |
|             unwrap!(addrs.push(IpCidr::Ipv6(config.address)).ok());
 | |
|             gateway_v6 = config.gateway.into();
 | |
|             #[cfg(feature = "dns")]
 | |
|             for s in &config.dns_servers {
 | |
|                 debug!("   DNS server:      {:?}", s);
 | |
|                 unwrap!(dns_servers.push(s.clone().into()).ok());
 | |
|             }
 | |
|         } else {
 | |
|             info!("IPv6: DOWN");
 | |
|         }
 | |
| 
 | |
|         // Apply addresses
 | |
|         s.iface.update_ip_addrs(|a| *a = addrs);
 | |
| 
 | |
|         // Apply gateways
 | |
|         #[cfg(feature = "proto-ipv4")]
 | |
|         if let Some(gateway) = gateway_v4 {
 | |
|             unwrap!(s.iface.routes_mut().add_default_ipv4_route(gateway));
 | |
|         } else {
 | |
|             s.iface.routes_mut().remove_default_ipv4_route();
 | |
|         }
 | |
|         #[cfg(feature = "proto-ipv6")]
 | |
|         if let Some(gateway) = gateway_v6 {
 | |
|             unwrap!(s.iface.routes_mut().add_default_ipv6_route(gateway));
 | |
|         } else {
 | |
|             s.iface.routes_mut().remove_default_ipv6_route();
 | |
|         }
 | |
| 
 | |
|         // Apply DNS servers
 | |
|         #[cfg(feature = "dns")]
 | |
|         s.sockets
 | |
|             .get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket)
 | |
|             .update_servers(&dns_servers[..]);
 | |
| 
 | |
|         self.config_waker.wake();
 | |
|     }
 | |
| 
 | |
|     fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
 | |
|         s.waker.register(cx.waker());
 | |
| 
 | |
|         let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address());
 | |
| 
 | |
|         #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
 | |
|         {
 | |
|             let do_set = match medium {
 | |
|                 #[cfg(feature = "medium-ethernet")]
 | |
|                 Medium::Ethernet => true,
 | |
|                 #[cfg(feature = "medium-ieee802154")]
 | |
|                 Medium::Ieee802154 => true,
 | |
|                 #[allow(unreachable_patterns)]
 | |
|                 _ => false,
 | |
|             };
 | |
|             if do_set {
 | |
|                 s.iface.set_hardware_addr(_hardware_addr);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let timestamp = instant_to_smoltcp(Instant::now());
 | |
|         let mut smoldev = DriverAdapter {
 | |
|             cx: Some(cx),
 | |
|             inner: &mut self.device,
 | |
|             medium,
 | |
|         };
 | |
|         s.iface.poll(timestamp, &mut smoldev, &mut s.sockets);
 | |
| 
 | |
|         // Update link up
 | |
|         let old_link_up = self.link_up;
 | |
|         self.link_up = self.device.link_state(cx) == LinkState::Up;
 | |
| 
 | |
|         // Print when changed
 | |
|         if old_link_up != self.link_up {
 | |
|             info!("link_up = {:?}", self.link_up);
 | |
|         }
 | |
| 
 | |
|         #[allow(unused_mut)]
 | |
|         let mut apply_config = false;
 | |
| 
 | |
|         #[cfg(feature = "dhcpv4")]
 | |
|         if let Some(dhcp_handle) = self.dhcp_socket {
 | |
|             let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
 | |
| 
 | |
|             if self.link_up {
 | |
|                 if old_link_up != self.link_up {
 | |
|                     socket.reset();
 | |
|                 }
 | |
|                 match socket.poll() {
 | |
|                     None => {}
 | |
|                     Some(dhcpv4::Event::Deconfigured) => {
 | |
|                         self.static_v4 = None;
 | |
|                         apply_config = true;
 | |
|                     }
 | |
|                     Some(dhcpv4::Event::Configured(config)) => {
 | |
|                         self.static_v4 = Some(StaticConfigV4 {
 | |
|                             address: config.address,
 | |
|                             gateway: config.router,
 | |
|                             dns_servers: config.dns_servers,
 | |
|                         });
 | |
|                         apply_config = true;
 | |
|                     }
 | |
|                 }
 | |
|             } else if old_link_up {
 | |
|                 socket.reset();
 | |
|                 self.static_v4 = None;
 | |
|                 apply_config = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if apply_config {
 | |
|             self.apply_static_config(s);
 | |
|         }
 | |
| 
 | |
|         if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
 | |
|             let t = Timer::at(instant_from_smoltcp(poll_at));
 | |
|             pin_mut!(t);
 | |
|             if t.poll(cx).is_ready() {
 | |
|                 cx.waker().wake_by_ref();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |