Skip to main content

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