From b5748524f86f809d9c8dc2c5b4bb3f07e55dbda1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 01:03:24 +0200 Subject: [PATCH 1/8] net: improve error message on unsupported medium. --- embassy-net/Cargo.toml | 1 + embassy-net/src/lib.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 0c551f20..0361f1db 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -9,6 +9,7 @@ categories = [ "embedded", "no-std", "asynchronous", + "network-programming", ] [package.metadata.embassy_docs] diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 9f881289..3a385fad 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -249,7 +249,10 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> HardwareAddress driver::HardwareAddress::Ip => HardwareAddress::Ip, #[allow(unreachable_patterns)] - _ => panic!("Unsupported address {:?}. Make sure to enable medium-ethernet or medium-ieee802154 in embassy-net's Cargo features.", addr), + _ => panic!( + "Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.", + addr + ), } } From 100200d021bbf650f7dd569414ee52b2d5ac10f0 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 01:03:39 +0200 Subject: [PATCH 2/8] net-driver-channel: do not hardcode medium to ethernet. --- embassy-net-driver-channel/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs index 076238ba..f23c0441 100644 --- a/embassy-net-driver-channel/src/lib.rs +++ b/embassy-net-driver-channel/src/lib.rs @@ -8,6 +8,7 @@ use core::cell::RefCell; use core::mem::MaybeUninit; use core::task::{Context, Poll}; +use driver::HardwareAddress; pub use embassy_net_driver as driver; use embassy_net_driver::{Capabilities, LinkState, Medium}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; @@ -218,7 +219,11 @@ pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( ) -> (Runner<'d, MTU>, Device<'d, MTU>) { let mut caps = Capabilities::default(); caps.max_transmission_unit = MTU; - caps.medium = Medium::Ethernet; + caps.medium = match &hardware_address { + HardwareAddress::Ethernet(_) => Medium::Ethernet, + HardwareAddress::Ieee802154(_) => Medium::Ieee802154, + HardwareAddress::Ip => Medium::Ip, + }; // safety: this is a self-referential struct, however: // - it can't move while the `'d` borrow is active. From aacf14b62a28277599871edf74106b1cd2e01795 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 01:04:51 +0200 Subject: [PATCH 3/8] net-ppp: Add it. --- embassy-net-ppp/Cargo.toml | 28 ++++ embassy-net-ppp/README.md | 19 +++ embassy-net-ppp/src/fmt.rs | 257 +++++++++++++++++++++++++++++++++++++ embassy-net-ppp/src/lib.rs | 165 ++++++++++++++++++++++++ 4 files changed, 469 insertions(+) create mode 100644 embassy-net-ppp/Cargo.toml create mode 100644 embassy-net-ppp/README.md create mode 100644 embassy-net-ppp/src/fmt.rs create mode 100644 embassy-net-ppp/src/lib.rs diff --git a/embassy-net-ppp/Cargo.toml b/embassy-net-ppp/Cargo.toml new file mode 100644 index 00000000..b2874c68 --- /dev/null +++ b/embassy-net-ppp/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "embassy-net-ppp" +version = "0.1.0" +description = "embassy-net driver for PPP over Serial" +keywords = ["embedded", "ppp", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] +license = "MIT OR Apache-2.0" +edition = "2021" + +[features] +defmt = ["dep:defmt", "ppproto/defmt"] +log = ["dep:log", "ppproto/log"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embedded-io-async = { version = "0.5.0" } +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +ppproto = { version = "0.1.1"} +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-ppp/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] diff --git a/embassy-net-ppp/README.md b/embassy-net-ppp/README.md new file mode 100644 index 00000000..58d67395 --- /dev/null +++ b/embassy-net-ppp/README.md @@ -0,0 +1,19 @@ +# `embassy-net-ppp` + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for PPP over Serial. + +## Interoperability + +This crate can run on any executor. + +It supports any serial port implementing [`embedded-io-async`](https://crates.io/crates/embedded-io-async). + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/embassy-net-ppp/src/fmt.rs b/embassy-net-ppp/src/fmt.rs new file mode 100644 index 00000000..91984bde --- /dev/null +++ b/embassy-net-ppp/src/fmt.rs @@ -0,0 +1,257 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*); + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs new file mode 100644 index 00000000..7853e04a --- /dev/null +++ b/embassy-net-ppp/src/lib.rs @@ -0,0 +1,165 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +// must be first +mod fmt; + +use core::convert::Infallible; +use core::mem::MaybeUninit; + +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::signal::Signal; +use embedded_io_async::{BufRead, Write, WriteAllError}; +use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction}; + +const MTU: usize = 1500; + +/// Type alias for the embassy-net driver. +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the driver. +/// +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d, R: BufRead, W: Write> { + ch: ch::Runner<'d, MTU>, + r: R, + w: W, +} + +/// Error returned by [`Runner::run`]. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RunError { + /// Reading from the serial port failed. + Read(RE), + /// Writing to the serial port failed. + Write(WE), + /// Writing to the serial port wrote zero bytes, indicating it can't accept more data. + WriteZero, + /// Writing to the serial got EOF. + Eof, +} + +impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { + /// You must call this in a background task for the driver to operate. + pub async fn run(mut self) -> Result> { + let config = ppproto::Config { + username: b"myuser", + password: b"mypass", + }; + let mut ppp = PPPoS::new(config); + ppp.open().unwrap(); + + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + state_chan.set_link_state(LinkState::Down); + let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down)); + + let mut rx_buf = [0; 2048]; + let mut tx_buf = [0; 2048]; + + let poll_signal: Signal = Signal::new(); + poll_signal.signal(()); + + loop { + let mut poll = false; + match select3(self.r.fill_buf(), tx_chan.tx_buf(), poll_signal.wait()).await { + Either3::First(r) => { + let data = r.map_err(RunError::Read)?; + if data.is_empty() { + return Err(RunError::Eof); + } + let n = ppp.consume(data, &mut rx_buf); + self.r.consume(n); + poll = true; + } + Either3::Second(pkt) => { + match ppp.send(pkt, &mut tx_buf) { + Ok(n) => match self.w.write_all(&tx_buf[..n]).await { + Ok(()) => {} + Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), + Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), + }, + Err(BufferFullError) => unreachable!(), + } + tx_chan.tx_done(); + } + Either3::Third(_) => poll = true, + } + + if poll { + match ppp.poll(&mut tx_buf, &mut rx_buf) { + PPPoSAction::None => {} + PPPoSAction::Received(rg) => { + let pkt = &rx_buf[rg]; + let buf = rx_chan.rx_buf().await; // TODO: fix possible deadlock + buf[..pkt.len()].copy_from_slice(pkt); + rx_chan.rx_done(pkt.len()); + + poll_signal.signal(()); + } + PPPoSAction::Transmit(n) => { + match self.w.write_all(&tx_buf[..n]).await { + Ok(()) => {} + Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), + Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), + } + poll_signal.signal(()); + } + } + + match ppp.status().phase { + ppproto::Phase::Open => state_chan.set_link_state(LinkState::Up), + _ => state_chan.set_link_state(LinkState::Down), + } + } + } + } +} + +/// Create a PPP embassy-net driver instance. +/// +/// This returns two structs: +/// - a `Device` that you must pass to the `embassy-net` stack. +/// - a `Runner`. You must call `.run()` on it in a background task. +pub fn new<'a, const N_RX: usize, const N_TX: usize, R: BufRead, W: Write>( + state: &'a mut State, + r: R, + w: W, +) -> (Device<'a>, Runner<'a, R, W>) { + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip); + (device, Runner { ch: runner, r, w }) +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} From 2303382dfd6f4e6275a699b938f465a1e6170449 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 15:39:25 +0200 Subject: [PATCH 4/8] net-ppp: nicer processing loop structure that can't deadlock. --- embassy-net-ppp/src/lib.rs | 84 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs index 7853e04a..af216c96 100644 --- a/embassy-net-ppp/src/lib.rs +++ b/embassy-net-ppp/src/lib.rs @@ -8,11 +8,9 @@ mod fmt; use core::convert::Infallible; use core::mem::MaybeUninit; -use embassy_futures::select::{select3, Either3}; +use embassy_futures::select::{select, Either}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::signal::Signal; use embedded_io_async::{BufRead, Write, WriteAllError}; use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction}; @@ -75,22 +73,50 @@ impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { let mut rx_buf = [0; 2048]; let mut tx_buf = [0; 2048]; - let poll_signal: Signal = Signal::new(); - poll_signal.signal(()); + let mut needs_poll = true; loop { - let mut poll = false; - match select3(self.r.fill_buf(), tx_chan.tx_buf(), poll_signal.wait()).await { - Either3::First(r) => { - let data = r.map_err(RunError::Read)?; - if data.is_empty() { - return Err(RunError::Eof); - } - let n = ppp.consume(data, &mut rx_buf); + let rx_fut = async { + let buf = rx_chan.rx_buf().await; + let rx_data = match needs_poll { + true => &[][..], + false => match self.r.fill_buf().await { + Ok(rx_data) if rx_data.len() == 0 => return Err(RunError::Eof), + Ok(rx_data) => rx_data, + Err(e) => return Err(RunError::Read(e)), + }, + }; + Ok((buf, rx_data)) + }; + let tx_fut = tx_chan.tx_buf(); + match select(rx_fut, tx_fut).await { + Either::First(r) => { + needs_poll = false; + + let (buf, rx_data) = r?; + let n = ppp.consume(rx_data, &mut rx_buf); self.r.consume(n); - poll = true; + + match ppp.poll(&mut tx_buf, &mut rx_buf) { + PPPoSAction::None => {} + PPPoSAction::Received(rg) => { + let pkt = &rx_buf[rg]; + buf[..pkt.len()].copy_from_slice(pkt); + rx_chan.rx_done(pkt.len()); + } + PPPoSAction::Transmit(n) => match self.w.write_all(&tx_buf[..n]).await { + Ok(()) => {} + Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), + Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), + }, + } + + match ppp.status().phase { + ppproto::Phase::Open => state_chan.set_link_state(LinkState::Up), + _ => state_chan.set_link_state(LinkState::Down), + } } - Either3::Second(pkt) => { + Either::Second(pkt) => { match ppp.send(pkt, &mut tx_buf) { Ok(n) => match self.w.write_all(&tx_buf[..n]).await { Ok(()) => {} @@ -101,34 +127,6 @@ impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { } tx_chan.tx_done(); } - Either3::Third(_) => poll = true, - } - - if poll { - match ppp.poll(&mut tx_buf, &mut rx_buf) { - PPPoSAction::None => {} - PPPoSAction::Received(rg) => { - let pkt = &rx_buf[rg]; - let buf = rx_chan.rx_buf().await; // TODO: fix possible deadlock - buf[..pkt.len()].copy_from_slice(pkt); - rx_chan.rx_done(pkt.len()); - - poll_signal.signal(()); - } - PPPoSAction::Transmit(n) => { - match self.w.write_all(&tx_buf[..n]).await { - Ok(()) => {} - Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), - Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), - } - poll_signal.signal(()); - } - } - - match ppp.status().phase { - ppproto::Phase::Open => state_chan.set_link_state(LinkState::Up), - _ => state_chan.set_link_state(LinkState::Down), - } } } } From c2d601abeff6f9f911b8140f3ceb6806971dc7dd Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 15:57:02 +0200 Subject: [PATCH 5/8] net-ppp: take serial port and config in run(), allow calling it multiple times. --- embassy-net-driver-channel/src/lib.rs | 12 +++++++ embassy-net-ppp/src/lib.rs | 50 +++++++++++++++------------ 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs index f23c0441..f2aa6b25 100644 --- a/embassy-net-driver-channel/src/lib.rs +++ b/embassy-net-driver-channel/src/lib.rs @@ -74,6 +74,18 @@ impl<'d, const MTU: usize> Runner<'d, MTU> { ) } + pub fn borrow_split(&mut self) -> (StateRunner<'_>, RxRunner<'_, MTU>, TxRunner<'_, MTU>) { + ( + StateRunner { shared: self.shared }, + RxRunner { + rx_chan: self.rx_chan.borrow(), + }, + TxRunner { + tx_chan: self.tx_chan.borrow(), + }, + ) + } + pub fn state_runner(&self) -> StateRunner<'d> { StateRunner { shared: self.shared } } diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs index af216c96..df583fb3 100644 --- a/embassy-net-ppp/src/lib.rs +++ b/embassy-net-ppp/src/lib.rs @@ -13,6 +13,7 @@ use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; use embedded_io_async::{BufRead, Write, WriteAllError}; use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction}; +pub use ppproto::Config; const MTU: usize = 1500; @@ -36,37 +37,44 @@ impl State { /// Background runner for the driver. /// /// You must call `.run()` in a background task for the driver to operate. -pub struct Runner<'d, R: BufRead, W: Write> { +pub struct Runner<'d> { ch: ch::Runner<'d, MTU>, - r: R, - w: W, } /// Error returned by [`Runner::run`]. #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RunError { +pub enum RunError { /// Reading from the serial port failed. - Read(RE), + Read(E), /// Writing to the serial port failed. - Write(WE), + Write(E), /// Writing to the serial port wrote zero bytes, indicating it can't accept more data. WriteZero, /// Writing to the serial got EOF. Eof, } -impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { +impl<'d> Runner<'d> { /// You must call this in a background task for the driver to operate. - pub async fn run(mut self) -> Result> { - let config = ppproto::Config { - username: b"myuser", - password: b"mypass", - }; + /// + /// If reading/writing to the underlying serial port fails, the link state + /// is set to Down and the error is returned. + /// + /// It is allowed to cancel this function's future (i.e. drop it). This will terminate + /// the PPP connection and set the link state to Down. + /// + /// After this function returns or is canceled, you can call it again to establish + /// a new PPP connection. + pub async fn run( + &mut self, + mut rw: RW, + config: ppproto::Config<'_>, + ) -> Result> { let mut ppp = PPPoS::new(config); ppp.open().unwrap(); - let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.borrow_split(); state_chan.set_link_state(LinkState::Down); let _ondrop = OnDrop::new(|| state_chan.set_link_state(LinkState::Down)); @@ -80,7 +88,7 @@ impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { let buf = rx_chan.rx_buf().await; let rx_data = match needs_poll { true => &[][..], - false => match self.r.fill_buf().await { + false => match rw.fill_buf().await { Ok(rx_data) if rx_data.len() == 0 => return Err(RunError::Eof), Ok(rx_data) => rx_data, Err(e) => return Err(RunError::Read(e)), @@ -95,7 +103,7 @@ impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { let (buf, rx_data) = r?; let n = ppp.consume(rx_data, &mut rx_buf); - self.r.consume(n); + rw.consume(n); match ppp.poll(&mut tx_buf, &mut rx_buf) { PPPoSAction::None => {} @@ -104,7 +112,7 @@ impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { buf[..pkt.len()].copy_from_slice(pkt); rx_chan.rx_done(pkt.len()); } - PPPoSAction::Transmit(n) => match self.w.write_all(&tx_buf[..n]).await { + PPPoSAction::Transmit(n) => match rw.write_all(&tx_buf[..n]).await { Ok(()) => {} Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), @@ -118,7 +126,7 @@ impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { } Either::Second(pkt) => { match ppp.send(pkt, &mut tx_buf) { - Ok(n) => match self.w.write_all(&tx_buf[..n]).await { + Ok(n) => match rw.write_all(&tx_buf[..n]).await { Ok(()) => {} Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), @@ -137,13 +145,9 @@ impl<'d, R: BufRead, W: Write> Runner<'d, R, W> { /// This returns two structs: /// - a `Device` that you must pass to the `embassy-net` stack. /// - a `Runner`. You must call `.run()` on it in a background task. -pub fn new<'a, const N_RX: usize, const N_TX: usize, R: BufRead, W: Write>( - state: &'a mut State, - r: R, - w: W, -) -> (Device<'a>, Runner<'a, R, W>) { +pub fn new<'a, const N_RX: usize, const N_TX: usize>(state: &'a mut State) -> (Device<'a>, Runner<'a>) { let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ip); - (device, Runner { ch: runner, r, w }) + (device, Runner { ch: runner }) } struct OnDrop { From a026db3f570cae504dc5da6c7055afc52e122930 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 16:04:22 +0200 Subject: [PATCH 6/8] net-ppp: use From and ? to handle write errors. --- embassy-net-ppp/src/lib.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs index df583fb3..e3b208ee 100644 --- a/embassy-net-ppp/src/lib.rs +++ b/embassy-net-ppp/src/lib.rs @@ -55,6 +55,15 @@ pub enum RunError { Eof, } +impl From> for RunError { + fn from(value: WriteAllError) -> Self { + match value { + WriteAllError::Other(e) => Self::Write(e), + WriteAllError::WriteZero => Self::WriteZero, + } + } +} + impl<'d> Runner<'d> { /// You must call this in a background task for the driver to operate. /// @@ -112,11 +121,7 @@ impl<'d> Runner<'d> { buf[..pkt.len()].copy_from_slice(pkt); rx_chan.rx_done(pkt.len()); } - PPPoSAction::Transmit(n) => match rw.write_all(&tx_buf[..n]).await { - Ok(()) => {} - Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), - Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), - }, + PPPoSAction::Transmit(n) => rw.write_all(&tx_buf[..n]).await?, } match ppp.status().phase { @@ -126,11 +131,7 @@ impl<'d> Runner<'d> { } Either::Second(pkt) => { match ppp.send(pkt, &mut tx_buf) { - Ok(n) => match rw.write_all(&tx_buf[..n]).await { - Ok(()) => {} - Err(WriteAllError::WriteZero) => return Err(RunError::WriteZero), - Err(WriteAllError::Other(e)) => return Err(RunError::Write(e)), - }, + Ok(n) => rw.write_all(&tx_buf[..n]).await?, Err(BufferFullError) => unreachable!(), } tx_chan.tx_done(); From 623f37a27368c53d782cc9819c180bdf45f15612 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 19:49:28 +0200 Subject: [PATCH 7/8] net-ppp: add callback for IP configuration. --- embassy-net-ppp/src/lib.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/embassy-net-ppp/src/lib.rs b/embassy-net-ppp/src/lib.rs index e3b208ee..ca87fbae 100644 --- a/embassy-net-ppp/src/lib.rs +++ b/embassy-net-ppp/src/lib.rs @@ -13,7 +13,7 @@ use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; use embedded_io_async::{BufRead, Write, WriteAllError}; use ppproto::pppos::{BufferFullError, PPPoS, PPPoSAction}; -pub use ppproto::Config; +pub use ppproto::{Config, Ipv4Status}; const MTU: usize = 1500; @@ -79,6 +79,7 @@ impl<'d> Runner<'d> { &mut self, mut rw: RW, config: ppproto::Config<'_>, + mut on_ipv4_up: impl FnMut(Ipv4Status), ) -> Result> { let mut ppp = PPPoS::new(config); ppp.open().unwrap(); @@ -91,6 +92,7 @@ impl<'d> Runner<'d> { let mut tx_buf = [0; 2048]; let mut needs_poll = true; + let mut was_up = false; loop { let rx_fut = async { @@ -124,9 +126,19 @@ impl<'d> Runner<'d> { PPPoSAction::Transmit(n) => rw.write_all(&tx_buf[..n]).await?, } - match ppp.status().phase { - ppproto::Phase::Open => state_chan.set_link_state(LinkState::Up), - _ => state_chan.set_link_state(LinkState::Down), + let status = ppp.status(); + match status.phase { + ppproto::Phase::Open => { + if !was_up { + on_ipv4_up(status.ipv4.unwrap()); + } + was_up = true; + state_chan.set_link_state(LinkState::Up); + } + _ => { + was_up = false; + state_chan.set_link_state(LinkState::Down); + } } } Either::Second(pkt) => { From d812cc574571e60a092f10727046c494face09c9 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Aug 2023 19:49:43 +0200 Subject: [PATCH 8/8] net-ppp: add std example. --- examples/std/Cargo.toml | 4 +- examples/std/src/bin/net_ppp.rs | 218 ++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 examples/std/src/bin/net_ppp.rs diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 0d4d5fa1..7b0d0bda 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -8,11 +8,13 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["log"] } embassy-executor = { version = "0.3.0", path = "../../embassy-executor", features = ["arch-std", "executor-thread", "log", "nightly", "integrated-timers"] } embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["log", "std", "nightly"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } +embassy-net-ppp = { version = "0.1.0", path = "../../embassy-net-ppp", features = ["log"]} embedded-io-async = { version = "0.5.0" } embedded-io-adapters = { version = "0.5.0", features = ["futures-03"] } critical-section = { version = "1.1", features = ["std"] } +smoltcp = { version = "0.10.0", features = ["dns-max-server-count-4"] } async-io = "1.6.0" env_logger = "0.9.0" diff --git a/examples/std/src/bin/net_ppp.rs b/examples/std/src/bin/net_ppp.rs new file mode 100644 index 00000000..9cf6e19d --- /dev/null +++ b/examples/std/src/bin/net_ppp.rs @@ -0,0 +1,218 @@ +//! Testing against pppd: +//! +//! echo myuser $(hostname) mypass 192.168.7.10 >> /etc/ppp/pap-secrets +//! socat -v -x PTY,link=pty1,rawer PTY,link=pty2,rawer +//! sudo pppd $PWD/pty1 115200 192.168.7.1: ms-dns 8.8.4.4 ms-dns 8.8.8.8 nodetach debug local persist silent noproxyarp +//! RUST_LOG=trace cargo run --bin net_ppp -- --device pty2 +//! ping 192.168.7.10 +//! nc 192.168.7.10 1234 + +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait, impl_trait_projections)] + +#[path = "../serial_port.rs"] +mod serial_port; + +use async_io::Async; +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, ConfigV4, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net_ppp::Runner; +use embedded_io_async::Write; +use futures::io::BufReader; +use heapless::Vec; +use log::*; +use nix::sys::termios; +use rand_core::{OsRng, RngCore}; +use static_cell::{make_static, StaticCell}; + +use crate::serial_port::SerialPort; + +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// Serial port device name + #[clap(short, long)] + device: String, +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn ppp_task( + stack: &'static Stack>, + mut runner: Runner<'static>, + port: SerialPort, +) -> ! { + let port = Async::new(port).unwrap(); + let port = BufReader::new(port); + let port = adapter::FromFutures::new(port); + + let config = embassy_net_ppp::Config { + username: b"myuser", + password: b"mypass", + }; + + runner + .run(port, config, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0)); + } + let config = ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0), + gateway: None, + dns_servers, + }); + stack.set_config_v4(config); + }) + .await + .unwrap(); + unreachable!() +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Open serial port + let baudrate = termios::BaudRate::B115200; + let port = SerialPort::new(opts.device.as_str(), baudrate).unwrap(); + + // Init network device + let state = make_static!(embassy_net_ppp::State::<4, 4>::new()); + let (device, runner) = embassy_net_ppp::new(state); + + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + Config::default(), // don't configure IP yet + make_static!(StackResources::<3>::new()), + seed + )); + + // Launch network task + spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(ppp_task(stack, runner, port)).unwrap(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x?}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Trace) + .filter_module("polling", log::LevelFilter::Info) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} + +mod adapter { + use core::future::poll_fn; + use core::pin::Pin; + + use futures::AsyncBufReadExt; + + /// Adapter from `futures::io` traits. + #[derive(Clone)] + pub struct FromFutures { + inner: T, + } + + impl FromFutures { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + } + + impl embedded_io_async::ErrorType for FromFutures { + type Error = std::io::Error; + } + + impl embedded_io_async::Read for FromFutures { + async fn read(&mut self, buf: &mut [u8]) -> Result { + poll_fn(|cx| Pin::new(&mut self.inner).poll_read(cx, buf)).await + } + } + + impl embedded_io_async::BufRead for FromFutures { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Pin::new(&mut self.inner).consume(amt) + } + } + + impl embedded_io_async::Write for FromFutures { + async fn write(&mut self, buf: &[u8]) -> Result { + poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + poll_fn(|cx| Pin::new(&mut self.inner).poll_flush(cx)).await + } + } +}