Skip to main content

ts_netstack_smoltcp/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3
4extern crate alloc;
5
6#[cfg(any(test, feature = "std"))]
7extern crate std;
8
9use core::{borrow::Borrow, time::Duration};
10
11pub extern crate ts_netstack_smoltcp_core as netcore;
12pub extern crate ts_netstack_smoltcp_socket as netsock;
13
14use netcore::{Channel, smoltcp};
15pub use netcore::{HasChannel, Netstack as CoreStack};
16#[cfg(feature = "tokio")]
17pub use netsock::ping;
18pub use netsock::{CreateSocket, PingError};
19
20mod pipe;
21mod run;
22#[cfg(feature = "std")]
23mod std_clock;
24#[cfg(feature = "tun")]
25mod tun_rs_device;
26
27pub use pipe::{WakingPipe, WakingPipeDev, WakingPipeReceiver, WakingPipeSender};
28pub use run::{run, run_blocking};
29#[cfg(feature = "tun")]
30pub use tun_rs_device::{TunRsDevice, TunRsDeviceAsync};
31
32/// A function that yields "now" [`Instant`][smoltcp::time::Instant]s.
33///
34/// Must be monotonic.
35pub type Clock = &'static (dyn Fn() -> smoltcp::time::Instant + Send + Sync);
36
37/// A userspace network stack.
38///
39/// This is a relatively thin shell around [`CoreStack`] to provide convenience runtime
40/// features.
41pub struct Netstack<D> {
42    core: CoreStack,
43    dev: D,
44    clock: Clock,
45}
46
47/// Convenience function to construct a new network stack around a [`WakingPipeDev`].
48///
49/// Returns the netstack and the remote end of the pipe (from which outgoing packets
50/// can be read and to which incoming packets can be transmitted).
51#[cfg(feature = "std")]
52pub fn piped(config: netcore::Config) -> (Netstack<WakingPipeDev>, WakingPipe) {
53    let (pipe1, pipe2) = WakingPipe::unbounded();
54
55    let dev = WakingPipeDev {
56        pipe: pipe1,
57        mtu: config.mtu,
58        medium: smoltcp::phy::Medium::Ip,
59    };
60
61    (Netstack::<WakingPipeDev>::new(dev, config), pipe2)
62}
63
64/// Convenience function to create a pair of network stacks connected by a point-to-point
65/// in-memory link.
66#[cfg(feature = "std")]
67pub fn piped_pair(config: netcore::Config) -> (Netstack<WakingPipeDev>, Netstack<WakingPipeDev>) {
68    let (pipe1, pipe2) = WakingPipe::unbounded();
69
70    let dev1 = WakingPipeDev {
71        pipe: pipe1,
72        mtu: config.mtu,
73        medium: smoltcp::phy::Medium::Ip,
74    };
75
76    let dev2 = WakingPipeDev {
77        pipe: pipe2,
78        mtu: config.mtu,
79        medium: smoltcp::phy::Medium::Ip,
80    };
81
82    (
83        Netstack::<WakingPipeDev>::new(dev1, config.clone()),
84        Netstack::<WakingPipeDev>::new(dev2, config),
85    )
86}
87
88impl<D> Netstack<D> {
89    /// Construct a new netstack with the given device and configuration.
90    ///
91    /// Uses [`std::time::Instant`] as a clock.
92    #[cfg(feature = "std")]
93    pub fn new(dev: D, config: netcore::Config) -> Self {
94        Self::with_clock(dev, config, &|| std_clock::CLOCK.now())
95    }
96
97    /// Construct a new netstack with the given device, configuration, and clock.
98    pub fn with_clock(dev: D, config: netcore::Config, clock: Clock) -> Self {
99        Self {
100            core: CoreStack::new(config, clock()),
101            dev,
102            clock,
103        }
104    }
105
106    /// Run the netstack, driving the internal event loop to consume commands.
107    ///
108    /// Runs forever, blocking the current thread.
109    ///
110    /// `poll_delay` is the amount of time to sleep when we're done with I/O and commands to
111    /// process: a smaller delay improves latency at the cost of CPU cycles spent polling.
112    /// Consider the async methods for an event-driven approach.
113    pub fn run_blocking(&mut self, poll_delay: Duration)
114    where
115        D: smoltcp::phy::Device,
116    {
117        run_blocking(&mut self.core, &mut self.dev, self.clock, poll_delay)
118    }
119
120    /// Spawn the netstack runner in an OS thread.
121    #[cfg(feature = "std")]
122    pub fn spawn_threaded(mut self, poll_dur: Duration) -> std::thread::JoinHandle<()>
123    where
124        D: smoltcp::phy::Device + Send + 'static,
125    {
126        std::thread::spawn(move || self.run_blocking(poll_dur))
127    }
128
129    /// Spawn the netstack runner into a tokio task.
130    #[cfg(feature = "tokio")]
131    pub fn spawn_tokio(mut self) -> tokio::task::JoinHandle<()>
132    where
133        D: smoltcp::phy::Device + netcore::AsyncWakeDevice + Send + 'static + Unpin,
134    {
135        tokio::spawn(async move { self.run_tokio().await })
136    }
137
138    /// Run the netstack, driving the internal event loop to consume commands.
139    ///
140    /// Uses [`tokio::time::sleep`] as the sleep implementation.
141    #[cfg(feature = "tokio")]
142    pub async fn run_tokio(&mut self)
143    where
144        D: smoltcp::phy::Device + netcore::AsyncWakeDevice + Unpin,
145    {
146        run(&mut self.core, &mut self.dev, self.clock, |dur| {
147            tokio::time::sleep(dur)
148        })
149        .await
150    }
151
152    /// Run the netstack, driving the internal event loop to consume commands.
153    ///
154    /// The `sleep` function is the runtime-specific sleep implementation. It has the
155    /// `Clone` bound for esoteric async type system reasons (the loop needs to call the
156    /// function multiple times, but it doesn't want to reason about `&impl AsyncFn`). A
157    /// normal closure or async fn ref will satisfy the bound.
158    pub async fn run_with_sleep(&mut self, sleep: impl AsyncFn(Duration) + Clone)
159    where
160        D: smoltcp::phy::Device + netcore::AsyncWakeDevice + Unpin,
161    {
162        run(&mut self.core, &mut self.dev, self.clock, sleep).await
163    }
164}
165
166impl<D> HasChannel for Netstack<D> {
167    fn borrow_channel(&self) -> impl Borrow<Channel> + Send {
168        self.core.borrow_channel()
169    }
170}