Update to latest embassy, atomic-pool, smoltcp

This commit is contained in:
Dario Nieuwenhuis 2021-04-07 19:06:45 +02:00
parent 9bee576fd2
commit 9c5a8b945a
13 changed files with 182 additions and 378 deletions

View file

@ -42,8 +42,8 @@ opt-level = 0
overflow-checks = false
[patch.crates-io]
embassy = { git = "https://github.com/akiles/embassy" }
embassy-std = { git = "https://github.com/akiles/embassy" }
embassy-macros = { git = "https://github.com/akiles/embassy" }
embassy-traits = { git = "https://github.com/akiles/embassy" }
smoltcp = { git = "https://github.com/akiles/smoltcp" }
embassy = { git = "https://github.com/embassy-rs/embassy" }
embassy-std = { git = "https://github.com/embassy-rs/embassy" }
embassy-macros = { git = "https://github.com/embassy-rs/embassy" }
embassy-traits = { git = "https://github.com/embassy-rs/embassy" }
smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp", rev="9ce3d9505ef5455bb049713b9262561d78ebf330" }

View file

@ -8,10 +8,11 @@ edition = "2018"
heapless = { version = "0.5.6", default-features = false }
embassy = { version = "0.1.0", features=["std", "log"] }
embassy-std = { version = "0.1.0" }
embassy-net = { version = "0.1.0", path = "../embassy-net", features=["std", "log"] }
embassy-net = { version = "0.1.0", path = "../embassy-net", features=["std", "log", "tcp", "dhcpv4"] }
env_logger = "0.8.2"
log = "0.4.11"
futures = "0.3.8"
libc = "0.2.81"
async-io = "1.3.1"
smoltcp = { version = "0.6.0", default-features = false }
smoltcp = { version = "0.7.0", default-features = false }
clap = { version = "3.0.0-beta.2", features = ["derive"] }

View file

