Browse Source

Impement fault injection.

whitequark 8 years ago
parent
commit
5860c365f5
5 changed files with 212 additions and 6 deletions
  1. 1 0
      Cargo.toml
  2. 3 0
      README.md
  3. 27 6
      examples/server.rs
  4. 179 0
      src/phy/fault_injector.rs
  5. 2 0
      src/phy/mod.rs

+ 1 - 0
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"]

+ 3 - 0
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
 -------
 

+ 27 - 6
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);

+ 179 - 0
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<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 => ()
+        }
+    }
+}

+ 2 - 0
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"))]