|
@@ -1,3 +1,5 @@
|
|
|
+use std::time::{Instant, Duration};
|
|
|
+
|
|
|
use Error;
|
|
|
use super::{DeviceLimits, Device};
|
|
|
|
|
@@ -12,54 +14,102 @@ fn xorshift32(state: &mut u32) -> u32 {
|
|
|
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)]
|
|
|
+#[derive(Debug, Clone, Copy, Default)]
|
|
|
struct Config {
|
|
|
corrupt_pct: u8,
|
|
|
drop_pct: u8,
|
|
|
- reorder_pct: u8
|
|
|
+ reorder_pct: u8,
|
|
|
+ max_size: usize,
|
|
|
+ max_tx_rate: u64,
|
|
|
+ max_rx_rate: u64,
|
|
|
+ interval: Duration,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug, Clone, Copy)]
|
|
|
+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 mut 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) {
|
|
|
+ if self.refilled_at.elapsed() > config.interval {
|
|
|
+ self.tx_bucket = config.max_tx_rate;
|
|
|
+ self.rx_bucket = config.max_rx_rate;
|
|
|
+ self.refilled_at = Instant::now();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn maybe_transmit(&mut self, config: &Config) -> bool {
|
|
|
+ if config.max_tx_rate == 0 { return true }
|
|
|
+
|
|
|
+ self.refill(config);
|
|
|
+ if self.tx_bucket > 0 {
|
|
|
+ self.tx_bucket -= 1;
|
|
|
+ true
|
|
|
+ } else {
|
|
|
+ false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn maybe_receive(&mut self, config: &Config) -> bool {
|
|
|
+ if config.max_rx_rate == 0 { return true }
|
|
|
+
|
|
|
+ self.refill(config);
|
|
|
+ if self.rx_bucket > 0 {
|
|
|
+ self.rx_bucket -= 1;
|
|
|
+ true
|
|
|
+ } else {
|
|
|
+ false
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// A fault injector device.
|
|
|
///
|
|
|
-/// A fault injector is a device that randomly drops or corrupts packets traversing it,
|
|
|
-/// according to preset probabilities.
|
|
|
+/// 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<T: Device> {
|
|
|
lower: T,
|
|
|
- state: u32,
|
|
|
+ state: State,
|
|
|
config: Config
|
|
|
}
|
|
|
|
|
|
impl<T: Device> FaultInjector<T> {
|
|
|
- /// Create a tracer device, using the given random number generator seed.
|
|
|
+ /// Create a fault injector 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
|
|
|
- }
|
|
|
+ state: State {
|
|
|
+ rng_seed: seed,
|
|
|
+ refilled_at: Instant::now(),
|
|
|
+ tx_bucket: 0,
|
|
|
+ rx_bucket: 0,
|
|
|
+ },
|
|
|
+ config: Config::default()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// Return the underlying device, consuming the tracer.
|
|
|
+ /// Return the underlying device, consuming the fault injector.
|
|
|
pub fn into_lower(self) -> T {
|
|
|
self.lower
|
|
|
}
|
|
@@ -74,6 +124,26 @@ impl<T: Device> FaultInjector<T> {
|
|
|
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_rx_rate
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Return the maximum packet reception rate, in packets per second.
|
|
|
+ pub fn max_rx_rate(&self) -> u64 {
|
|
|
+ self.config.max_tx_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
|
|
@@ -91,6 +161,27 @@ impl<T: Device> FaultInjector<T> {
|
|
|
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::now() - self.config.interval;
|
|
|
+ self.config.interval = interval
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl<T: Device> Device for FaultInjector<T>
|
|
@@ -108,28 +199,42 @@ impl<T: Device> Device for FaultInjector<T>
|
|
|
|
|
|
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");
|
|
|
+ if self.state.maybe(self.config.drop_pct) {
|
|
|
+ net_trace!("rx: randomly 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)
|
|
|
+ if self.state.maybe(self.config.corrupt_pct) {
|
|
|
+ net_trace!("rx: randomly corrupting a packet");
|
|
|
+ self.state.corrupt(&mut buffer)
|
|
|
+ }
|
|
|
+ if self.config.max_size > 0 && buffer.as_ref().len() > self.config.max_size {
|
|
|
+ net_trace!("rx: dropping a packet that is too large");
|
|
|
+ return Err(Error::Exhausted)
|
|
|
+ }
|
|
|
+ if !self.state.maybe_receive(&self.config) {
|
|
|
+ net_trace!("rx: dropping a packet because of rate limiting");
|
|
|
+ return Err(Error::Exhausted)
|
|
|
}
|
|
|
Ok(buffer)
|
|
|
}
|
|
|
|
|
|
fn transmit(&mut self, length: usize) -> Result<Self::TxBuffer, Error> {
|
|
|
let buffer;
|
|
|
- if check_rng(&mut self.state, self.config.drop_pct) {
|
|
|
- net_trace!("tx: dropping a packet");
|
|
|
+ if self.state.maybe(self.config.drop_pct) {
|
|
|
+ net_trace!("tx: randomly dropping a packet");
|
|
|
+ buffer = None;
|
|
|
+ } else if self.config.max_size > 0 && length > self.config.max_size {
|
|
|
+ net_trace!("tx: dropping a packet that is too large");
|
|
|
+ buffer = None;
|
|
|
+ } else if !self.state.maybe_transmit(&self.config) {
|
|
|
+ net_trace!("tx: dropping a packet because of rate limiting");
|
|
|
buffer = None;
|
|
|
} else {
|
|
|
buffer = Some(try!(self.lower.transmit(length)));
|
|
|
}
|
|
|
Ok(TxBuffer {
|
|
|
buffer: buffer,
|
|
|
- state: xorshift32(&mut self.state),
|
|
|
+ state: self.state.clone(),
|
|
|
config: self.config,
|
|
|
junk: [0; MTU],
|
|
|
length: length
|
|
@@ -139,7 +244,7 @@ impl<T: Device> Device for FaultInjector<T>
|
|
|
|
|
|
#[doc(hidden)]
|
|
|
pub struct TxBuffer<T: AsRef<[u8]> + AsMut<[u8]>> {
|
|
|
- state: u32,
|
|
|
+ state: State,
|
|
|
config: Config,
|
|
|
buffer: Option<T>,
|
|
|
junk: [u8; MTU],
|
|
@@ -170,9 +275,9 @@ 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) {
|
|
|
+ if self.state.maybe(self.config.corrupt_pct) {
|
|
|
net_trace!("tx: corrupting a packet");
|
|
|
- corrupt(&mut self.state, buf)
|
|
|
+ self.state.corrupt(buf)
|
|
|
}
|
|
|
},
|
|
|
None => ()
|