diff options
Diffstat (limited to 'src/phy')
-rw-r--r-- | src/phy/fault_injector.rs | 330 | ||||
-rw-r--r-- | src/phy/fuzz_injector.rs | 129 | ||||
-rw-r--r-- | src/phy/loopback.rs | 88 | ||||
-rw-r--r-- | src/phy/mod.rs | 398 | ||||
-rw-r--r-- | src/phy/pcap_writer.rs | 268 | ||||
-rw-r--r-- | src/phy/raw_socket.rs | 137 | ||||
-rw-r--r-- | src/phy/sys/bpf.rs | 180 | ||||
-rw-r--r-- | src/phy/sys/linux.rs | 26 | ||||
-rw-r--r-- | src/phy/sys/mod.rs | 136 | ||||
-rw-r--r-- | src/phy/sys/raw_socket.rs | 115 | ||||
-rw-r--r-- | src/phy/sys/tuntap_interface.rs | 130 | ||||
-rw-r--r-- | src/phy/tracer.rs | 189 | ||||
-rw-r--r-- | src/phy/tuntap_interface.rs | 126 |
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 + } +} |