Skip to main content

embassy_net/
lib.rs

1#![no_std]
2#![allow(async_fn_in_trait)]
3#![allow(unsafe_op_in_unsafe_fn)]
4#![warn(missing_docs)]
5#![doc = include_str!("../README.md")]
6
7//! ## Feature flags
8#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
9
10#[cfg(not(any(feature = "proto-ipv4", feature = "proto-ipv6")))]
11compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6");
12
13// This mod MUST go first, so that the others see its macros.
14pub(crate) mod fmt;
15
16#[cfg(feature = "dns")]
17pub mod dns;
18mod driver_util;
19#[cfg(feature = "icmp")]
20pub mod icmp;
21#[cfg(feature = "raw")]
22pub mod raw;
23#[cfg(feature = "tcp")]
24pub mod tcp;
25mod time;
26#[cfg(feature = "udp")]
27pub mod udp;
28
29use core::cell::RefCell;
30use core::future::{Future, poll_fn};
31use core::mem::MaybeUninit;
32use core::pin::pin;
33use core::task::{Context, Poll};
34
35pub use embassy_net_driver as driver;
36use embassy_net_driver::{Driver, LinkState};
37use embassy_sync::waitqueue::WakerRegistration;
38use embassy_time::{Instant, Timer};
39use heapless::Vec;
40#[cfg(feature = "dns")]
41pub use smoltcp::config::DNS_MAX_SERVER_COUNT;
42#[cfg(feature = "multicast")]
43pub use smoltcp::iface::MulticastError;
44#[cfg(any(feature = "dns", feature = "dhcpv4"))]
45use smoltcp::iface::SocketHandle;
46use smoltcp::iface::{Interface, SocketSet, SocketStorage};
47use smoltcp::phy::Medium;
48#[cfg(feature = "dhcpv4")]
49use smoltcp::socket::dhcpv4::{self, RetryConfig};
50#[cfg(feature = "medium-ethernet")]
51pub use smoltcp::wire::EthernetAddress;
52#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154", feature = "medium-ip"))]
53pub use smoltcp::wire::HardwareAddress;
54#[cfg(any(feature = "udp", feature = "tcp"))]
55pub use smoltcp::wire::IpListenEndpoint;
56#[cfg(feature = "medium-ieee802154")]
57pub use smoltcp::wire::{Ieee802154Address, Ieee802154Frame};
58pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint};
59#[cfg(feature = "proto-ipv4")]
60pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
61#[cfg(feature = "proto-ipv6")]
62pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
63
64use crate::driver_util::DriverAdapter;
65use crate::time::{instant_from_smoltcp, instant_to_smoltcp};
66
67const LOCAL_PORT_MIN: u16 = 1025;
68const LOCAL_PORT_MAX: u16 = 65535;
69#[cfg(feature = "dns")]
70const MAX_QUERIES: usize = 4;
71#[cfg(feature = "dhcpv4-hostname")]
72const MAX_HOSTNAME_LEN: usize = 32;
73
74/// Memory resources needed for a network stack.
75pub struct StackResources<const SOCK: usize> {
76    sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>,
77    inner: MaybeUninit<RefCell<Inner>>,
78    #[cfg(feature = "dns")]
79    queries: MaybeUninit<[Option<dns::DnsQuery>; MAX_QUERIES]>,
80    #[cfg(feature = "dhcpv4-hostname")]
81    hostname: HostnameResources,
82}
83
84#[cfg(feature = "dhcpv4-hostname")]
85struct HostnameResources {
86    option: MaybeUninit<smoltcp::wire::DhcpOption<'static>>,
87    data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>,
88}
89
90impl<const SOCK: usize> StackResources<SOCK> {
91    /// Create a new set of stack resources.
92    pub const fn new() -> Self {
93        Self {
94            sockets: MaybeUninit::uninit(),
95            inner: MaybeUninit::uninit(),
96            #[cfg(feature = "dns")]
97            queries: MaybeUninit::uninit(),
98            #[cfg(feature = "dhcpv4-hostname")]
99            hostname: HostnameResources {
100                option: MaybeUninit::uninit(),
101                data: MaybeUninit::uninit(),
102            },
103        }
104    }
105}
106
107/// Static IP address configuration.
108#[cfg(feature = "proto-ipv4")]
109#[derive(Debug, Clone, PartialEq, Eq)]
110#[cfg_attr(feature = "defmt", derive(defmt::Format))]
111pub struct StaticConfigV4 {
112    /// IP address and subnet mask.
113    pub address: Ipv4Cidr,
114    /// Default gateway.
115    pub gateway: Option<Ipv4Address>,
116    /// DNS servers.
117    pub dns_servers: Vec<Ipv4Address, 3>,
118}
119
120/// Static IPv6 address configuration
121#[cfg(feature = "proto-ipv6")]
122#[derive(Debug, Clone, PartialEq, Eq)]
123#[cfg_attr(feature = "defmt", derive(defmt::Format))]
124pub struct StaticConfigV6 {
125    /// IP address and subnet mask.
126    pub address: Ipv6Cidr,
127    /// Default gateway.
128    pub gateway: Option<Ipv6Address>,
129    /// DNS servers.
130    pub dns_servers: Vec<Ipv6Address, 3>,
131}
132
133/// DHCP configuration.
134#[cfg(feature = "dhcpv4")]
135#[derive(Debug, Clone, PartialEq, Eq)]
136#[cfg_attr(feature = "defmt", derive(defmt::Format))]
137#[non_exhaustive]
138pub struct DhcpConfig {
139    /// Maximum lease duration.
140    ///
141    /// If not set, the lease duration specified by the server will be used.
142    /// If set, the lease duration will be capped at this value.
143    pub max_lease_duration: Option<embassy_time::Duration>,
144    /// Retry configuration.
145    pub retry_config: RetryConfig,
146    /// Ignore NAKs from DHCP servers.
147    ///
148    /// 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.
149    pub ignore_naks: bool,
150    /// Server port. This is almost always 67. Do not change unless you know what you're doing.
151    pub server_port: u16,
152    /// Client port. This is almost always 68. Do not change unless you know what you're doing.
153    pub client_port: u16,
154    /// Our hostname. This will be sent to the DHCP server as Option 12.
155    #[cfg(feature = "dhcpv4-hostname")]
156    pub hostname: Option<heapless::String<MAX_HOSTNAME_LEN>>,
157}
158
159#[cfg(feature = "dhcpv4")]
160impl Default for DhcpConfig {
161    fn default() -> Self {
162        Self {
163            max_lease_duration: Default::default(),
164            retry_config: Default::default(),
165            ignore_naks: Default::default(),
166            server_port: smoltcp::wire::DHCP_SERVER_PORT,
167            client_port: smoltcp::wire::DHCP_CLIENT_PORT,
168            #[cfg(feature = "dhcpv4-hostname")]
169            hostname: None,
170        }
171    }
172}
173
174/// Network stack configuration.
175#[derive(Debug, Clone, Default, PartialEq, Eq)]
176#[cfg_attr(feature = "defmt", derive(defmt::Format))]
177#[non_exhaustive]
178pub struct Config {
179    /// IPv4 configuration
180    #[cfg(feature = "proto-ipv4")]
181    pub ipv4: ConfigV4,
182    /// IPv6 configuration
183    #[cfg(feature = "proto-ipv6")]
184    pub ipv6: ConfigV6,
185}
186
187impl Config {
188    /// IPv4 configuration with static addressing.
189    #[cfg(feature = "proto-ipv4")]
190    pub const fn ipv4_static(config: StaticConfigV4) -> Self {
191        Self {
192            ipv4: ConfigV4::Static(config),
193            #[cfg(feature = "proto-ipv6")]
194            ipv6: ConfigV6::None,
195        }
196    }
197
198    /// IPv6 configuration with static addressing.
199    #[cfg(feature = "proto-ipv6")]
200    pub const fn ipv6_static(config: StaticConfigV6) -> Self {
201        Self {
202            #[cfg(feature = "proto-ipv4")]
203            ipv4: ConfigV4::None,
204            ipv6: ConfigV6::Static(config),
205        }
206    }
207
208    /// IPv4 configuration with dynamic addressing.
209    ///
210    /// # Example
211    /// ```rust
212    /// # use embassy_net::Config;
213    /// let _cfg = Config::dhcpv4(Default::default());
214    /// ```
215    #[cfg(feature = "dhcpv4")]
216    pub const fn dhcpv4(config: DhcpConfig) -> Self {
217        Self {
218            ipv4: ConfigV4::Dhcp(config),
219            #[cfg(feature = "proto-ipv6")]
220            ipv6: ConfigV6::None,
221        }
222    }
223
224    /// Slaac configuration with dynamic addressing.
225    #[cfg(feature = "slaac")]
226    pub const fn slaac() -> Self {
227        Self {
228            #[cfg(feature = "proto-ipv4")]
229            ipv4: ConfigV4::None,
230            ipv6: ConfigV6::Slaac,
231        }
232    }
233}
234
235/// Network stack IPv4 configuration.
236#[cfg(feature = "proto-ipv4")]
237#[derive(Debug, Clone, Default, PartialEq, Eq)]
238#[cfg_attr(feature = "defmt", derive(defmt::Format))]
239pub enum ConfigV4 {
240    /// Do not configure IPv4.
241    #[default]
242    None,
243    /// Use a static IPv4 address configuration.
244    Static(StaticConfigV4),
245    /// Use DHCP to obtain an IP address configuration.
246    #[cfg(feature = "dhcpv4")]
247    Dhcp(DhcpConfig),
248}
249
250/// Network stack IPv6 configuration.
251#[cfg(feature = "proto-ipv6")]
252#[derive(Debug, Clone, Default, PartialEq, Eq)]
253#[cfg_attr(feature = "defmt", derive(defmt::Format))]
254pub enum ConfigV6 {
255    /// Do not configure IPv6.
256    #[default]
257    None,
258    /// Use a static IPv6 address configuration.
259    Static(StaticConfigV6),
260    /// Use SLAAC for IPv6 address configuration.
261    #[cfg(feature = "slaac")]
262    Slaac,
263}
264
265/// Network stack runner.
266///
267/// You must call [`Runner::run()`] in a background task for the network stack to work.
268pub struct Runner<'d, D: Driver> {
269    driver: D,
270    stack: Stack<'d>,
271}
272
273/// Network stack handle
274///
275/// Use this to create sockets. It's `Copy`, so you can pass
276/// it by value instead of by reference.
277#[derive(Copy, Clone)]
278pub struct Stack<'d> {
279    inner: &'d RefCell<Inner>,
280}
281
282pub(crate) struct Inner {
283    pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased.
284    pub(crate) iface: Interface,
285    /// Waker used for triggering polls.
286    pub(crate) waker: WakerRegistration,
287    /// Waker used for waiting for link up or config up.
288    state_waker: WakerRegistration,
289    hardware_address: HardwareAddress,
290    next_local_port: u16,
291    link_up: bool,
292    #[cfg(feature = "proto-ipv4")]
293    static_v4: Option<StaticConfigV4>,
294    #[cfg(feature = "proto-ipv6")]
295    static_v6: Option<StaticConfigV6>,
296    #[cfg(feature = "slaac")]
297    slaac: bool,
298    #[cfg(feature = "dhcpv4")]
299    dhcp_socket: Option<SocketHandle>,
300    #[cfg(feature = "dns")]
301    dns_socket: SocketHandle,
302    #[cfg(feature = "dns")]
303    dns_waker: WakerRegistration,
304    #[cfg(feature = "dhcpv4-hostname")]
305    hostname: *mut HostnameResources,
306}
307
308fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> {
309    x
310}
311
312/// Create a new network stack.
313pub fn new<'d, D: Driver, const SOCK: usize>(
314    mut driver: D,
315    config: Config,
316    resources: &'d mut StackResources<SOCK>,
317    random_seed: u64,
318) -> (Stack<'d>, Runner<'d, D>) {
319    let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address());
320    let mut iface_cfg = smoltcp::iface::Config::new(hardware_address);
321    iface_cfg.random_seed = random_seed;
322    #[cfg(feature = "slaac")]
323    {
324        iface_cfg.slaac = matches!(config.ipv6, ConfigV6::Slaac);
325    }
326
327    #[allow(unused_mut)]
328    let mut iface = Interface::new(
329        iface_cfg,
330        &mut DriverAdapter {
331            inner: &mut driver,
332            cx: None,
333            medium,
334            tx_exhausted: false,
335        },
336        instant_to_smoltcp(Instant::now()),
337    );
338
339    unsafe fn transmute_slice<T>(x: &mut [T]) -> &'static mut [T] {
340        core::mem::transmute(x)
341    }
342
343    let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]);
344    #[allow(unused_mut)]
345    let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) });
346
347    let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
348
349    #[cfg(feature = "dns")]
350    let dns_socket = sockets.add(dns::Socket::new(
351        &[],
352        managed::ManagedSlice::Borrowed(unsafe {
353            transmute_slice(resources.queries.write([const { None }; MAX_QUERIES]))
354        }),
355    ));
356
357    let mut inner = Inner {
358        sockets,
359        iface,
360        waker: WakerRegistration::new(),
361        state_waker: WakerRegistration::new(),
362        next_local_port,
363        hardware_address,
364        link_up: false,
365        #[cfg(feature = "proto-ipv4")]
366        static_v4: None,
367        #[cfg(feature = "proto-ipv6")]
368        static_v6: None,
369        #[cfg(feature = "slaac")]
370        slaac: false,
371        #[cfg(feature = "dhcpv4")]
372        dhcp_socket: None,
373        #[cfg(feature = "dns")]
374        dns_socket,
375        #[cfg(feature = "dns")]
376        dns_waker: WakerRegistration::new(),
377        #[cfg(feature = "dhcpv4-hostname")]
378        hostname: &mut resources.hostname,
379    };
380
381    #[cfg(feature = "proto-ipv4")]
382    inner.set_config_v4(config.ipv4);
383    #[cfg(feature = "proto-ipv6")]
384    inner.set_config_v6(config.ipv6);
385    inner.apply_static_config();
386
387    let inner = &*resources.inner.write(RefCell::new(inner));
388    let stack = Stack { inner };
389    (stack, Runner { driver, stack })
390}
391
392fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) {
393    match addr {
394        #[cfg(feature = "medium-ethernet")]
395        driver::HardwareAddress::Ethernet(eth) => (HardwareAddress::Ethernet(EthernetAddress(eth)), Medium::Ethernet),
396        #[cfg(feature = "medium-ieee802154")]
397        driver::HardwareAddress::Ieee802154(ieee) => (
398            HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)),
399            Medium::Ieee802154,
400        ),
401        #[cfg(feature = "medium-ip")]
402        driver::HardwareAddress::Ip => (HardwareAddress::Ip, Medium::Ip),
403
404        #[allow(unreachable_patterns)]
405        _ => panic!(
406            "Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.",
407            addr
408        ),
409    }
410}
411
412impl<'d> Stack<'d> {
413    fn with<R>(&self, f: impl FnOnce(&Inner) -> R) -> R {
414        f(&self.inner.borrow())
415    }
416
417    fn with_mut<R>(&self, f: impl FnOnce(&mut Inner) -> R) -> R {
418        f(&mut self.inner.borrow_mut())
419    }
420
421    /// Get the hardware address of the network interface.
422    pub fn hardware_address(&self) -> HardwareAddress {
423        self.with(|i| i.hardware_address)
424    }
425
426    /// Check whether the link is up.
427    pub fn is_link_up(&self) -> bool {
428        self.with(|i| i.link_up)
429    }
430
431    /// Check whether the network stack has a valid IP configuration.
432    /// This is true if the network stack has a static IP configuration or if DHCP has completed
433    pub fn is_config_up(&self) -> bool {
434        let v4_up;
435        let v6_up;
436
437        #[cfg(feature = "proto-ipv4")]
438        {
439            v4_up = self.config_v4().is_some();
440        }
441        #[cfg(not(feature = "proto-ipv4"))]
442        {
443            v4_up = false;
444        }
445
446        #[cfg(feature = "proto-ipv6")]
447        {
448            v6_up = self.config_v6().is_some();
449        }
450        #[cfg(not(feature = "proto-ipv6"))]
451        {
452            v6_up = false;
453        }
454
455        v4_up || v6_up
456    }
457
458    /// Wait for the network device to obtain a link signal.
459    pub async fn wait_link_up(&self) {
460        self.wait(|| self.is_link_up()).await
461    }
462
463    /// Wait for the network device to lose link signal.
464    pub async fn wait_link_down(&self) {
465        self.wait(|| !self.is_link_up()).await
466    }
467
468    /// Wait for the network stack to obtain a valid IP configuration.
469    ///
470    /// ## Notes:
471    /// - Ensure [`Runner::run`] has been started before using this function.
472    ///
473    /// - This function may never return (e.g. if no configuration is obtained through DHCP).
474    /// The caller is supposed to handle a timeout for this case.
475    ///
476    /// ## Example
477    /// ```ignore
478    /// let config = embassy_net::Config::dhcpv4(Default::default());
479    /// // Init network stack
480    /// // NOTE: DHCP and DNS need one socket slot if enabled. This is why we're
481    /// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP).
482    /// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it.
483    /// static RESOURCES: StaticCell<embassy_net::StackResources<3>> = StaticCell::new();
484    /// let (stack, runner) = embassy_net::new(
485    ///    driver,
486    ///    config,
487    ///    RESOURCES.init(embassy_net::StackResources::new()),
488    ///    seed
489    /// );
490    /// // Launch network task that runs `runner.run().await`
491    /// spawner.spawn(net_task(runner).unwrap());
492    /// // Wait for DHCP config
493    /// stack.wait_config_up().await;
494    /// // use the network stack
495    /// // ...
496    /// ```
497    pub async fn wait_config_up(&self) {
498        self.wait(|| self.is_config_up()).await
499    }
500
501    /// Wait for the network stack to lose a valid IP configuration.
502    pub async fn wait_config_down(&self) {
503        self.wait(|| !self.is_config_up()).await
504    }
505
506    fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future<Output = ()> + 'a {
507        poll_fn(move |cx| {
508            if predicate() {
509                Poll::Ready(())
510            } else {
511                // If the config is not up, we register a waker that is woken up
512                // when a config is applied (static, slaac or DHCP).
513                trace!("Waiting for config up");
514
515                self.with_mut(|i| {
516                    i.state_waker.register(cx.waker());
517                });
518
519                Poll::Pending
520            }
521        })
522    }
523
524    /// Get the current IPv4 configuration.
525    ///
526    /// If using DHCP, this will be None if DHCP hasn't been able to
527    /// acquire an IP address, or Some if it has.
528    #[cfg(feature = "proto-ipv4")]
529    pub fn config_v4(&self) -> Option<StaticConfigV4> {
530        self.with(|i| i.static_v4.clone())
531    }
532
533    /// Get the current IPv6 configuration.
534    #[cfg(feature = "proto-ipv6")]
535    pub fn config_v6(&self) -> Option<StaticConfigV6> {
536        self.with(|i| i.static_v6.clone())
537    }
538
539    /// Set the IPv4 configuration.
540    #[cfg(feature = "proto-ipv4")]
541    pub fn set_config_v4(&self, config: ConfigV4) {
542        self.with_mut(|i| {
543            i.set_config_v4(config);
544            i.apply_static_config();
545        })
546    }
547
548    /// Set the IPv6 configuration.
549    #[cfg(feature = "proto-ipv6")]
550    pub fn set_config_v6(&self, config: ConfigV6) {
551        self.with_mut(|i| {
552            i.set_config_v6(config);
553            i.apply_static_config();
554        })
555    }
556
557    /// Make a query for a given name and return the corresponding IP addresses.
558    #[cfg(feature = "dns")]
559    pub async fn dns_query(
560        &self,
561        name: &str,
562        qtype: dns::DnsQueryType,
563    ) -> Result<Vec<IpAddress, { smoltcp::config::DNS_MAX_RESULT_COUNT }>, dns::Error> {
564        // For A and AAAA queries we try detect whether `name` is just an IP address
565        match qtype {
566            #[cfg(feature = "proto-ipv4")]
567            dns::DnsQueryType::A => {
568                if let Ok(ip) = name.parse().map(IpAddress::Ipv4) {
569                    return Ok([ip].into_iter().collect());
570                }
571            }
572            #[cfg(feature = "proto-ipv6")]
573            dns::DnsQueryType::Aaaa => {
574                if let Ok(ip) = name.parse().map(IpAddress::Ipv6) {
575                    return Ok([ip].into_iter().collect());
576                }
577            }
578            _ => {}
579        }
580
581        let query = poll_fn(|cx| {
582            self.with_mut(|i| {
583                let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
584                match socket.start_query(i.iface.context(), name, qtype) {
585                    Ok(handle) => {
586                        i.waker.wake();
587                        Poll::Ready(Ok(handle))
588                    }
589                    Err(dns::StartQueryError::NoFreeSlot) => {
590                        i.dns_waker.register(cx.waker());
591                        Poll::Pending
592                    }
593                    Err(e) => Poll::Ready(Err(e)),
594                }
595            })
596        })
597        .await?;
598
599        #[must_use = "to delay the drop handler invocation to the end of the scope"]
600        struct OnDrop<F: FnOnce()> {
601            f: core::mem::MaybeUninit<F>,
602        }
603
604        impl<F: FnOnce()> OnDrop<F> {
605            fn new(f: F) -> Self {
606                Self {
607                    f: core::mem::MaybeUninit::new(f),
608                }
609            }
610
611            fn defuse(self) {
612                core::mem::forget(self)
613            }
614        }
615
616        impl<F: FnOnce()> Drop for OnDrop<F> {
617            fn drop(&mut self) {
618                unsafe { self.f.as_ptr().read()() }
619            }
620        }
621
622        let drop = OnDrop::new(|| {
623            self.with_mut(|i| {
624                let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
625                socket.cancel_query(query);
626                i.waker.wake();
627                i.dns_waker.wake();
628            })
629        });
630
631        let res = poll_fn(|cx| {
632            self.with_mut(|i| {
633                let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
634                match socket.get_query_result(query) {
635                    Ok(addrs) => {
636                        i.dns_waker.wake();
637                        Poll::Ready(Ok(addrs))
638                    }
639                    Err(dns::GetQueryResultError::Pending) => {
640                        socket.register_query_waker(query, cx.waker());
641                        Poll::Pending
642                    }
643                    Err(e) => {
644                        i.dns_waker.wake();
645                        Poll::Ready(Err(e.into()))
646                    }
647                }
648            })
649        })
650        .await;
651
652        drop.defuse();
653
654        res
655    }
656}
657
658#[cfg(feature = "multicast")]
659impl<'d> Stack<'d> {
660    /// Join a multicast group.
661    pub fn join_multicast_group(&self, addr: impl Into<IpAddress>) -> Result<(), MulticastError> {
662        self.with_mut(|i| i.iface.join_multicast_group(addr))
663    }
664
665    /// Leave a multicast group.
666    pub fn leave_multicast_group(&self, addr: impl Into<IpAddress>) -> Result<(), MulticastError> {
667        self.with_mut(|i| i.iface.leave_multicast_group(addr))
668    }
669
670    /// Get whether the network stack has joined the given multicast group.
671    pub fn has_multicast_group(&self, addr: impl Into<IpAddress>) -> bool {
672        self.with(|i| i.iface.has_multicast_group(addr))
673    }
674}
675
676impl Inner {
677    #[cfg(feature = "slaac")]
678    fn get_link_local_address(&self) -> IpCidr {
679        let ll_prefix = Ipv6Cidr::new(Ipv6Cidr::LINK_LOCAL_PREFIX.address(), 64);
680        Ipv6Cidr::from_link_prefix(&ll_prefix, self.hardware_address)
681            .unwrap()
682            .into()
683    }
684
685    #[allow(clippy::absurd_extreme_comparisons)]
686    pub fn get_local_port(&mut self) -> u16 {
687        let res = self.next_local_port;
688        self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
689        res
690    }
691
692    #[cfg(feature = "proto-ipv4")]
693    pub fn set_config_v4(&mut self, config: ConfigV4) {
694        // Handle static config.
695        self.static_v4 = match config.clone() {
696            ConfigV4::None => None,
697            #[cfg(feature = "dhcpv4")]
698            ConfigV4::Dhcp(_) => None,
699            ConfigV4::Static(c) => Some(c),
700        };
701
702        // Handle DHCP config.
703        #[cfg(feature = "dhcpv4")]
704        match config {
705            ConfigV4::Dhcp(c) => {
706                // Create the socket if it doesn't exist.
707                if self.dhcp_socket.is_none() {
708                    let socket = smoltcp::socket::dhcpv4::Socket::new();
709                    let handle = self.sockets.add(socket);
710                    self.dhcp_socket = Some(handle);
711                }
712
713                // Configure it
714                let socket = self.sockets.get_mut::<dhcpv4::Socket>(unwrap!(self.dhcp_socket));
715                socket.set_ignore_naks(c.ignore_naks);
716                socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp));
717                socket.set_ports(c.server_port, c.client_port);
718                socket.set_retry_config(c.retry_config);
719
720                socket.set_outgoing_options(&[]);
721                #[cfg(feature = "dhcpv4-hostname")]
722                if let Some(h) = c.hostname {
723                    // safety:
724                    // - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference.
725                    // - we know this pointer lives for as long as the stack exists, because `new()` borrows
726                    //   the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp.
727                    let hostname = unsafe { &mut *self.hostname };
728
729                    // create data
730                    let data = hostname.data.write([0; MAX_HOSTNAME_LEN]);
731                    data[..h.len()].copy_from_slice(h.as_bytes());
732                    let data: &[u8] = &data[..h.len()];
733
734                    // set the option.
735                    let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 });
736                    socket.set_outgoing_options(core::slice::from_ref(option));
737                }
738
739                socket.reset();
740            }
741            _ => {
742                // Remove DHCP socket if any.
743                if let Some(socket) = self.dhcp_socket {
744                    self.sockets.remove(socket);
745                    self.dhcp_socket = None;
746                }
747            }
748        }
749    }
750
751    #[cfg(feature = "proto-ipv6")]
752    pub fn set_config_v6(&mut self, config: ConfigV6) {
753        #[cfg(feature = "slaac")]
754        {
755            self.slaac = matches!(config, ConfigV6::Slaac);
756        }
757        self.static_v6 = match config {
758            ConfigV6::None => None,
759            ConfigV6::Static(c) => Some(c),
760            #[cfg(feature = "slaac")]
761            ConfigV6::Slaac => None,
762        };
763    }
764
765    fn apply_static_config(&mut self) {
766        let mut addrs = Vec::new();
767        #[cfg(feature = "dns")]
768        let mut dns_servers: Vec<_, 6> = Vec::new();
769        #[cfg(feature = "proto-ipv4")]
770        let mut gateway_v4 = None;
771        #[cfg(feature = "proto-ipv6")]
772        let mut gateway_v6 = None;
773
774        #[cfg(feature = "proto-ipv4")]
775        if let Some(config) = &self.static_v4 {
776            debug!("IPv4: UP");
777            debug!("   IP address:      {:?}", config.address);
778            debug!("   Default gateway: {:?}", config.gateway);
779
780            unwrap!(addrs.push(IpCidr::Ipv4(config.address)).ok());
781            gateway_v4 = config.gateway;
782            #[cfg(feature = "dns")]
783            for s in &config.dns_servers {
784                debug!("   DNS server:      {:?}", s);
785                unwrap!(dns_servers.push(s.clone().into()).ok());
786            }
787        } else {
788            info!("IPv4: DOWN");
789        }
790
791        #[cfg(feature = "proto-ipv6")]
792        if let Some(config) = &self.static_v6 {
793            debug!("IPv6: UP");
794            debug!("   IP address:      {:?}", config.address);
795            debug!("   Default gateway: {:?}", config.gateway);
796
797            unwrap!(addrs.push(IpCidr::Ipv6(config.address)).ok());
798            gateway_v6 = config.gateway;
799            #[cfg(feature = "dns")]
800            for s in &config.dns_servers {
801                debug!("   DNS server:      {:?}", s);
802                unwrap!(dns_servers.push(s.clone().into()).ok());
803            }
804        } else {
805            info!("IPv6: DOWN");
806        }
807
808        // Apply addresses
809        self.iface.update_ip_addrs(|a| {
810            *a = addrs;
811        });
812
813        // Add the link local-address
814        #[cfg(feature = "slaac")]
815        {
816            let ll_address = self.get_link_local_address();
817            self.iface.update_ip_addrs(|a| {
818                let _ = a.push(ll_address);
819            })
820        }
821
822        // Apply gateways
823        #[cfg(feature = "proto-ipv4")]
824        if let Some(gateway) = gateway_v4 {
825            unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway));
826        } else {
827            self.iface.routes_mut().remove_default_ipv4_route();
828        }
829        #[cfg(feature = "proto-ipv6")]
830        if let Some(gateway) = gateway_v6 {
831            unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway));
832        } else {
833            self.iface.routes_mut().remove_default_ipv6_route();
834        }
835
836        // Apply DNS servers
837        #[cfg(feature = "dns")]
838        if !dns_servers.is_empty() {
839            let count = if dns_servers.len() > DNS_MAX_SERVER_COUNT {
840                warn!("Number of DNS servers exceeds DNS_MAX_SERVER_COUNT, truncating list.");
841                DNS_MAX_SERVER_COUNT
842            } else {
843                dns_servers.len()
844            };
845            self.sockets
846                .get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket)
847                .update_servers(&dns_servers[..count]);
848        }
849
850        self.state_waker.wake();
851    }
852
853    fn poll<D: Driver>(&mut self, cx: &mut Context<'_>, driver: &mut D) {
854        self.waker.register(cx.waker());
855
856        let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address());
857
858        #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
859        {
860            let do_set = match medium {
861                #[cfg(feature = "medium-ethernet")]
862                Medium::Ethernet => true,
863                #[cfg(feature = "medium-ieee802154")]
864                Medium::Ieee802154 => true,
865                #[allow(unreachable_patterns)]
866                _ => false,
867            };
868            if do_set {
869                self.iface.set_hardware_addr(_hardware_addr);
870            }
871        }
872
873        let timestamp = instant_to_smoltcp(Instant::now());
874        let mut smoldev = DriverAdapter {
875            cx: Some(cx),
876            inner: driver,
877            medium,
878            tx_exhausted: false,
879        };
880        self.iface.poll(timestamp, &mut smoldev, &mut self.sockets);
881        let tx_exhausted = smoldev.tx_exhausted;
882
883        // Update link up
884        let old_link_up = self.link_up;
885        self.link_up = driver.link_state(cx) == LinkState::Up;
886
887        // Print when changed
888        if old_link_up != self.link_up {
889            info!("link_up = {:?}", self.link_up);
890            self.state_waker.wake();
891        }
892
893        #[allow(unused_mut)]
894        let mut configure = false;
895
896        #[cfg(feature = "dhcpv4")]
897        {
898            configure |= if let Some(dhcp_handle) = self.dhcp_socket {
899                let socket = self.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
900
901                if self.link_up {
902                    if old_link_up != self.link_up {
903                        socket.reset();
904                    }
905                    match socket.poll() {
906                        None => false,
907                        Some(dhcpv4::Event::Deconfigured) => {
908                            self.static_v4 = None;
909                            true
910                        }
911                        Some(dhcpv4::Event::Configured(config)) => {
912                            self.static_v4 = Some(StaticConfigV4 {
913                                address: config.address,
914                                gateway: config.router,
915                                dns_servers: config.dns_servers,
916                            });
917                            true
918                        }
919                    }
920                } else if old_link_up {
921                    socket.reset();
922                    self.static_v4 = None;
923                    true
924                } else {
925                    false
926                }
927            } else {
928                false
929            }
930        }
931
932        #[cfg(feature = "slaac")]
933        if self.slaac && self.iface.slaac_updated_at() == timestamp {
934            let ipv6_address = self.iface.ip_addrs().iter().find_map(|addr| match addr {
935                IpCidr::Ipv6(ip6_address) if !Ipv6Cidr::LINK_LOCAL_PREFIX.contains_addr(&ip6_address.address()) => {
936                    Some(ip6_address)
937                }
938                _ => None,
939            });
940            self.static_v6 = if let Some(address) = ipv6_address {
941                let gateway = self
942                    .iface
943                    .routes()
944                    .get_default_ipv6_route()
945                    .map(|r| match r.via_router {
946                        IpAddress::Ipv6(gateway) => Some(gateway),
947                        #[cfg(feature = "proto-ipv4")]
948                        _ => None,
949                    })
950                    .flatten();
951                let config = StaticConfigV6 {
952                    address: *address,
953                    gateway,
954                    dns_servers: Vec::new(), // RDNSS not (yet) supported by smoltcp.
955                };
956                Some(config)
957            } else {
958                None
959            };
960            configure = true;
961        }
962
963        if configure {
964            self.apply_static_config()
965        }
966
967        if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets)
968            && !tx_exhausted
969        {
970            let t = pin!(Timer::at(instant_from_smoltcp(poll_at)));
971            if t.poll(cx).is_ready() {
972                cx.waker().wake_by_ref();
973            }
974        }
975    }
976}
977
978impl<'d, D: Driver> Runner<'d, D> {
979    /// Run the network stack.
980    ///
981    /// You must call this in a background task, to process network events.
982    pub async fn run(&mut self) -> ! {
983        poll_fn(|cx| {
984            self.stack.with_mut(|i| i.poll(cx, &mut self.driver));
985            Poll::<()>::Pending
986        })
987        .await;
988        unreachable!()
989    }
990}