@ -1,6 +1,10 @@
#![feature(type_alias_impl_trait)]
#![feature(min_type_alias_impl_trait)]
#![feature(impl_trait_in_bindings)]
#![allow(incomplete_features)]
use embassy::executor::{task, Spawner};
use clap::{AppSettings, Clap};
use embassy::executor::Spawner;
use embassy::io::AsyncWriteExt;
use embassy::util::Forever;
use embassy_net::*;
@ -13,25 +17,39 @@ mod tuntap;
use crate::tuntap::TunTapDevice;
static DEVICE: Forever<TunTapDevice> = Forever::new();
static CONFIG: Forever<StaticConfigurator> = Forever::new();
static CONFIG: Forever<DhcpConfigurator> = Forever::new();
#[task]
#[derive(Clap)]
#[clap(version = "1.0")]
#[clap(setting = AppSettings::ColoredHelp)]
struct Opts {
/// TAP device name
#[clap(long, default_value = "tap0")]
tap: String,
}
#[embassy::task]
async fn net_task() {
embassy_net::run().await
}
#[task]
#[embassy::task]
async fn main_task(spawner: Spawner) {
let opts: Opts = Opts::parse();
// Init network device
let device = TunTapDevice::new("tap0").unwrap();
let device = TunTapDevice::new(&opts.tap).unwrap();
// Static IP configuration
let config = StaticConfigurator::new(UpConfig {
address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 1), 24),
let config = StaticConfigurator::new(Config {
address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24),
dns_servers: Vec::new(),
gateway: Ipv4Address::new(192, 168, 69, 100),
gateway: Some(Ipv4Address::new(192, 168, 69, 1)),
});
// DHCP configruation
let config = DhcpConfigurator::new();
// Init network stack
embassy_net::init(DEVICE.put(device), CONFIG.put(config));
@ -45,7 +63,7 @@ async fn main_task(spawner: Spawner) {
socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10)));
let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 8000);
let remote_endpoint = (Ipv4Address::new(192, 168, 69, 74), 8000);
info!("connecting to {:?}...", remote_endpoint);
let r = socket.connect(remote_endpoint).await;
if let Err(e) = r {

View file

@ -1,5 +1,4 @@
use async_io::Async;
use embassy::util::WakerRegistration;
use libc;
use log::*;
use smoltcp::wire::EthernetFrame;
@ -130,20 +129,21 @@ impl io::Write for TunTap {
pub struct TunTapDevice {
device: Async<TunTap>,
waker: WakerRegistration,
waker: Option<Waker>,
}
impl TunTapDevice {
pub fn new(name: &str) -> io::Result<TunTapDevice> {
Ok(Self {
device: Async::new(TunTap::new(name)?)?,
waker: WakerRegistration::new(),
waker: None,
})
}
}
use core::task::Waker;
use embassy_net::{DeviceCapabilities, LinkState, Packet, PacketBox, PacketBuf};
use embassy_net::{DeviceCapabilities, LinkState, Packet, PacketBox, PacketBoxExt, PacketBuf};
use std::task::Context;
impl crate::Device for TunTapDevice {
fn is_transmit_ready(&mut self) -> bool {
@ -169,7 +169,8 @@ impl crate::Device for TunTapDevice {
return Some(pkt.slice(0..n));
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
let ready = if let Some(mut cx) = self.waker.context() {
let ready = if let Some(w) = self.waker.as_ref() {
let mut cx = Context::from_waker(w);
let ready = self.device.poll_readable(&mut cx).is_ready();
ready
} else {
@ -184,8 +185,28 @@ impl crate::Device for TunTapDevice {
}
}
fn register_waker(&mut self, waker: &Waker) {
self.waker.register(waker)
fn register_waker(&mut self, w: &Waker) {
match self.waker {
// Optimization: If both the old and new Wakers wake the same task, we can simply
// keep the old waker, skipping the clone. (In most executor implementations,
// cloning a waker is somewhat expensive, comparable to cloning an Arc).
Some(ref w2) if (w2.will_wake(w)) => {}
_ => {
// clone the new waker and store it
if let Some(old_waker) = core::mem::replace(&mut self.waker, Some(w.clone())) {
// We had a waker registered for another task. Wake it, so the other task can
// reregister itself if it's still interested.
//
// If two tasks are waiting on the same thing concurrently, this will cause them
// to wake each other in a loop fighting over this WakerRegistration. This wastes
// CPU but things will still work.
//
// If the user wants to have two tasks waiting on the same thing they should use
// a more appropriate primitive that can store multiple wakers.
old_waker.wake()
}
}
}
}
fn capabilities(&mut self) -> DeviceCapabilities {

View file

@ -12,6 +12,9 @@ defmt-info = []
defmt-warn = []
defmt-error = []
tcp = ["smoltcp/socket-tcp"]
dhcpv4 = ["smoltcp/socket-dhcpv4"]
[dependencies]
defmt = { version = "0.2.0", optional = true }
@ -25,9 +28,10 @@ as-slice = { version = "0.1.4" }
generic-array = { version = "0.14.4", default-features = false }
stable_deref_trait = { version = "1.2.0", default-features = false }
futures = { version = "0.3.5", default-features = false, features = [ "async-await" ]}
atomic-pool = "0.1.0"
[dependencies.smoltcp]
version = "0.6.0"
version = "0.7.0"
#git = "https://github.com/akiles/smoltcp"
#rev = "00952e2c5cdf5667a1dfb6142258055f58d3851c"
default-features = false
@ -35,12 +39,12 @@ features = [
"medium-ethernet",
"medium-ip",
"proto-ipv4",
"proto-dhcpv4",
#"proto-dhcpv4",
#"proto-igmp",
#"proto-ipv6",
#"socket-raw",
#"socket-icmp",
#"socket-udp",
"socket-tcp",
#"socket-tcp",
"async",
]

View file

@ -1,80 +1,59 @@
use embassy::util::Forever;
use heapless::Vec;
use smoltcp::dhcp::Dhcpv4Client;
use smoltcp::socket::{RawPacketMetadata, RawSocketBuffer};
use smoltcp::socket::{Dhcpv4Event, Dhcpv4Socket, SocketHandle};
use smoltcp::time::Instant;
use smoltcp::wire::Ipv4Address;
use super::*;
use crate::device::LinkState;
use crate::fmt::*;
use crate::{Interface, SocketSet};
pub struct DhcpResources {
rx_buffer: [u8; 900],
tx_buffer: [u8; 600],
rx_meta: [RawPacketMetadata; 1],
tx_meta: [RawPacketMetadata; 1],
}
pub struct DhcpConfigurator {
client: Option<Dhcpv4Client>,
handle: Option<SocketHandle>,
}
impl DhcpConfigurator {
pub fn new() -> Self {
Self { client: None }
Self { handle: None }
}
}
static DHCP_RESOURCES: Forever<DhcpResources> = Forever::new();
impl Configurator for DhcpConfigurator {
fn poll(
&mut self,
iface: &mut Interface,
sockets: &mut SocketSet,
timestamp: Instant,
) -> Option<Config> {
if self.client.is_none() {
let res = DHCP_RESOURCES.put(DhcpResources {
rx_buffer: [0; 900],
tx_buffer: [0; 600],
rx_meta: [RawPacketMetadata::EMPTY; 1],
tx_meta: [RawPacketMetadata::EMPTY; 1],
});
let rx_buffer = RawSocketBuffer::new(&mut res.rx_meta[..], &mut res.rx_buffer[..]);
let tx_buffer = RawSocketBuffer::new(&mut res.tx_meta[..], &mut res.tx_buffer[..]);
let dhcp = Dhcpv4Client::new(sockets, rx_buffer, tx_buffer, timestamp);
info!("created dhcp");
self.client = Some(dhcp)
_timestamp: Instant,
) -> Event {
if self.handle.is_none() {
let handle = sockets.add(Dhcpv4Socket::new());
self.handle = Some(handle)
}
let client = self.client.as_mut().unwrap();
let mut socket = sockets.get::<Dhcpv4Socket>(self.handle.unwrap());
let link_up = iface.device_mut().device.link_state() == LinkState::Up;
if !link_up {
client.reset(timestamp);
return Some(Config::Down);
socket.reset();
return Event::Deconfigured;
}
let config = client.poll(iface, sockets, timestamp).unwrap_or(None)?;
match socket.poll() {
Dhcpv4Event::NoChange => Event::NoChange,
Dhcpv4Event::Deconfigured => Event::Deconfigured,
Dhcpv4Event::Configured(config) => {
let mut dns_servers = Vec::new();
for s in &config.dns_servers {
if let Some(addr) = s {
dns_servers.push(addr.clone()).unwrap();
}
}
if config.address.is_none() {
return Some(Config::Down);
}
let mut dns_servers = Vec::new();
for s in &config.dns_servers {
if let Some(addr) = s {
dns_servers.push(addr.clone()).unwrap();
Event::Configured(Config {
address: config.address,
gateway: config.router,
dns_servers,
})
}
}
return Some(Config::Up(UpConfig {
address: config.address.unwrap(),
gateway: config.router.unwrap_or(Ipv4Address::UNSPECIFIED),
dns_servers,
}));
}
}

View file

@ -6,29 +6,33 @@ use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
use crate::fmt::*;
use crate::{Interface, SocketSet};
mod dhcp;
mod statik;
pub use dhcp::DhcpConfigurator;
pub use statik::StaticConfigurator;
#[cfg(feature = "dhcpv4")]
mod dhcp;
#[cfg(feature = "dhcpv4")]
pub use dhcp::DhcpConfigurator;
/// Return value for the `Configurator::poll` function
#[derive(Debug, Clone)]
pub enum Config {
Down,
Up(UpConfig),
pub enum Event {
/// No change has occured to the configuration.
NoChange,
/// Configuration has been lost (for example, DHCP lease has expired)
Deconfigured,
/// Configuration has been newly acquired, or modified.
Configured(Config),
}
#[derive(Debug, Clone)]
pub struct UpConfig {
pub struct Config {
pub address: Ipv4Cidr,
pub gateway: Ipv4Address,
pub gateway: Option<Ipv4Address>,
pub dns_servers: Vec<Ipv4Address, U3>,
}
pub trait Configurator {
fn poll(
&mut self,
iface: &mut Interface,
sockets: &mut SocketSet,
timestamp: Instant,
) -> Option<Config>;
fn poll(&mut self, iface: &mut Interface, sockets: &mut SocketSet, timestamp: Instant)
-> Event;
}

View file

@ -5,12 +5,16 @@ use crate::fmt::*;
use crate::{Interface, SocketSet};
pub struct StaticConfigurator {
config: UpConfig,
config: Config,
returned: bool,
}
impl StaticConfigurator {
pub fn new(config: UpConfig) -> Self {
Self { config }
pub fn new(config: Config) -> Self {
Self {
config,
returned: false,
}
}
}
@ -20,7 +24,12 @@ impl Configurator for StaticConfigurator {
_iface: &mut Interface,
_sockets: &mut SocketSet,
_timestamp: Instant,
) -> Option<Config> {
Some(Config::Up(self.config.clone()))
) -> Event {
if self.returned {
Event::NoChange
} else {
self.returned = true;
Event::Configured(self.config.clone())
}
}
}

View file

@ -4,6 +4,7 @@ use smoltcp::phy::DeviceCapabilities;
use smoltcp::time::Instant as SmolInstant;
use crate::fmt::*;
use crate::packet_pool::PacketBoxExt;
use crate::Result;
use crate::{Packet, PacketBox, PacketBuf};

View file

@ -7,18 +7,19 @@
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
mod pool; // TODO extract to embassy, or to own crate
mod config;
mod device;
mod packet_pool;
mod stack;
mod tcp_socket;
pub use config::{Config, Configurator, DhcpConfigurator, StaticConfigurator, UpConfig};
pub use config::{Config, Configurator, DhcpConfigurator, StaticConfigurator};
pub use device::{Device, LinkState};
pub use packet_pool::{Packet, PacketBox, PacketBuf};
pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf};
pub use stack::{init, is_init, run};
#[cfg(feature = "tcp")]
mod tcp_socket;
#[cfg(feature = "tcp")]
pub use tcp_socket::TcpSocket;
// smoltcp reexports

View file

@ -1,7 +1,7 @@
use as_slice::{AsMutSlice, AsSlice};
use core::ops::{Deref, DerefMut, Range};
use super::pool::{BitPool, Box, StaticPool};
use atomic_pool::{pool, Box};
pub const MTU: usize = 1514;
pub const PACKET_POOL_SIZE: usize = 4;
@ -17,8 +17,12 @@ impl Packet {
}
}
impl Box<PacketPool> {
pub fn slice(self, range: Range<usize>) -> PacketBuf {
pub trait PacketBoxExt {
fn slice(self, range: Range<usize>) -> PacketBuf;
}
impl PacketBoxExt for PacketBox {
fn slice(self, range: Range<usize>) -> PacketBuf {
PacketBuf {
packet: self,
range,

View file

@ -1,245 +0,0 @@
#![macro_use]
use as_slice::{AsMutSlice, AsSlice};
use core::cmp;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicU32, Ordering};
use crate::fmt::{assert, *};
struct AtomicBitset<const N: usize>
where
[AtomicU32; (N + 31) / 32]: Sized,
{
used: [AtomicU32; (N + 31) / 32],
}
impl<const N: usize> AtomicBitset<N>
where
[AtomicU32; (N + 31) / 32]: Sized,
{
const fn new() -> Self {
const Z: AtomicU32 = AtomicU32::new(0);
Self {
used: [Z; (N + 31) / 32],
}
}
fn alloc(&self) -> Option<usize> {
for (i, val) in self.used.iter().enumerate() {
let res = val.fetch_update(Ordering::AcqRel, Ordering::Acquire, |val| {
let n = val.trailing_ones() as usize + i * 32;
if n >= N {
None
} else {
Some(val | (1 << n))
}
});
if let Ok(val) = res {
let n = val.trailing_ones() as usize + i * 32;
return Some(n);
}
}
None
}
fn free(&self, i: usize) {
assert!(i < N);
self.used[i / 32].fetch_and(!(1 << ((i % 32) as u32)), Ordering::AcqRel);
}
}
pub trait Pool<T> {
fn alloc(&self) -> Option<*mut T>;
unsafe fn free(&self, p: *mut T);
}
pub struct BitPool<T, const N: usize>
where
[AtomicU32; (N + 31) / 32]: Sized,
{
used: AtomicBitset<N>,
data: MaybeUninit<[T; N]>,
}
impl<T, const N: usize> BitPool<T, N>
where
[AtomicU32; (N + 31) / 32]: Sized,
{
pub const fn new() -> Self {
Self {
used: AtomicBitset::new(),
data: MaybeUninit::uninit(),
}
}
}
impl<T, const N: usize> Pool<T> for BitPool<T, N>
where
[AtomicU32; (N + 31) / 32]: Sized,
{
fn alloc(&self) -> Option<*mut T> {
let n = self.used.alloc()?;
let origin = self.data.as_ptr() as *mut T;
Some(unsafe { origin.add(n) })
}
/// safety: p must be a pointer obtained from self.alloc that hasn't been freed yet.
unsafe fn free(&self, p: *mut T) {
let origin = self.data.as_ptr() as *mut T;
let n = p.offset_from(origin);
assert!(n >= 0);
assert!((n as usize) < N);
self.used.free(n as usize);
}
}
pub trait StaticPool: 'static {
type Item: 'static;
type Pool: Pool<Self::Item>;
fn get() -> &'static Self::Pool;
}
pub struct Box<P: StaticPool> {
ptr: *mut P::Item,
}
impl<P: StaticPool> Box<P> {
pub fn new(item: P::Item) -> Option<Self> {
let p = match P::get().alloc() {
Some(p) => p,
None => {
warn!("alloc failed!");
return None;
}
};
//trace!("allocated {:u32}", p as u32);
unsafe { p.write(item) };
Some(Self { ptr: p })
}
}
impl<P: StaticPool> Drop for Box<P> {
fn drop(&mut self) {
unsafe {
//trace!("dropping {:u32}", self.ptr as u32);
self.ptr.drop_in_place();
P::get().free(self.ptr);
};
}
}
unsafe impl<P: StaticPool> Send for Box<P> where P::Item: Send {}
unsafe impl<P: StaticPool> Sync for Box<P> where P::Item: Sync {}
unsafe impl<P: StaticPool> stable_deref_trait::StableDeref for Box<P> {}
impl<P: StaticPool> AsSlice for Box<P>
where
P::Item: AsSlice,
{
type Element = <P::Item as AsSlice>::Element;
fn as_slice(&self) -> &[Self::Element] {
self.deref().as_slice()
}
}
impl<P: StaticPool> AsMutSlice for Box<P>
where
P::Item: AsMutSlice,
{
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
self.deref_mut().as_mut_slice()
}
}
impl<P: StaticPool> Deref for Box<P> {
type Target = P::Item;
fn deref(&self) -> &P::Item {
unsafe { &*self.ptr }
}
}
impl<P: StaticPool> DerefMut for Box<P> {
fn deref_mut(&mut self) -> &mut P::Item {
unsafe { &mut *self.ptr }
}
}
impl<P: StaticPool> fmt::Debug for Box<P>
where
P::Item: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<P::Item as fmt::Debug>::fmt(self, f)
}
}
impl<P: StaticPool> fmt::Display for Box<P>
where
P::Item: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<P::Item as fmt::Display>::fmt(self, f)
}
}
impl<P: StaticPool> PartialEq for Box<P>
where
P::Item: PartialEq,
{
fn eq(&self, rhs: &Box<P>) -> bool {
<P::Item as PartialEq>::eq(self, rhs)
}
}
impl<P: StaticPool> Eq for Box<P> where P::Item: Eq {}
impl<P: StaticPool> PartialOrd for Box<P>
where
P::Item: PartialOrd,
{
fn partial_cmp(&self, rhs: &Box<P>) -> Option<cmp::Ordering> {
<P::Item as PartialOrd>::partial_cmp(self, rhs)
}
}
impl<P: StaticPool> Ord for Box<P>
where
P::Item: Ord,
{
fn cmp(&self, rhs: &Box<P>) -> cmp::Ordering {
<P::Item as Ord>::cmp(self, rhs)
}
}
impl<P: StaticPool> Hash for Box<P>
where
P::Item: Hash,
{
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
<P::Item as Hash>::hash(self, state)
}
}
macro_rules! pool {
($vis:vis $name:ident: [$ty:ty; $size:expr]) => {
$vis struct $name;
impl StaticPool for $name {
type Item = $ty;
type Pool = BitPool<$ty, $size>;
fn get() -> &'static Self::Pool {
static POOL: BitPool<$ty, $size> = BitPool::new();
&POOL
}
}
};
}

View file

@ -11,14 +11,12 @@ use smoltcp::phy::Device as _;
use smoltcp::phy::Medium;
use smoltcp::socket::SocketSetItem;
use smoltcp::time::Instant as SmolInstant;
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address};
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
use crate::device::{Device, DeviceAdapter};
use crate::config::Configurator;
use crate::config::Event;
use crate::device::{Device, DeviceAdapter, LinkState};
use crate::fmt::*;
use crate::{
config::{Config, Configurator},
device::LinkState,
};
use crate::{Interface, SocketSet};
const ADDRESSES_LEN: usize = 1;
@ -68,39 +66,41 @@ impl Stack {
}
fn poll_configurator(&mut self, timestamp: SmolInstant) {
if let Some(config) = self
let medium = self.iface.device().capabilities().medium;
match self
.configurator
.poll(&mut self.iface, &mut self.sockets, timestamp)
{
let medium = self.iface.device().capabilities().medium;
Event::NoChange => {}
Event::Configured(config) => {
debug!("Acquired IP configuration:");
let (addr, gateway) = match config {
Config::Up(config) => (config.address.into(), Some(config.gateway)),
Config::Down => (IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32), None),
};
debug!(" IP address: {}", config.address);
set_ipv4_addr(&mut self.iface, config.address);
self.iface.update_ip_addrs(|addrs| {
let curr_addr = &mut addrs[0];
if *curr_addr != addr {
info!("IPv4 address: {:?} -> {:?}", *curr_addr, addr);
*curr_addr = addr;
}
});
if medium == Medium::Ethernet {
self.iface.routes_mut().update(|r| {
let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0);
let curr_gateway = r.get(&cidr).map(|r| r.via_router);
if curr_gateway != gateway.map(|a| a.into()) {
info!("IPv4 gateway: {:?} -> {:?}", curr_gateway, gateway);
if let Some(gateway) = gateway {
r.insert(cidr, Route::new_ipv4_gateway(gateway)).unwrap();
} else {
r.remove(&cidr);
}
if medium == Medium::Ethernet {
if let Some(gateway) = config.gateway {
debug!(" Default gateway: {}", gateway);
self.iface
.routes_mut()
.add_default_ipv4_route(gateway)
.unwrap();
} else {
debug!(" Default gateway: None");
self.iface.routes_mut().remove_default_ipv4_route();
}
});
}
for (i, s) in config.dns_servers.iter().enumerate() {
debug!(" DNS server {}: {}", i, s);
}
}
Event::Deconfigured => {
debug!("Lost IP configuration");
set_ipv4_addr(&mut self.iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
if medium == Medium::Ethernet {
self.iface.routes_mut().remove_default_ipv4_route();
}
}
}
}
@ -143,6 +143,13 @@ impl Stack {
}
}
fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) {
iface.update_ip_addrs(|addrs| {
let dest = addrs.iter_mut().next().unwrap();
*dest = IpCidr::Ipv4(cidr);
});
}
/// Initialize embassy_net.
/// This function must be called from thread mode.
pub fn init(device: &'static mut dyn Device, configurator: &'static mut dyn Configurator) {