Impement fault injection.
parent
3ea9606827
commit
e1e0c2e838
|
@ -17,6 +17,7 @@ libc = { version = "0.2.18", optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.3"
|
||||
getopts = "0.2"
|
||||
|
||||
[features]
|
||||
use_std = ["libc"]
|
||||
|
|
|
@ -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
|
||||
-------
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<T: AsMut<[u8]>>(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<T: Device> {
|
||||
lower: T,
|
||||
state: u32,
|
||||
config: Config
|
||||
}
|
||||
|
||||
impl<T: Device> FaultInjector<T> {
|
||||
/// Create a tracer device, using the given random number generator seed.
|
||||
pub fn new(lower: T, seed: u32) -> FaultInjector<T> {
|
||||
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<T: Device> Device for FaultInjector<T>
|
||||
where T::RxBuffer: AsMut<[u8]> {
|
||||
type RxBuffer = T::RxBuffer;
|
||||
type TxBuffer = TxBuffer<T::TxBuffer>;
|
||||
|
||||
fn mtu(&self) -> usize {
|
||||
if self.lower.mtu() < MTU {
|
||||
self.lower.mtu()
|
||||
} else {
|
||||
MTU
|
||||
}
|
||||
}
|
||||
|
||||
fn receive(&mut self) -> Result<Self::RxBuffer, Error> {
|
||||
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<Self::TxBuffer, Error> {
|
||||
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<T: AsRef<[u8]> + AsMut<[u8]>> {
|
||||
state: u32,
|
||||
config: Config,
|
||||
buffer: Option<T>,
|
||||
junk: [u8; MTU]
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + AsMut<[u8]>> AsRef<[u8]>
|
||||
for TxBuffer<T> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match self.buffer {
|
||||
Some(ref buf) => buf.as_ref(),
|
||||
None => &self.junk[..]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + AsMut<[u8]>> AsMut<[u8]>
|
||||
for TxBuffer<T> {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
match self.buffer {
|
||||
Some(ref mut buf) => buf.as_mut(),
|
||||
None => &mut self.junk[..]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + AsMut<[u8]>> Drop for TxBuffer<T> {
|
||||
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 => ()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"))]
|
||||
|
|
Loading…
Reference in New Issue