summaryrefslogtreecommitdiff
path: root/src/phy
diff options
context:
space:
mode:
Diffstat (limited to 'src/phy')
-rw-r--r--src/phy/fault_injector.rs330
-rw-r--r--src/phy/fuzz_injector.rs129
-rw-r--r--src/phy/loopback.rs88
-rw-r--r--src/phy/mod.rs398
-rw-r--r--src/phy/pcap_writer.rs268
-rw-r--r--src/phy/raw_socket.rs137
-rw-r--r--src/phy/sys/bpf.rs180
-rw-r--r--src/phy/sys/linux.rs26
-rw-r--r--src/phy/sys/mod.rs136
-rw-r--r--src/phy/sys/raw_socket.rs115
-rw-r--r--src/phy/sys/tuntap_interface.rs130
-rw-r--r--src/phy/tracer.rs189
-rw-r--r--src/phy/tuntap_interface.rs126
13 files changed, 2252 insertions, 0 deletions
diff --git a/src/phy/fault_injector.rs b/src/phy/fault_injector.rs
new file mode 100644
index 0000000..fffe11a
--- /dev/null
+++ b/src/phy/fault_injector.rs
@@ -0,0 +1,330 @@
+use crate::phy::{self, Device, DeviceCapabilities};
+use crate::time::{Duration, Instant};
+
+use super::PacketMeta;
+
+// We use our own RNG to stay compatible with #![no_std].
+// The use of the RNG below has a slight bias, but it doesn't matter.
+fn xorshift32(state: &mut u32) -> u32 {
+ let mut x = *state;
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ *state = x;
+ x
+}
+
+// This could be fixed once associated consts are stable.
+const MTU: usize = 1536;
+
+#[derive(Debug, Default, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct Config {
+ corrupt_pct: u8,
+ drop_pct: u8,
+ max_size: usize,
+ max_tx_rate: u64,
+ max_rx_rate: u64,
+ interval: Duration,
+}
+
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct State {
+ rng_seed: u32,
+ refilled_at: Instant,
+ tx_bucket: u64,
+ rx_bucket: u64,
+}
+
+impl State {
+ fn maybe(&mut self, pct: u8) -> bool {
+ xorshift32(&mut self.rng_seed) % 100 < pct as u32
+ }
+
+ fn corrupt<T: AsMut<[u8]>>(&mut self, mut buffer: T) {
+ let buffer = buffer.as_mut();
+ // We introduce a single bitflip, as the most likely, and the hardest to detect, error.
+ let index = (xorshift32(&mut self.rng_seed) as usize) % buffer.len();
+ let bit = 1 << (xorshift32(&mut self.rng_seed) % 8) as u8;
+ buffer[index] ^= bit;
+ }
+
+ fn refill(&mut self, config: &Config, timestamp: Instant) {
+ if timestamp - self.refilled_at > config.interval {
+ self.tx_bucket = config.max_tx_rate;
+ self.rx_bucket = config.max_rx_rate;
+ self.refilled_at = timestamp;
+ }
+ }
+
+ fn maybe_transmit(&mut self, config: &Config, timestamp: Instant) -> bool {
+ if config.max_tx_rate == 0 {
+ return true;
+ }
+
+ self.refill(config, timestamp);
+ if self.tx_bucket > 0 {
+ self.tx_bucket -= 1;
+ true
+ } else {
+ false
+ }
+ }
+
+ fn maybe_receive(&mut self, config: &Config, timestamp: Instant) -> bool {
+ if config.max_rx_rate == 0 {
+ return true;
+ }
+
+ self.refill(config, timestamp);
+ if self.rx_bucket > 0 {
+ self.rx_bucket -= 1;
+ true
+ } else {
+ false
+ }
+ }
+}
+
+/// A fault injector device.
+///
+/// A fault injector is a device that alters packets traversing through it to simulate
+/// adverse network conditions (such as random packet loss or corruption), or software
+/// or hardware limitations (such as a limited number or size of usable network buffers).
+#[derive(Debug)]
+pub struct FaultInjector<D: Device> {
+ inner: D,
+ state: State,
+ config: Config,
+ rx_buf: [u8; MTU],
+}
+
+impl<D: Device> FaultInjector<D> {
+ /// Create a fault injector device, using the given random number generator seed.
+ pub fn new(inner: D, seed: u32) -> FaultInjector<D> {
+ FaultInjector {
+ inner,
+ state: State {
+ rng_seed: seed,
+ refilled_at: Instant::from_millis(0),
+ tx_bucket: 0,
+ rx_bucket: 0,
+ },
+ config: Config::default(),
+ rx_buf: [0u8; MTU],
+ }
+ }
+
+ /// Return the underlying device, consuming the fault injector.
+ pub fn into_inner(self) -> D {
+ self.inner
+ }
+
+ /// Return the probability of corrupting a packet, in percents.
+ pub fn corrupt_chance(&self) -> u8 {
+ self.config.corrupt_pct
+ }
+
+ /// Return the probability of dropping a packet, in percents.
+ pub fn drop_chance(&self) -> u8 {
+ self.config.drop_pct
+ }
+
+ /// Return the maximum packet size, in octets.
+ pub fn max_packet_size(&self) -> usize {
+ self.config.max_size
+ }
+
+ /// Return the maximum packet transmission rate, in packets per second.
+ pub fn max_tx_rate(&self) -> u64 {
+ self.config.max_tx_rate
+ }
+
+ /// Return the maximum packet reception rate, in packets per second.
+ pub fn max_rx_rate(&self) -> u64 {
+ self.config.max_rx_rate
+ }
+
+ /// Return the interval for packet rate limiting, in milliseconds.
+ pub fn bucket_interval(&self) -> Duration {
+ self.config.interval
+ }
+
+ /// Set the probability of corrupting a packet, in percents.
+ ///
+ /// # Panics
+ /// This function panics if the probability is not between 0% and 100%.
+ pub fn set_corrupt_chance(&mut self, pct: u8) {
+ if pct > 100 {
+ panic!("percentage out of range")
+ }
+ self.config.corrupt_pct = pct
+ }
+
+ /// Set the probability of dropping a packet, in percents.
+ ///
+ /// # Panics
+ /// This function panics if the probability is not between 0% and 100%.
+ pub fn set_drop_chance(&mut self, pct: u8) {
+ if pct > 100 {
+ panic!("percentage out of range")
+ }
+ self.config.drop_pct = pct
+ }
+
+ /// Set the maximum packet size, in octets.
+ pub fn set_max_packet_size(&mut self, size: usize) {
+ self.config.max_size = size
+ }
+
+ /// Set the maximum packet transmission rate, in packets per interval.
+ pub fn set_max_tx_rate(&mut self, rate: u64) {
+ self.config.max_tx_rate = rate
+ }
+
+ /// Set the maximum packet reception rate, in packets per interval.
+ pub fn set_max_rx_rate(&mut self, rate: u64) {
+ self.config.max_rx_rate = rate
+ }
+
+ /// Set the interval for packet rate limiting, in milliseconds.
+ pub fn set_bucket_interval(&mut self, interval: Duration) {
+ self.state.refilled_at = Instant::from_millis(0);
+ self.config.interval = interval
+ }
+}
+
+impl<D: Device> Device for FaultInjector<D> {
+ type RxToken<'a> = RxToken<'a>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<'a, D::TxToken<'a>>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ let mut caps = self.inner.capabilities();
+ if caps.max_transmission_unit > MTU {
+ caps.max_transmission_unit = MTU;
+ }
+ caps
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let (rx_token, tx_token) = self.inner.receive(timestamp)?;
+ let rx_meta = <D::RxToken<'_> as phy::RxToken>::meta(&rx_token);
+
+ let len = super::RxToken::consume(rx_token, |buffer| {
+ if (self.config.max_size > 0 && buffer.len() > self.config.max_size)
+ || buffer.len() > self.rx_buf.len()
+ {
+ net_trace!("rx: dropping a packet that is too large");
+ return None;
+ }
+ self.rx_buf[..buffer.len()].copy_from_slice(buffer);
+ Some(buffer.len())
+ })?;
+
+ let buf = &mut self.rx_buf[..len];
+
+ if self.state.maybe(self.config.drop_pct) {
+ net_trace!("rx: randomly dropping a packet");
+ return None;
+ }
+
+ if !self.state.maybe_receive(&self.config, timestamp) {
+ net_trace!("rx: dropping a packet because of rate limiting");
+ return None;
+ }
+
+ if self.state.maybe(self.config.corrupt_pct) {
+ net_trace!("rx: randomly corrupting a packet");
+ self.state.corrupt(&mut buf[..]);
+ }
+
+ let rx = RxToken { buf, meta: rx_meta };
+ let tx = TxToken {
+ state: &mut self.state,
+ config: self.config,
+ token: tx_token,
+ junk: [0; MTU],
+ timestamp,
+ };
+ Some((rx, tx))
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ self.inner.transmit(timestamp).map(|token| TxToken {
+ state: &mut self.state,
+ config: self.config,
+ token,
+ junk: [0; MTU],
+ timestamp,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<'a> {
+ buf: &'a mut [u8],
+ meta: PacketMeta,
+}
+
+impl<'a> phy::RxToken for RxToken<'a> {
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(self.buf)
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.meta
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<'a, Tx: phy::TxToken> {
+ state: &'a mut State,
+ config: Config,
+ token: Tx,
+ junk: [u8; MTU],
+ timestamp: Instant,
+}
+
+impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> {
+ fn consume<R, F>(mut self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let drop = if self.state.maybe(self.config.drop_pct) {
+ net_trace!("tx: randomly dropping a packet");
+ true
+ } else if self.config.max_size > 0 && len > self.config.max_size {
+ net_trace!("tx: dropping a packet that is too large");
+ true
+ } else if !self.state.maybe_transmit(&self.config, self.timestamp) {
+ net_trace!("tx: dropping a packet because of rate limiting");
+ true
+ } else {
+ false
+ };
+
+ if drop {
+ return f(&mut self.junk[..len]);
+ }
+
+ self.token.consume(len, |mut buf| {
+ if self.state.maybe(self.config.corrupt_pct) {
+ net_trace!("tx: corrupting a packet");
+ self.state.corrupt(&mut buf)
+ }
+ f(buf)
+ })
+ }
+
+ fn set_meta(&mut self, meta: PacketMeta) {
+ self.token.set_meta(meta);
+ }
+}
diff --git a/src/phy/fuzz_injector.rs b/src/phy/fuzz_injector.rs
new file mode 100644
index 0000000..6769d8e
--- /dev/null
+++ b/src/phy/fuzz_injector.rs
@@ -0,0 +1,129 @@
+use crate::phy::{self, Device, DeviceCapabilities};
+use crate::time::Instant;
+
+// This could be fixed once associated consts are stable.
+const MTU: usize = 1536;
+
+/// Represents a fuzzer. It is expected to replace bytes in the packet with fuzzed data.
+pub trait Fuzzer {
+ /// Modify a single packet with fuzzed data.
+ fn fuzz_packet(&self, packet_data: &mut [u8]);
+}
+
+/// A fuzz injector device.
+///
+/// A fuzz injector is a device that alters packets traversing through it according to the
+/// directions of a guided fuzzer. It is designed to support fuzzing internal state machines inside
+/// smoltcp, and is not for production use.
+#[allow(unused)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct FuzzInjector<D: Device, FTx: Fuzzer, FRx: Fuzzer> {
+ inner: D,
+ fuzz_tx: FTx,
+ fuzz_rx: FRx,
+}
+
+#[allow(unused)]
+impl<D: Device, FTx: Fuzzer, FRx: Fuzzer> FuzzInjector<D, FTx, FRx> {
+ /// Create a fuzz injector device.
+ pub fn new(inner: D, fuzz_tx: FTx, fuzz_rx: FRx) -> FuzzInjector<D, FTx, FRx> {
+ FuzzInjector {
+ inner,
+ fuzz_tx,
+ fuzz_rx,
+ }
+ }
+
+ /// Return the underlying device, consuming the fuzz injector.
+ pub fn into_inner(self) -> D {
+ self.inner
+ }
+}
+
+impl<D: Device, FTx, FRx> Device for FuzzInjector<D, FTx, FRx>
+where
+ FTx: Fuzzer,
+ FRx: Fuzzer,
+{
+ type RxToken<'a> = RxToken<'a, D::RxToken<'a>, FRx>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<'a, D::TxToken<'a>, FTx>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ let mut caps = self.inner.capabilities();
+ if caps.max_transmission_unit > MTU {
+ caps.max_transmission_unit = MTU;
+ }
+ caps
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ self.inner.receive(timestamp).map(|(rx_token, tx_token)| {
+ let rx = RxToken {
+ fuzzer: &mut self.fuzz_rx,
+ token: rx_token,
+ };
+ let tx = TxToken {
+ fuzzer: &mut self.fuzz_tx,
+ token: tx_token,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ self.inner.transmit(timestamp).map(|token| TxToken {
+ fuzzer: &mut self.fuzz_tx,
+ token: token,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<'a, Rx: phy::RxToken, F: Fuzzer + 'a> {
+ fuzzer: &'a F,
+ token: Rx,
+}
+
+impl<'a, Rx: phy::RxToken, FRx: Fuzzer> phy::RxToken for RxToken<'a, Rx, FRx> {
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(|buffer| {
+ self.fuzzer.fuzz_packet(buffer);
+ f(buffer)
+ })
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.token.meta()
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<'a, Tx: phy::TxToken, F: Fuzzer + 'a> {
+ fuzzer: &'a F,
+ token: Tx,
+}
+
+impl<'a, Tx: phy::TxToken, FTx: Fuzzer> phy::TxToken for TxToken<'a, Tx, FTx> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(len, |buf| {
+ let result = f(buf);
+ self.fuzzer.fuzz_packet(buf);
+ result
+ })
+ }
+
+ fn set_meta(&mut self, meta: phy::PacketMeta) {
+ self.token.set_meta(meta)
+ }
+}
diff --git a/src/phy/loopback.rs b/src/phy/loopback.rs
new file mode 100644
index 0000000..1f57c0c
--- /dev/null
+++ b/src/phy/loopback.rs
@@ -0,0 +1,88 @@
+use alloc::collections::VecDeque;
+use alloc::vec::Vec;
+
+use crate::phy::{self, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+
+/// A loopback device.
+#[derive(Debug)]
+pub struct Loopback {
+ pub(crate) queue: VecDeque<Vec<u8>>,
+ medium: Medium,
+}
+
+#[allow(clippy::new_without_default)]
+impl Loopback {
+ /// Creates a loopback device.
+ ///
+ /// Every packet transmitted through this device will be received through it
+ /// in FIFO order.
+ pub fn new(medium: Medium) -> Loopback {
+ Loopback {
+ queue: VecDeque::new(),
+ medium,
+ }
+ }
+}
+
+impl Device for Loopback {
+ type RxToken<'a> = RxToken;
+ type TxToken<'a> = TxToken<'a>;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ DeviceCapabilities {
+ max_transmission_unit: 65535,
+ medium: self.medium,
+ ..DeviceCapabilities::default()
+ }
+ }
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ self.queue.pop_front().map(move |buffer| {
+ let rx = RxToken { buffer };
+ let tx = TxToken {
+ queue: &mut self.queue,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(TxToken {
+ queue: &mut self.queue,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken {
+ buffer: Vec<u8>,
+}
+
+impl phy::RxToken for RxToken {
+ fn consume<R, F>(mut self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(&mut self.buffer)
+ }
+}
+
+#[doc(hidden)]
+#[derive(Debug)]
+pub struct TxToken<'a> {
+ queue: &'a mut VecDeque<Vec<u8>>,
+}
+
+impl<'a> phy::TxToken for TxToken<'a> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut buffer = Vec::new();
+ buffer.resize(len, 0);
+ let result = f(&mut buffer);
+ self.queue.push_back(buffer);
+ result
+ }
+}
diff --git a/src/phy/mod.rs b/src/phy/mod.rs
new file mode 100644
index 0000000..c3845d9
--- /dev/null
+++ b/src/phy/mod.rs
@@ -0,0 +1,398 @@
+/*! Access to networking hardware.
+
+The `phy` module deals with the *network devices*. It provides a trait
+for transmitting and receiving frames, [Device](trait.Device.html)
+and implementations of it:
+
+ * the [_loopback_](struct.Loopback.html), for zero dependency testing;
+ * _middleware_ [Tracer](struct.Tracer.html) and
+ [FaultInjector](struct.FaultInjector.html), to facilitate debugging;
+ * _adapters_ [RawSocket](struct.RawSocket.html) and
+ [TunTapInterface](struct.TunTapInterface.html), to transmit and receive frames
+ on the host OS.
+*/
+#![cfg_attr(
+ feature = "medium-ethernet",
+ doc = r##"
+# Examples
+
+An implementation of the [Device](trait.Device.html) trait for a simple hardware
+Ethernet controller could look as follows:
+
+```rust
+use smoltcp::phy::{self, DeviceCapabilities, Device, Medium};
+use smoltcp::time::Instant;
+
+struct StmPhy {
+ rx_buffer: [u8; 1536],
+ tx_buffer: [u8; 1536],
+}
+
+impl<'a> StmPhy {
+ fn new() -> StmPhy {
+ StmPhy {
+ rx_buffer: [0; 1536],
+ tx_buffer: [0; 1536],
+ }
+ }
+}
+
+impl phy::Device for StmPhy {
+ type RxToken<'a> = StmPhyRxToken<'a> where Self: 'a;
+ type TxToken<'a> = StmPhyTxToken<'a> where Self: 'a;
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ Some((StmPhyRxToken(&mut self.rx_buffer[..]),
+ StmPhyTxToken(&mut self.tx_buffer[..])))
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(StmPhyTxToken(&mut self.tx_buffer[..]))
+ }
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ let mut caps = DeviceCapabilities::default();
+ caps.max_transmission_unit = 1536;
+ caps.max_burst_size = Some(1);
+ caps.medium = Medium::Ethernet;
+ caps
+ }
+}
+
+struct StmPhyRxToken<'a>(&'a mut [u8]);
+
+impl<'a> phy::RxToken for StmPhyRxToken<'a> {
+ fn consume<R, F>(mut self, f: F) -> R
+ where F: FnOnce(&mut [u8]) -> R
+ {
+ // TODO: receive packet into buffer
+ let result = f(&mut self.0);
+ println!("rx called");
+ result
+ }
+}
+
+struct StmPhyTxToken<'a>(&'a mut [u8]);
+
+impl<'a> phy::TxToken for StmPhyTxToken<'a> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where F: FnOnce(&mut [u8]) -> R
+ {
+ let result = f(&mut self.0[..len]);
+ println!("tx called {}", len);
+ // TODO: send packet out
+ result
+ }
+}
+```
+"##
+)]
+
+use crate::time::Instant;
+
+#[cfg(all(
+ any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"),
+ unix
+))]
+mod sys;
+
+mod fault_injector;
+mod fuzz_injector;
+#[cfg(feature = "alloc")]
+mod loopback;
+mod pcap_writer;
+#[cfg(all(feature = "phy-raw_socket", unix))]
+mod raw_socket;
+mod tracer;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+mod tuntap_interface;
+
+#[cfg(all(
+ any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"),
+ unix
+))]
+pub use self::sys::wait;
+
+pub use self::fault_injector::FaultInjector;
+pub use self::fuzz_injector::{FuzzInjector, Fuzzer};
+#[cfg(feature = "alloc")]
+pub use self::loopback::Loopback;
+pub use self::pcap_writer::{PcapLinkType, PcapMode, PcapSink, PcapWriter};
+#[cfg(all(feature = "phy-raw_socket", unix))]
+pub use self::raw_socket::RawSocket;
+pub use self::tracer::Tracer;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+pub use self::tuntap_interface::TunTapInterface;
+
+/// Metadata associated to a packet.
+///
+/// The packet metadata is a set of attributes associated to network packets
+/// as they travel up or down the stack. The metadata is get/set by the
+/// [`Device`] implementations or by the user when sending/receiving packets from a
+/// socket.
+///
+/// Metadata fields are enabled via Cargo features. If no field is enabled, this
+/// struct becomes zero-sized, which allows the compiler to optimize it out as if
+/// the packet metadata mechanism didn't exist at all.
+///
+/// Currently only UDP sockets allow setting/retrieving packet metadata. The metadata
+/// for packets emitted with other sockets will be all default values.
+///
+/// This struct is marked as `#[non_exhaustive]`. This means it is not possible to
+/// create it directly by specifying all fields. You have to instead create it with
+/// default values and then set the fields you want. This makes adding metadata
+/// fields a non-breaking change.
+///
+/// ```rust
+/// let mut meta = smoltcp::phy::PacketMeta::default();
+/// #[cfg(feature = "packetmeta-id")]
+/// {
+/// meta.id = 15;
+/// }
+/// ```
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
+#[non_exhaustive]
+pub struct PacketMeta {
+ #[cfg(feature = "packetmeta-id")]
+ pub id: u32,
+}
+
+/// A description of checksum behavior for a particular protocol.
+#[derive(Debug, Clone, Copy, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Checksum {
+ /// Verify checksum when receiving and compute checksum when sending.
+ #[default]
+ Both,
+ /// Verify checksum when receiving.
+ Rx,
+ /// Compute checksum before sending.
+ Tx,
+ /// Ignore checksum completely.
+ None,
+}
+
+impl Checksum {
+ /// Returns whether checksum should be verified when receiving.
+ pub fn rx(&self) -> bool {
+ match *self {
+ Checksum::Both | Checksum::Rx => true,
+ _ => false,
+ }
+ }
+
+ /// Returns whether checksum should be verified when sending.
+ pub fn tx(&self) -> bool {
+ match *self {
+ Checksum::Both | Checksum::Tx => true,
+ _ => false,
+ }
+ }
+}
+
+/// A description of checksum behavior for every supported protocol.
+#[derive(Debug, Clone, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub struct ChecksumCapabilities {
+ pub ipv4: Checksum,
+ pub udp: Checksum,
+ pub tcp: Checksum,
+ #[cfg(feature = "proto-ipv4")]
+ pub icmpv4: Checksum,
+ #[cfg(feature = "proto-ipv6")]
+ pub icmpv6: Checksum,
+}
+
+impl ChecksumCapabilities {
+ /// Checksum behavior that results in not computing or verifying checksums
+ /// for any of the supported protocols.
+ pub fn ignored() -> Self {
+ ChecksumCapabilities {
+ ipv4: Checksum::None,
+ udp: Checksum::None,
+ tcp: Checksum::None,
+ #[cfg(feature = "proto-ipv4")]
+ icmpv4: Checksum::None,
+ #[cfg(feature = "proto-ipv6")]
+ icmpv6: Checksum::None,
+ }
+ }
+}
+
+/// A description of device capabilities.
+///
+/// Higher-level protocols may achieve higher throughput or lower latency if they consider
+/// the bandwidth or packet size limitations.
+#[derive(Debug, Clone, Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub struct DeviceCapabilities {
+ /// Medium of the device.
+ ///
+ /// This indicates what kind of packet the sent/received bytes are, and determines
+ /// some behaviors of Interface. For example, ARP/NDISC address resolution is only done
+ /// for Ethernet mediums.
+ pub medium: Medium,
+
+ /// Maximum transmission unit.
+ ///
+ /// The network device is unable to send or receive frames larger than the value returned
+ /// by this function.
+ ///
+ /// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but
+ /// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14.
+ ///
+ /// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet
+ /// devices. This is a common source of confusion.
+ ///
+ /// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets.
+ pub max_transmission_unit: usize,
+
+ /// Maximum burst size, in terms of MTU.
+ ///
+ /// The network device is unable to send or receive bursts large than the value returned
+ /// by this function.
+ ///
+ /// If `None`, there is no fixed limit on burst size, e.g. if network buffers are
+ /// dynamically allocated.
+ pub max_burst_size: Option<usize>,
+
+ /// Checksum behavior.
+ ///
+ /// If the network device is capable of verifying or computing checksums for some protocols,
+ /// it can request that the stack not do so in software to improve performance.
+ pub checksum: ChecksumCapabilities,
+}
+
+impl DeviceCapabilities {
+ pub fn ip_mtu(&self) -> usize {
+ match self.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => {
+ self.max_transmission_unit - crate::wire::EthernetFrame::<&[u8]>::header_len()
+ }
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => self.max_transmission_unit,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => self.max_transmission_unit, // TODO(thvdveld): what is the MTU for Medium::IEEE802
+ }
+ }
+}
+
+/// Type of medium of a device.
+#[derive(Debug, Eq, PartialEq, Copy, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Medium {
+ /// Ethernet medium. Devices of this type send and receive Ethernet frames,
+ /// and interfaces using it must do neighbor discovery via ARP or NDISC.
+ ///
+ /// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode.
+ #[cfg(feature = "medium-ethernet")]
+ Ethernet,
+
+ /// IP medium. Devices of this type send and receive IP frames, without an
+ /// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done.
+ ///
+ /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
+ #[cfg(feature = "medium-ip")]
+ Ip,
+
+ #[cfg(feature = "medium-ieee802154")]
+ Ieee802154,
+}
+
+impl Default for Medium {
+ fn default() -> Medium {
+ #[cfg(feature = "medium-ethernet")]
+ return Medium::Ethernet;
+ #[cfg(all(feature = "medium-ip", not(feature = "medium-ethernet")))]
+ return Medium::Ip;
+ #[cfg(all(
+ feature = "medium-ieee802154",
+ not(feature = "medium-ip"),
+ not(feature = "medium-ethernet")
+ ))]
+ return Medium::Ieee802154;
+ #[cfg(all(
+ not(feature = "medium-ip"),
+ not(feature = "medium-ethernet"),
+ not(feature = "medium-ieee802154")
+ ))]
+ return panic!("No medium enabled");
+ }
+}
+
+/// An interface for sending and receiving raw network frames.
+///
+/// The interface is based on _tokens_, which are types that allow to receive/transmit a
+/// single packet. The `receive` and `transmit` functions only construct such tokens, the
+/// real sending/receiving operation are performed when the tokens are consumed.
+pub trait Device {
+ type RxToken<'a>: RxToken
+ where
+ Self: 'a;
+ type TxToken<'a>: TxToken
+ where
+ Self: 'a;
+
+ /// Construct a token pair consisting of one receive token and one transmit token.
+ ///
+ /// The additional transmit token makes it possible to generate a reply packet based
+ /// on the contents of the received packet. For example, this makes it possible to
+ /// handle arbitrarily large ICMP echo ("ping") requests, where the all received bytes
+ /// need to be sent back, without heap allocation.
+ ///
+ /// The timestamp must be a number of milliseconds, monotonically increasing since an
+ /// arbitrary moment in time, such as system startup.
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>;
+
+ /// Construct a transmit token.
+ ///
+ /// The timestamp must be a number of milliseconds, monotonically increasing since an
+ /// arbitrary moment in time, such as system startup.
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>>;
+
+ /// Get a description of device capabilities.
+ fn capabilities(&self) -> DeviceCapabilities;
+}
+
+/// A token to receive a single network packet.
+pub trait RxToken {
+ /// Consumes the token to receive a single network packet.
+ ///
+ /// This method receives a packet and then calls the given closure `f` with the raw
+ /// packet bytes as argument.
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R;
+
+ /// The Packet ID associated with the frame received by this [`RxToken`]
+ fn meta(&self) -> PacketMeta {
+ PacketMeta::default()
+ }
+}
+
+/// A token to transmit a single network packet.
+pub trait TxToken {
+ /// Consumes the token to send a single network packet.
+ ///
+ /// This method constructs a transmit buffer of size `len` and calls the passed
+ /// closure `f` with a mutable reference to that buffer. The closure should construct
+ /// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure
+ /// returns, the transmit buffer is sent out.
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R;
+
+ /// The Packet ID to be associated with the frame to be transmitted by this [`TxToken`].
+ #[allow(unused_variables)]
+ fn set_meta(&mut self, meta: PacketMeta) {}
+}
diff --git a/src/phy/pcap_writer.rs b/src/phy/pcap_writer.rs
new file mode 100644
index 0000000..aadf2a2
--- /dev/null
+++ b/src/phy/pcap_writer.rs
@@ -0,0 +1,268 @@
+use byteorder::{ByteOrder, NativeEndian};
+use core::cell::RefCell;
+use phy::Medium;
+#[cfg(feature = "std")]
+use std::io::Write;
+
+use crate::phy::{self, Device, DeviceCapabilities};
+use crate::time::Instant;
+
+enum_with_unknown! {
+ /// Captured packet header type.
+ pub enum PcapLinkType(u32) {
+ /// Ethernet frames
+ Ethernet = 1,
+ /// IPv4 or IPv6 packets (depending on the version field)
+ Ip = 101,
+ /// IEEE 802.15.4 packets without FCS.
+ Ieee802154WithoutFcs = 230,
+ }
+}
+
+/// Packet capture mode.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum PcapMode {
+ /// Capture both received and transmitted packets.
+ Both,
+ /// Capture only received packets.
+ RxOnly,
+ /// Capture only transmitted packets.
+ TxOnly,
+}
+
+/// A packet capture sink.
+pub trait PcapSink {
+ /// Write data into the sink.
+ fn write(&mut self, data: &[u8]);
+
+ /// Flush data written into the sync.
+ fn flush(&mut self) {}
+
+ /// Write an `u16` into the sink, in native byte order.
+ fn write_u16(&mut self, value: u16) {
+ let mut bytes = [0u8; 2];
+ NativeEndian::write_u16(&mut bytes, value);
+ self.write(&bytes[..])
+ }
+
+ /// Write an `u32` into the sink, in native byte order.
+ fn write_u32(&mut self, value: u32) {
+ let mut bytes = [0u8; 4];
+ NativeEndian::write_u32(&mut bytes, value);
+ self.write(&bytes[..])
+ }
+
+ /// Write the libpcap global header into the sink.
+ ///
+ /// This method may be overridden e.g. if special synchronization is necessary.
+ fn global_header(&mut self, link_type: PcapLinkType) {
+ self.write_u32(0xa1b2c3d4); // magic number
+ self.write_u16(2); // major version
+ self.write_u16(4); // minor version
+ self.write_u32(0); // timezone (= UTC)
+ self.write_u32(0); // accuracy (not used)
+ self.write_u32(65535); // maximum packet length
+ self.write_u32(link_type.into()); // link-layer header type
+ }
+
+ /// Write the libpcap packet header into the sink.
+ ///
+ /// See also the note for [global_header](#method.global_header).
+ ///
+ /// # Panics
+ /// This function panics if `length` is greater than 65535.
+ fn packet_header(&mut self, timestamp: Instant, length: usize) {
+ assert!(length <= 65535);
+
+ self.write_u32(timestamp.secs() as u32); // timestamp seconds
+ self.write_u32(timestamp.micros() as u32); // timestamp microseconds
+ self.write_u32(length as u32); // captured length
+ self.write_u32(length as u32); // original length
+ }
+
+ /// Write the libpcap packet header followed by packet data into the sink.
+ ///
+ /// See also the note for [global_header](#method.global_header).
+ fn packet(&mut self, timestamp: Instant, packet: &[u8]) {
+ self.packet_header(timestamp, packet.len());
+ self.write(packet);
+ self.flush();
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T: Write> PcapSink for T {
+ fn write(&mut self, data: &[u8]) {
+ T::write_all(self, data).expect("cannot write")
+ }
+
+ fn flush(&mut self) {
+ T::flush(self).expect("cannot flush")
+ }
+}
+
+/// A packet capture writer device.
+///
+/// Every packet transmitted or received through this device is timestamped
+/// and written (in the [libpcap] format) using the provided [sink].
+/// Note that writes are fine-grained, and buffering is recommended.
+///
+/// The packet sink should be cheaply cloneable, as it is cloned on every
+/// transmitted packet. For example, `&'a mut Vec<u8>` is cheaply cloneable
+/// but `&std::io::File`
+///
+/// [libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat
+/// [sink]: trait.PcapSink.html
+#[derive(Debug)]
+pub struct PcapWriter<D, S>
+where
+ D: Device,
+ S: PcapSink,
+{
+ lower: D,
+ sink: RefCell<S>,
+ mode: PcapMode,
+}
+
+impl<D: Device, S: PcapSink> PcapWriter<D, S> {
+ /// Creates a packet capture writer.
+ pub fn new(lower: D, mut sink: S, mode: PcapMode) -> PcapWriter<D, S> {
+ let medium = lower.capabilities().medium;
+ let link_type = match medium {
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => PcapLinkType::Ip,
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => PcapLinkType::Ethernet,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => PcapLinkType::Ieee802154WithoutFcs,
+ };
+ sink.global_header(link_type);
+ PcapWriter {
+ lower,
+ sink: RefCell::new(sink),
+ mode,
+ }
+ }
+
+ /// Get a reference to the underlying device.
+ ///
+ /// Even if the device offers reading through a standard reference, it is inadvisable to
+ /// directly read from the device as doing so will circumvent the packet capture.
+ pub fn get_ref(&self) -> &D {
+ &self.lower
+ }
+
+ /// Get a mutable reference to the underlying device.
+ ///
+ /// It is inadvisable to directly read from the device as doing so will circumvent the packet capture.
+ pub fn get_mut(&mut self) -> &mut D {
+ &mut self.lower
+ }
+}
+
+impl<D: Device, S> Device for PcapWriter<D, S>
+where
+ S: PcapSink,
+{
+ type RxToken<'a> = RxToken<'a, D::RxToken<'a>, S>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<'a, D::TxToken<'a>, S>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ self.lower.capabilities()
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let sink = &self.sink;
+ let mode = self.mode;
+ self.lower
+ .receive(timestamp)
+ .map(move |(rx_token, tx_token)| {
+ let rx = RxToken {
+ token: rx_token,
+ sink,
+ mode,
+ timestamp,
+ };
+ let tx = TxToken {
+ token: tx_token,
+ sink,
+ mode,
+ timestamp,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ let sink = &self.sink;
+ let mode = self.mode;
+ self.lower.transmit(timestamp).map(move |token| TxToken {
+ token,
+ sink,
+ mode,
+ timestamp,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<'a, Rx: phy::RxToken, S: PcapSink> {
+ token: Rx,
+ sink: &'a RefCell<S>,
+ mode: PcapMode,
+ timestamp: Instant,
+}
+
+impl<'a, Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<'a, Rx, S> {
+ fn consume<R, F: FnOnce(&mut [u8]) -> R>(self, f: F) -> R {
+ self.token.consume(|buffer| {
+ match self.mode {
+ PcapMode::Both | PcapMode::RxOnly => self
+ .sink
+ .borrow_mut()
+ .packet(self.timestamp, buffer.as_ref()),
+ PcapMode::TxOnly => (),
+ }
+ f(buffer)
+ })
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.token.meta()
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<'a, Tx: phy::TxToken, S: PcapSink> {
+ token: Tx,
+ sink: &'a RefCell<S>,
+ mode: PcapMode,
+ timestamp: Instant,
+}
+
+impl<'a, Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<'a, Tx, S> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(len, |buffer| {
+ let result = f(buffer);
+ match self.mode {
+ PcapMode::Both | PcapMode::TxOnly => {
+ self.sink.borrow_mut().packet(self.timestamp, buffer)
+ }
+ PcapMode::RxOnly => (),
+ };
+ result
+ })
+ }
+
+ fn set_meta(&mut self, meta: phy::PacketMeta) {
+ self.token.set_meta(meta)
+ }
+}
diff --git a/src/phy/raw_socket.rs b/src/phy/raw_socket.rs
new file mode 100644
index 0000000..19c5b98
--- /dev/null
+++ b/src/phy/raw_socket.rs
@@ -0,0 +1,137 @@
+use std::cell::RefCell;
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::rc::Rc;
+use std::vec::Vec;
+
+use crate::phy::{self, sys, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+
+/// A socket that captures or transmits the complete frame.
+#[derive(Debug)]
+pub struct RawSocket {
+ medium: Medium,
+ lower: Rc<RefCell<sys::RawSocketDesc>>,
+ mtu: usize,
+}
+
+impl AsRawFd for RawSocket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower.borrow().as_raw_fd()
+ }
+}
+
+impl RawSocket {
+ /// Creates a raw socket, bound to the interface called `name`.
+ ///
+ /// This requires superuser privileges or a corresponding capability bit
+ /// set on the executable.
+ pub fn new(name: &str, medium: Medium) -> io::Result<RawSocket> {
+ let mut lower = sys::RawSocketDesc::new(name, medium)?;
+ lower.bind_interface()?;
+
+ let mut mtu = lower.interface_mtu()?;
+
+ #[cfg(feature = "medium-ieee802154")]
+ if medium == Medium::Ieee802154 {
+ // SIOCGIFMTU returns 127 - (ACK_PSDU - FCS - 1) - FCS.
+ // 127 - (5 - 2 - 1) - 2 = 123
+ // For IEEE802154, we want to add (ACK_PSDU - FCS - 1), since that is what SIOCGIFMTU
+ // uses as the size of the link layer header.
+ //
+ // https://github.com/torvalds/linux/blob/7475e51b87969e01a6812eac713a1c8310372e8a/net/mac802154/iface.c#L541
+ mtu += 2;
+ }
+
+ #[cfg(feature = "medium-ethernet")]
+ if medium == Medium::Ethernet {
+ // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
+ // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
+ mtu += crate::wire::EthernetFrame::<&[u8]>::header_len()
+ }
+
+ Ok(RawSocket {
+ medium,
+ lower: Rc::new(RefCell::new(lower)),
+ mtu,
+ })
+ }
+}
+
+impl Device for RawSocket {
+ type RxToken<'a> = RxToken
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ DeviceCapabilities {
+ max_transmission_unit: self.mtu,
+ medium: self.medium,
+ ..DeviceCapabilities::default()
+ }
+ }
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; self.mtu];
+ match lower.recv(&mut buffer[..]) {
+ Ok(size) => {
+ buffer.resize(size, 0);
+ let rx = RxToken { buffer };
+ let tx = TxToken {
+ lower: self.lower.clone(),
+ };
+ Some((rx, tx))
+ }
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => None,
+ Err(err) => panic!("{}", err),
+ }
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(TxToken {
+ lower: self.lower.clone(),
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken {
+ buffer: Vec<u8>,
+}
+
+impl phy::RxToken for RxToken {
+ fn consume<R, F>(mut self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(&mut self.buffer[..])
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken {
+ lower: Rc<RefCell<sys::RawSocketDesc>>,
+}
+
+impl phy::TxToken for TxToken {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; len];
+ let result = f(&mut buffer);
+ match lower.send(&buffer[..]) {
+ Ok(_) => {}
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
+ net_debug!("phy: tx failed due to WouldBlock")
+ }
+ Err(err) => panic!("{}", err),
+ }
+ result
+ }
+}
diff --git a/src/phy/sys/bpf.rs b/src/phy/sys/bpf.rs
new file mode 100644
index 0000000..7e65b98
--- /dev/null
+++ b/src/phy/sys/bpf.rs
@@ -0,0 +1,180 @@
+use std::io;
+use std::mem;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+use libc;
+
+use super::{ifreq, ifreq_for};
+use crate::phy::Medium;
+use crate::wire::ETHERNET_HEADER_LEN;
+
+/// set interface
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BIOCSETIF: libc::c_ulong = 0x8020426c;
+/// get buffer length
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BIOCGBLEN: libc::c_ulong = 0x40044266;
+/// set immediate/nonblocking read
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BIOCIMMEDIATE: libc::c_ulong = 0x80044270;
+/// set bpf_hdr struct size
+#[cfg(target_os = "macos")]
+const SIZEOF_BPF_HDR: usize = 18;
+/// set bpf_hdr struct size
+#[cfg(target_os = "openbsd")]
+const SIZEOF_BPF_HDR: usize = 24;
+/// The actual header length may be larger than the bpf_hdr struct due to aligning
+/// see https://github.com/openbsd/src/blob/37ecb4d066e5566411cc16b362d3960c93b1d0be/sys/net/bpf.c#L1649
+/// and https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/bsd/net/bpf.c#L3580
+#[cfg(any(target_os = "macos", target_os = "openbsd"))]
+const BPF_HDRLEN: usize = (((SIZEOF_BPF_HDR + ETHERNET_HEADER_LEN) + mem::align_of::<u32>() - 1)
+ & !(mem::align_of::<u32>() - 1))
+ - ETHERNET_HEADER_LEN;
+
+macro_rules! try_ioctl {
+ ($fd:expr,$cmd:expr,$req:expr) => {
+ unsafe {
+ if libc::ioctl($fd, $cmd, $req) == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ };
+}
+
+#[derive(Debug)]
+pub struct BpfDevice {
+ fd: libc::c_int,
+ ifreq: ifreq,
+}
+
+impl AsRawFd for BpfDevice {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+fn open_device() -> io::Result<libc::c_int> {
+ unsafe {
+ for i in 0..256 {
+ let dev = format!("/dev/bpf{}\0", i);
+ match libc::open(
+ dev.as_ptr() as *const libc::c_char,
+ libc::O_RDWR | libc::O_NONBLOCK,
+ ) {
+ -1 => continue,
+ fd => return Ok(fd),
+ };
+ }
+ }
+ // at this point, all 256 BPF devices were busy and we weren't able to open any
+ Err(io::Error::last_os_error())
+}
+
+impl BpfDevice {
+ pub fn new(name: &str, _medium: Medium) -> io::Result<BpfDevice> {
+ Ok(BpfDevice {
+ fd: open_device()?,
+ ifreq: ifreq_for(name),
+ })
+ }
+
+ pub fn bind_interface(&mut self) -> io::Result<()> {
+ try_ioctl!(self.fd, BIOCSETIF, &mut self.ifreq);
+
+ Ok(())
+ }
+
+ /// This in fact does not return the interface's mtu,
+ /// but it returns the size of the buffer that the app needs to allocate
+ /// for the BPF device
+ ///
+ /// The `SIOGIFMTU` cant be called on a BPF descriptor. There is a workaround
+ /// to get the actual interface mtu, but this should work better
+ ///
+ /// To get the interface MTU, you would need to create a raw socket first,
+ /// and then call `SIOGIFMTU` for the same interface your BPF device is "bound" to.
+ /// This MTU that you would get would not include the length of `struct bpf_hdr`
+ /// which gets prepended to every packet by BPF,
+ /// and your packet will be truncated if it has the length of the MTU.
+ ///
+ /// The buffer size for BPF is usually 4096 bytes, MTU is typically 1500 bytes.
+ /// You could do something like `mtu += BPF_HDRLEN`,
+ /// but you must change the buffer size the BPF device expects using `BIOCSBLEN` accordingly,
+ /// and you must set it before setting the interface with the `BIOCSETIF` ioctl.
+ ///
+ /// The reason I said this should work better is because you might see some unexpected behavior,
+ /// truncated/unaligned packets, I/O errors on read()
+ /// if you change the buffer size to the actual MTU of the interface.
+ pub fn interface_mtu(&mut self) -> io::Result<usize> {
+ let mut bufsize: libc::c_int = 1;
+ try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int);
+ try_ioctl!(self.fd, BIOCGBLEN, &mut bufsize as *mut libc::c_int);
+
+ Ok(bufsize as usize)
+ }
+
+ pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::read(
+ self.fd,
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ buffer.len(),
+ );
+
+ if len == -1 || len < BPF_HDRLEN as isize {
+ return Err(io::Error::last_os_error());
+ }
+
+ let len = len as usize;
+
+ libc::memmove(
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ &buffer[BPF_HDRLEN] as *const u8 as *const libc::c_void,
+ len - BPF_HDRLEN,
+ );
+
+ Ok(len)
+ }
+ }
+
+ pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::write(
+ self.fd,
+ buffer.as_ptr() as *const libc::c_void,
+ buffer.len(),
+ );
+
+ if len == -1 {
+ Err(io::Error::last_os_error()).unwrap()
+ }
+
+ Ok(len as usize)
+ }
+ }
+}
+
+impl Drop for BpfDevice {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.fd);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ #[cfg(target_os = "macos")]
+ fn test_aligned_bpf_hdr_len() {
+ assert_eq!(18, BPF_HDRLEN);
+ }
+
+ #[test]
+ #[cfg(target_os = "openbsd")]
+ fn test_aligned_bpf_hdr_len() {
+ assert_eq!(26, BPF_HDRLEN);
+ }
+}
diff --git a/src/phy/sys/linux.rs b/src/phy/sys/linux.rs
new file mode 100644
index 0000000..c73eb4f
--- /dev/null
+++ b/src/phy/sys/linux.rs
@@ -0,0 +1,26 @@
+#![allow(unused)]
+
+pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
+pub const SIOCGIFINDEX: libc::c_ulong = 0x8933;
+pub const ETH_P_ALL: libc::c_short = 0x0003;
+pub const ETH_P_IEEE802154: libc::c_short = 0x00F6;
+
+// Constant definition as per
+// https://github.com/golang/sys/blob/master/unix/zerrors_linux_<arch>.go
+pub const TUNSETIFF: libc::c_ulong = if cfg!(any(
+ target_arch = "mips",
+ target_arch = "mips64",
+ target_arch = "mips64el",
+ target_arch = "mipsel",
+ target_arch = "powerpc",
+ target_arch = "powerpc64",
+ target_arch = "powerpc64le",
+ target_arch = "sparc64"
+)) {
+ 0x800454CA
+} else {
+ 0x400454CA
+};
+pub const IFF_TUN: libc::c_int = 0x0001;
+pub const IFF_TAP: libc::c_int = 0x0002;
+pub const IFF_NO_PI: libc::c_int = 0x1000;
diff --git a/src/phy/sys/mod.rs b/src/phy/sys/mod.rs
new file mode 100644
index 0000000..3f42301
--- /dev/null
+++ b/src/phy/sys/mod.rs
@@ -0,0 +1,136 @@
+#![allow(unsafe_code)]
+
+use crate::time::Duration;
+use std::os::unix::io::RawFd;
+use std::{io, mem, ptr};
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[path = "linux.rs"]
+mod imp;
+
+#[cfg(all(
+ feature = "phy-raw_socket",
+ not(any(target_os = "linux", target_os = "android")),
+ unix
+))]
+pub mod bpf;
+#[cfg(all(
+ feature = "phy-raw_socket",
+ any(target_os = "linux", target_os = "android")
+))]
+pub mod raw_socket;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+pub mod tuntap_interface;
+
+#[cfg(all(
+ feature = "phy-raw_socket",
+ not(any(target_os = "linux", target_os = "android")),
+ unix
+))]
+pub use self::bpf::BpfDevice as RawSocketDesc;
+#[cfg(all(
+ feature = "phy-raw_socket",
+ any(target_os = "linux", target_os = "android")
+))]
+pub use self::raw_socket::RawSocketDesc;
+#[cfg(all(
+ feature = "phy-tuntap_interface",
+ any(target_os = "linux", target_os = "android")
+))]
+pub use self::tuntap_interface::TunTapInterfaceDesc;
+
+/// Wait until given file descriptor becomes readable, but no longer than given timeout.
+pub fn wait(fd: RawFd, duration: Option<Duration>) -> io::Result<()> {
+ unsafe {
+ let mut readfds = {
+ let mut readfds = mem::MaybeUninit::<libc::fd_set>::uninit();
+ libc::FD_ZERO(readfds.as_mut_ptr());
+ libc::FD_SET(fd, readfds.as_mut_ptr());
+ readfds.assume_init()
+ };
+
+ let mut writefds = {
+ let mut writefds = mem::MaybeUninit::<libc::fd_set>::uninit();
+ libc::FD_ZERO(writefds.as_mut_ptr());
+ writefds.assume_init()
+ };
+
+ let mut exceptfds = {
+ let mut exceptfds = mem::MaybeUninit::<libc::fd_set>::uninit();
+ libc::FD_ZERO(exceptfds.as_mut_ptr());
+ exceptfds.assume_init()
+ };
+
+ let mut timeout = libc::timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ };
+ let timeout_ptr = if let Some(duration) = duration {
+ timeout.tv_sec = duration.secs() as libc::time_t;
+ timeout.tv_usec = (duration.millis() * 1_000) as libc::suseconds_t;
+ &mut timeout as *mut _
+ } else {
+ ptr::null_mut()
+ };
+
+ let res = libc::select(
+ fd + 1,
+ &mut readfds,
+ &mut writefds,
+ &mut exceptfds,
+ timeout_ptr,
+ );
+ if res == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(())
+ }
+}
+
+#[cfg(all(
+ any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"),
+ unix
+))]
+#[repr(C)]
+#[derive(Debug)]
+struct ifreq {
+ ifr_name: [libc::c_char; libc::IF_NAMESIZE],
+ ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
+}
+
+#[cfg(all(
+ any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"),
+ unix
+))]
+fn ifreq_for(name: &str) -> ifreq {
+ let mut ifreq = ifreq {
+ ifr_name: [0; libc::IF_NAMESIZE],
+ ifr_data: 0,
+ };
+ for (i, byte) in name.as_bytes().iter().enumerate() {
+ ifreq.ifr_name[i] = *byte as libc::c_char
+ }
+ ifreq
+}
+
+#[cfg(all(
+ any(target_os = "linux", target_os = "android"),
+ any(feature = "phy-tuntap_interface", feature = "phy-raw_socket")
+))]
+fn ifreq_ioctl(
+ lower: libc::c_int,
+ ifreq: &mut ifreq,
+ cmd: libc::c_ulong,
+) -> io::Result<libc::c_int> {
+ unsafe {
+ let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq);
+ if res == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+
+ Ok(ifreq.ifr_data)
+}
diff --git a/src/phy/sys/raw_socket.rs b/src/phy/sys/raw_socket.rs
new file mode 100644
index 0000000..f37fe96
--- /dev/null
+++ b/src/phy/sys/raw_socket.rs
@@ -0,0 +1,115 @@
+use super::*;
+use crate::phy::Medium;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::{io, mem};
+
+#[derive(Debug)]
+pub struct RawSocketDesc {
+ protocol: libc::c_short,
+ lower: libc::c_int,
+ ifreq: ifreq,
+}
+
+impl AsRawFd for RawSocketDesc {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower
+ }
+}
+
+impl RawSocketDesc {
+ pub fn new(name: &str, medium: Medium) -> io::Result<RawSocketDesc> {
+ let protocol = match medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => imp::ETH_P_ALL,
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => imp::ETH_P_ALL,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => imp::ETH_P_IEEE802154,
+ };
+
+ let lower = unsafe {
+ let lower = libc::socket(
+ libc::AF_PACKET,
+ libc::SOCK_RAW | libc::SOCK_NONBLOCK,
+ protocol.to_be() as i32,
+ );
+ if lower == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ lower
+ };
+
+ Ok(RawSocketDesc {
+ protocol,
+ lower,
+ ifreq: ifreq_for(name),
+ })
+ }
+
+ pub fn interface_mtu(&mut self) -> io::Result<usize> {
+ ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize)
+ }
+
+ pub fn bind_interface(&mut self) -> io::Result<()> {
+ let sockaddr = libc::sockaddr_ll {
+ sll_family: libc::AF_PACKET as u16,
+ sll_protocol: self.protocol.to_be() as u16,
+ sll_ifindex: ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFINDEX)?,
+ sll_hatype: 1,
+ sll_pkttype: 0,
+ sll_halen: 6,
+ sll_addr: [0; 8],
+ };
+
+ unsafe {
+ let res = libc::bind(
+ self.lower,
+ &sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr,
+ mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
+ );
+ if res == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::recv(
+ self.lower,
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ buffer.len(),
+ 0,
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+
+ pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::send(
+ self.lower,
+ buffer.as_ptr() as *const libc::c_void,
+ buffer.len(),
+ 0,
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+}
+
+impl Drop for RawSocketDesc {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.lower);
+ }
+ }
+}
diff --git a/src/phy/sys/tuntap_interface.rs b/src/phy/sys/tuntap_interface.rs
new file mode 100644
index 0000000..3019cad
--- /dev/null
+++ b/src/phy/sys/tuntap_interface.rs
@@ -0,0 +1,130 @@
+use super::*;
+use crate::{phy::Medium, wire::EthernetFrame};
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+#[derive(Debug)]
+pub struct TunTapInterfaceDesc {
+ lower: libc::c_int,
+ mtu: usize,
+}
+
+impl AsRawFd for TunTapInterfaceDesc {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower
+ }
+}
+
+impl TunTapInterfaceDesc {
+ pub fn new(name: &str, medium: Medium) -> io::Result<TunTapInterfaceDesc> {
+ let lower = unsafe {
+ let lower = libc::open(
+ "/dev/net/tun\0".as_ptr() as *const libc::c_char,
+ libc::O_RDWR | libc::O_NONBLOCK,
+ );
+ if lower == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ lower
+ };
+
+ let mut ifreq = ifreq_for(name);
+ Self::attach_interface_ifreq(lower, medium, &mut ifreq)?;
+ let mtu = Self::mtu_ifreq(medium, &mut ifreq)?;
+
+ Ok(TunTapInterfaceDesc { lower, mtu })
+ }
+
+ pub fn from_fd(fd: RawFd, mtu: usize) -> io::Result<TunTapInterfaceDesc> {
+ Ok(TunTapInterfaceDesc { lower: fd, mtu })
+ }
+
+ fn attach_interface_ifreq(
+ lower: libc::c_int,
+ medium: Medium,
+ ifr: &mut ifreq,
+ ) -> io::Result<()> {
+ let mode = match medium {
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => imp::IFF_TUN,
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => imp::IFF_TAP,
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => todo!(),
+ };
+ ifr.ifr_data = mode | imp::IFF_NO_PI;
+ ifreq_ioctl(lower, ifr, imp::TUNSETIFF).map(|_| ())
+ }
+
+ fn mtu_ifreq(medium: Medium, ifr: &mut ifreq) -> io::Result<usize> {
+ let lower = unsafe {
+ let lower = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
+ if lower == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ lower
+ };
+
+ let ip_mtu = ifreq_ioctl(lower, ifr, imp::SIOCGIFMTU).map(|mtu| mtu as usize);
+
+ unsafe {
+ libc::close(lower);
+ }
+
+ // Propagate error after close, to ensure we always close.
+ let ip_mtu = ip_mtu?;
+
+ // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
+ // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
+ let mtu = match medium {
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => ip_mtu,
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => ip_mtu + EthernetFrame::<&[u8]>::header_len(),
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => todo!(),
+ };
+
+ Ok(mtu)
+ }
+
+ pub fn interface_mtu(&self) -> io::Result<usize> {
+ Ok(self.mtu)
+ }
+
+ pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::read(
+ self.lower,
+ buffer.as_mut_ptr() as *mut libc::c_void,
+ buffer.len(),
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+
+ pub fn send(&mut self, buffer: &[u8]) -> io::Result<usize> {
+ unsafe {
+ let len = libc::write(
+ self.lower,
+ buffer.as_ptr() as *const libc::c_void,
+ buffer.len(),
+ );
+ if len == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(len as usize)
+ }
+ }
+}
+
+impl Drop for TunTapInterfaceDesc {
+ fn drop(&mut self) {
+ unsafe {
+ libc::close(self.lower);
+ }
+ }
+}
diff --git a/src/phy/tracer.rs b/src/phy/tracer.rs
new file mode 100644
index 0000000..48e60ec
--- /dev/null
+++ b/src/phy/tracer.rs
@@ -0,0 +1,189 @@
+use core::fmt;
+
+use crate::phy::{self, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
+
+/// A tracer device.
+///
+/// A tracer is a device that pretty prints all packets traversing it
+/// using the provided writer function, and then passes them to another
+/// device.
+pub struct Tracer<D: Device> {
+ inner: D,
+ writer: fn(Instant, Packet),
+}
+
+impl<D: Device> Tracer<D> {
+ /// Create a tracer device.
+ pub fn new(inner: D, writer: fn(timestamp: Instant, packet: Packet)) -> Tracer<D> {
+ Tracer { inner, writer }
+ }
+
+ /// Get a reference to the underlying device.
+ ///
+ /// Even if the device offers reading through a standard reference, it is inadvisable to
+ /// directly read from the device as doing so will circumvent the tracing.
+ pub fn get_ref(&self) -> &D {
+ &self.inner
+ }
+
+ /// Get a mutable reference to the underlying device.
+ ///
+ /// It is inadvisable to directly read from the device as doing so will circumvent the tracing.
+ pub fn get_mut(&mut self) -> &mut D {
+ &mut self.inner
+ }
+
+ /// Return the underlying device, consuming the tracer.
+ pub fn into_inner(self) -> D {
+ self.inner
+ }
+}
+
+impl<D: Device> Device for Tracer<D> {
+ type RxToken<'a> = RxToken<D::RxToken<'a>>
+ where
+ Self: 'a;
+ type TxToken<'a> = TxToken<D::TxToken<'a>>
+ where
+ Self: 'a;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ self.inner.capabilities()
+ }
+
+ fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let medium = self.inner.capabilities().medium;
+ self.inner.receive(timestamp).map(|(rx_token, tx_token)| {
+ let rx = RxToken {
+ token: rx_token,
+ writer: self.writer,
+ medium,
+ timestamp,
+ };
+ let tx = TxToken {
+ token: tx_token,
+ writer: self.writer,
+ medium,
+ timestamp,
+ };
+ (rx, tx)
+ })
+ }
+
+ fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ let medium = self.inner.capabilities().medium;
+ self.inner.transmit(timestamp).map(|tx_token| TxToken {
+ token: tx_token,
+ medium,
+ writer: self.writer,
+ timestamp,
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken<Rx: phy::RxToken> {
+ token: Rx,
+ writer: fn(Instant, Packet),
+ medium: Medium,
+ timestamp: Instant,
+}
+
+impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
+ fn consume<R, F>(self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(|buffer| {
+ (self.writer)(
+ self.timestamp,
+ Packet {
+ buffer,
+ medium: self.medium,
+ prefix: "<- ",
+ },
+ );
+ f(buffer)
+ })
+ }
+
+ fn meta(&self) -> phy::PacketMeta {
+ self.token.meta()
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken<Tx: phy::TxToken> {
+ token: Tx,
+ writer: fn(Instant, Packet),
+ medium: Medium,
+ timestamp: Instant,
+}
+
+impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ self.token.consume(len, |buffer| {
+ let result = f(buffer);
+ (self.writer)(
+ self.timestamp,
+ Packet {
+ buffer,
+ medium: self.medium,
+ prefix: "-> ",
+ },
+ );
+ result
+ })
+ }
+
+ fn set_meta(&mut self, meta: phy::PacketMeta) {
+ self.token.set_meta(meta)
+ }
+}
+
+pub struct Packet<'a> {
+ buffer: &'a [u8],
+ medium: Medium,
+ prefix: &'static str,
+}
+
+impl<'a> fmt::Display for Packet<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut indent = PrettyIndent::new(self.prefix);
+ match self.medium {
+ #[cfg(feature = "medium-ethernet")]
+ Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print(
+ &self.buffer,
+ f,
+ &mut indent,
+ ),
+ #[cfg(feature = "medium-ip")]
+ Medium::Ip => match crate::wire::IpVersion::of_packet(self.buffer) {
+ #[cfg(feature = "proto-ipv4")]
+ Ok(crate::wire::IpVersion::Ipv4) => {
+ crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print(
+ &self.buffer,
+ f,
+ &mut indent,
+ )
+ }
+ #[cfg(feature = "proto-ipv6")]
+ Ok(crate::wire::IpVersion::Ipv6) => {
+ crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print(
+ &self.buffer,
+ f,
+ &mut indent,
+ )
+ }
+ _ => f.write_str("unrecognized IP version"),
+ },
+ #[cfg(feature = "medium-ieee802154")]
+ Medium::Ieee802154 => Ok(()), // XXX
+ }
+ }
+}
diff --git a/src/phy/tuntap_interface.rs b/src/phy/tuntap_interface.rs
new file mode 100644
index 0000000..32a28db
--- /dev/null
+++ b/src/phy/tuntap_interface.rs
@@ -0,0 +1,126 @@
+use std::cell::RefCell;
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::rc::Rc;
+use std::vec::Vec;
+
+use crate::phy::{self, sys, Device, DeviceCapabilities, Medium};
+use crate::time::Instant;
+
+/// A virtual TUN (IP) or TAP (Ethernet) interface.
+#[derive(Debug)]
+pub struct TunTapInterface {
+ lower: Rc<RefCell<sys::TunTapInterfaceDesc>>,
+ mtu: usize,
+ medium: Medium,
+}
+
+impl AsRawFd for TunTapInterface {
+ fn as_raw_fd(&self) -> RawFd {
+ self.lower.borrow().as_raw_fd()
+ }
+}
+
+impl TunTapInterface {
+ /// Attaches to a TUN/TAP interface called `name`, or creates it if it does not exist.
+ ///
+ /// If `name` is a persistent interface configured with UID of the current user,
+ /// no special privileges are needed. Otherwise, this requires superuser privileges
+ /// or a corresponding capability set on the executable.
+ pub fn new(name: &str, medium: Medium) -> io::Result<TunTapInterface> {
+ let lower = sys::TunTapInterfaceDesc::new(name, medium)?;
+ let mtu = lower.interface_mtu()?;
+ Ok(TunTapInterface {
+ lower: Rc::new(RefCell::new(lower)),
+ mtu,
+ medium,
+ })
+ }
+
+ /// Attaches to a TUN/TAP interface specified by file descriptor `fd`.
+ ///
+ /// On platforms like Android, a file descriptor to a tun interface is exposed.
+ /// On these platforms, a TunTapInterface cannot be instantiated with a name.
+ pub fn from_fd(fd: RawFd, medium: Medium, mtu: usize) -> io::Result<TunTapInterface> {
+ let lower = sys::TunTapInterfaceDesc::from_fd(fd, mtu)?;
+ Ok(TunTapInterface {
+ lower: Rc::new(RefCell::new(lower)),
+ mtu,
+ medium,
+ })
+ }
+}
+
+impl Device for TunTapInterface {
+ type RxToken<'a> = RxToken;
+ type TxToken<'a> = TxToken;
+
+ fn capabilities(&self) -> DeviceCapabilities {
+ DeviceCapabilities {
+ max_transmission_unit: self.mtu,
+ medium: self.medium,
+ ..DeviceCapabilities::default()
+ }
+ }
+
+ fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; self.mtu];
+ match lower.recv(&mut buffer[..]) {
+ Ok(size) => {
+ buffer.resize(size, 0);
+ let rx = RxToken { buffer };
+ let tx = TxToken {
+ lower: self.lower.clone(),
+ };
+ Some((rx, tx))
+ }
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => None,
+ Err(err) => panic!("{}", err),
+ }
+ }
+
+ fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
+ Some(TxToken {
+ lower: self.lower.clone(),
+ })
+ }
+}
+
+#[doc(hidden)]
+pub struct RxToken {
+ buffer: Vec<u8>,
+}
+
+impl phy::RxToken for RxToken {
+ fn consume<R, F>(mut self, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ f(&mut self.buffer[..])
+ }
+}
+
+#[doc(hidden)]
+pub struct TxToken {
+ lower: Rc<RefCell<sys::TunTapInterfaceDesc>>,
+}
+
+impl phy::TxToken for TxToken {
+ fn consume<R, F>(self, len: usize, f: F) -> R
+ where
+ F: FnOnce(&mut [u8]) -> R,
+ {
+ let mut lower = self.lower.borrow_mut();
+ let mut buffer = vec![0; len];
+ let result = f(&mut buffer);
+ match lower.send(&buffer[..]) {
+ Ok(_) => {}
+ Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
+ net_debug!("phy: tx failed due to WouldBlock")
+ }
+ Err(err) => panic!("{}", err),
+ }
+ result
+ }
+}