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