embassy_net/
lib.rs

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