diff --git a/Cargo.toml b/Cargo.toml index 4846b4a..399961f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ libc = { version = "0.2.18", optional = true } [dev-dependencies] env_logger = "0.3" +getopts = "0.2" [features] use_std = ["libc"] diff --git a/README.md b/README.md index 3218f5c..0415c14 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,9 @@ It responds to: The buffers are only 64 bytes long, for convenience of testing resource exhaustion conditions. +Fault injection is available through the `--drop-chance` and `--corrupt-chance` options, +with values in percents. A good starting value is 15%. + License ------- diff --git a/examples/server.rs b/examples/server.rs index a6c67df..a132b32 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,16 +1,17 @@ #[macro_use] extern crate log; extern crate env_logger; +extern crate getopts; extern crate smoltcp; -use std::str; +use std::str::{self, FromStr}; use std::env; -use std::time::Instant; +use std::time::{Instant, SystemTime, UNIX_EPOCH}; use log::{LogLevelFilter, LogRecord}; use env_logger::{LogBuilder}; use smoltcp::Error; -use smoltcp::phy::{Tracer, TapInterface}; +use smoltcp::phy::{Tracer, FaultInjector, TapInterface}; use smoltcp::wire::{EthernetFrame, EthernetAddress, IpAddress, IpEndpoint}; use smoltcp::wire::PrettyPrinter; use smoltcp::iface::{SliceArpCache, EthernetInterface}; @@ -19,6 +20,24 @@ use smoltcp::socket::{UdpSocket, UdpSocketBuffer, UdpPacketBuffer}; use smoltcp::socket::{TcpSocket, TcpSocketBuffer}; fn main() { + let mut opts = getopts::Options::new(); + opts.optopt("", "drop-chance", "Chance of dropping a packet (%)", "CHANCE"); + opts.optopt("", "corrupt-chance", "Chance of corrupting a packet (%)", "CHANCE"); + opts.optflag("h", "help", "print this help menu"); + + let matches = opts.parse(env::args().skip(1)).unwrap(); + if matches.opt_present("h") || matches.free.len() != 1 { + let brief = format!("Usage: {} FILE [options]", env::args().nth(0).unwrap()); + print!("{}", opts.usage(&brief)); + return + } + let drop_chance = u8::from_str(&matches.opt_str("drop-chance") + .unwrap_or("0".to_string())).unwrap(); + let corrupt_chance = u8::from_str(&matches.opt_str("corrupt-chance") + .unwrap_or("0".to_string())).unwrap(); + + let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos(); + let startup_time = Instant::now(); LogBuilder::new() .format(move |record: &LogRecord| { @@ -41,10 +60,12 @@ fn main() { print!("\x1b[37m{}\x1b[0m", printer) } - let ifname = env::args().nth(1).unwrap(); - - let device = TapInterface::new(ifname.as_ref()).unwrap(); + let device = TapInterface::new(&matches.free[0]).unwrap(); + let mut device = FaultInjector::new(device, seed); + device.set_drop_chance(drop_chance); + device.set_corrupt_chance(corrupt_chance); let device = Tracer::<_, EthernetFrame<&[u8]>>::new(device, trace_writer); + let arp_cache = SliceArpCache::new(vec![Default::default(); 8]); let endpoint = IpEndpoint::new(IpAddress::default(), 6969); diff --git a/src/phy/fault_injector.rs b/src/phy/fault_injector.rs new file mode 100644 index 0000000..a651948 --- /dev/null +++ b/src/phy/fault_injector.rs @@ -0,0 +1,179 @@ +use Error; +use super::Device; + +// 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 +} + +fn check_rng(state: &mut u32, pct: u8) -> bool { + xorshift32(state) % 100 < pct as u32 +} + +fn corrupt>(state: &mut u32, mut buffer: T) { + let mut buffer = buffer.as_mut(); + // We introduce a single bitflip, as the most likely, and the hardest to detect, error. + let index = (xorshift32(state) as usize) % buffer.len(); + let bit = 1 << (xorshift32(state) % 8) as u8; + buffer[index] ^= bit; +} + +// This could be fixed once associated consts are stable. +const MTU: usize = 1536; + +#[derive(Debug, Clone, Copy)] +struct Config { + corrupt_pct: u8, + drop_pct: u8, + reorder_pct: u8 +} + +/// A fault injector device. +/// +/// A fault injector is a device that randomly drops or corrupts packets traversing it, +/// according to preset probabilities. +#[derive(Debug)] +pub struct FaultInjector { + lower: T, + state: u32, + config: Config +} + +impl FaultInjector { + /// Create a tracer device, using the given random number generator seed. + pub fn new(lower: T, seed: u32) -> FaultInjector { + FaultInjector { + lower: lower, + state: seed, + config: Config { + corrupt_pct: 0, + drop_pct: 0, + reorder_pct: 0 + } + } + } + + /// Return the underlying device, consuming the tracer. + pub fn into_lower(self) -> T { + self.lower + } + + /// 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 + } + + /// 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 + } +} + +impl Device for FaultInjector + where T::RxBuffer: AsMut<[u8]> { + type RxBuffer = T::RxBuffer; + type TxBuffer = TxBuffer; + + fn mtu(&self) -> usize { + if self.lower.mtu() < MTU { + self.lower.mtu() + } else { + MTU + } + } + + fn receive(&mut self) -> Result { + let mut buffer = try!(self.lower.receive()); + if check_rng(&mut self.state, self.config.drop_pct) { + net_trace!("rx: dropping a packet"); + return Err(Error::Exhausted) + } + if check_rng(&mut self.state, self.config.corrupt_pct) { + net_trace!("rx: corrupting a packet"); + corrupt(&mut self.state, &mut buffer) + } + Ok(buffer) + } + + fn transmit(&mut self, len: usize) -> Result { + let buffer; + if check_rng(&mut self.state, self.config.drop_pct) { + net_trace!("rx: dropping a packet"); + buffer = None; + } else { + buffer = Some(try!(self.lower.transmit(len))); + } + Ok(TxBuffer { + buffer: buffer, + state: xorshift32(&mut self.state), + config: self.config, + junk: [0; MTU] + }) + } +} + +#[doc(hidden)] +pub struct TxBuffer + AsMut<[u8]>> { + state: u32, + config: Config, + buffer: Option, + junk: [u8; MTU] +} + +impl + AsMut<[u8]>> AsRef<[u8]> + for TxBuffer { + fn as_ref(&self) -> &[u8] { + match self.buffer { + Some(ref buf) => buf.as_ref(), + None => &self.junk[..] + } + } +} + +impl + AsMut<[u8]>> AsMut<[u8]> + for TxBuffer { + fn as_mut(&mut self) -> &mut [u8] { + match self.buffer { + Some(ref mut buf) => buf.as_mut(), + None => &mut self.junk[..] + } + } +} + +impl + AsMut<[u8]>> Drop for TxBuffer { + fn drop(&mut self) { + match self.buffer { + Some(ref mut buf) => { + if check_rng(&mut self.state, self.config.corrupt_pct) { + net_trace!("tx: corrupting a packet"); + corrupt(&mut self.state, buf) + } + }, + None => () + } + } +} diff --git a/src/phy/mod.rs b/src/phy/mod.rs index d53127a..bc1af66 100644 --- a/src/phy/mod.rs +++ b/src/phy/mod.rs @@ -11,12 +11,14 @@ use Error; mod sys; mod tracer; +mod fault_injector; #[cfg(feature = "use_std")] mod raw_socket; #[cfg(all(feature = "use_std", target_os = "linux"))] mod tap_interface; pub use self::tracer::Tracer; +pub use self::fault_injector::FaultInjector; #[cfg(feature = "use_std")] pub use self::raw_socket::RawSocket; #[cfg(all(feature = "use_std", target_os = "linux"))]