From 488ad321153157bb6044fafbc49c2a6f0dea51b6 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 4 Jan 2024 16:32:18 +0100 Subject: [PATCH 01/19] feat(sim): scaffold simulation crate --- Cargo.lock | 4 +++ Cargo.toml | 10 ++++++- msg-sim/Cargo.toml | 14 +++++++++ msg-sim/src/lib.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 msg-sim/Cargo.toml create mode 100644 msg-sim/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7324f45..38376f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -714,6 +714,10 @@ dependencies = [ "futures", ] +[[package]] +name = "msg-sim" +version = "0.1.1" + [[package]] name = "msg-socket" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 6c75990..90fc61a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,12 @@ [workspace] -members = ["msg", "msg-socket", "msg-wire", "msg-transport", "msg-common"] +members = [ + "msg", + "msg-socket", + "msg-wire", + "msg-transport", + "msg-common", + "msg-sim", +] [workspace.package] version = "0.1.1" @@ -16,6 +23,7 @@ msg-wire = { path = "./msg-wire" } msg-socket = { path = "./msg-socket" } msg-transport = { path = "./msg-transport" } msg-common = { path = "./msg-common" } +msg-sim = { path = "./msg-sim" } # async async-trait = "0.1" diff --git a/msg-sim/Cargo.toml b/msg-sim/Cargo.toml new file mode 100644 index 0000000..707a569 --- /dev/null +++ b/msg-sim/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "msg-sim" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +description.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs new file mode 100644 index 0000000..fc1fe46 --- /dev/null +++ b/msg-sim/src/lib.rs @@ -0,0 +1,72 @@ +use std::{collections::HashMap, time::Duration}; + +/// A type alias for a network device. +pub type Device = String; + +#[derive(Debug)] +pub struct SimConfig { + latency: Duration, + target_bw: u64, + default_bw: u64, + packet_loss: f64, +} + +pub struct Simulator { + /// A map of active simulations. + active_sims: HashMap, +} + +impl Simulator { + pub fn new() -> Self { + Self { + active_sims: HashMap::new(), + } + } + + /// Starts a new simulation on the given device according to the config. + pub fn start(&mut self, device: Device, config: SimConfig) { + let mut simulation = Simulation { + device: device.clone(), + config, + }; + + simulation.start(); + + self.active_sims.insert(device, simulation); + } + + /// Stops the simulation on the given device. + pub fn stop(&mut self, device: Device) { + // This will drop the simulation, which will kill the process. + self.active_sims.remove(&device); + } +} + +/// An active simulation. +struct Simulation { + device: Device, + config: SimConfig, +} + +impl Simulation { + /// Starts the simulation. + fn start(&mut self) { + // TODO: start the simulation by executing the right command + #[cfg(target_os = "linux")] + {} + + #[cfg(target_os = "macos")] + {} + } +} + +impl Drop for Simulation { + fn drop(&mut self) { + // TODO: kill the simulation by executing the right command + #[cfg(target_os = "linux")] + {} + + #[cfg(target_os = "macos")] + {} + } +} From da30b3641f92e2978a4b0f747ab74c35c64d4e37 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Sun, 7 Jan 2024 16:58:01 +0100 Subject: [PATCH 02/19] feat(sim): docs, more scaffolding --- msg-sim/README.md | 25 +++++++++++++++++++++++++ msg-sim/src/cmd/linux.rs | 0 msg-sim/src/cmd/macos.rs | 4 ++++ msg-sim/src/cmd/mod.rs | 5 +++++ msg-sim/src/lib.rs | 7 +++++++ msg-sim/src/protocol.rs | 18 ++++++++++++++++++ 6 files changed, 59 insertions(+) create mode 100644 msg-sim/README.md create mode 100644 msg-sim/src/cmd/linux.rs create mode 100644 msg-sim/src/cmd/macos.rs create mode 100644 msg-sim/src/cmd/mod.rs create mode 100644 msg-sim/src/protocol.rs diff --git a/msg-sim/README.md b/msg-sim/README.md new file mode 100644 index 0000000..5bd03d3 --- /dev/null +++ b/msg-sim/README.md @@ -0,0 +1,25 @@ +# `msg-sim` + +## Overview +This crate provides functionality to simulate real-world network conditions over an interface (e.g. `lo0`) for testing and benchmarking purposes. +It only works on MacOS and Linux. + +## Implementation + +### MacOS +* https://gist.github.com/tellyworth/2ce28add99fe743c702c090c8144355e + +* `dnctl` for creating a dummynet pipe + +Example: +```bash +dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror` +``` + +* `pfctl` for creating a rule to match traffic and send it through the pipe + +Example: +```bash +echo "dummynet out proto tcp from any to any pipe 1" | sudo pfctl -f - +pfctl -e +``` diff --git a/msg-sim/src/cmd/linux.rs b/msg-sim/src/cmd/linux.rs new file mode 100644 index 0000000..e69de29 diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs new file mode 100644 index 0000000..6b12b94 --- /dev/null +++ b/msg-sim/src/cmd/macos.rs @@ -0,0 +1,4 @@ +//! This module contains all the commands necessary to start and stop a simulation. +//! +//! ## Implementation +//! Under the hood, this module uses the `pfctl` command. diff --git a/msg-sim/src/cmd/mod.rs b/msg-sim/src/cmd/mod.rs new file mode 100644 index 0000000..204fd43 --- /dev/null +++ b/msg-sim/src/cmd/mod.rs @@ -0,0 +1,5 @@ +#[cfg(target_os = "linux")] +pub mod linux; + +#[cfg(target_os = "macos")] +pub mod macos; diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index fc1fe46..e393299 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -1,5 +1,10 @@ use std::{collections::HashMap, time::Duration}; +use protocol::Protocol; + +mod cmd; +mod protocol; + /// A type alias for a network device. pub type Device = String; @@ -9,8 +14,10 @@ pub struct SimConfig { target_bw: u64, default_bw: u64, packet_loss: f64, + protocols: Vec, } +#[derive(Default)] pub struct Simulator { /// A map of active simulations. active_sims: HashMap, diff --git a/msg-sim/src/protocol.rs b/msg-sim/src/protocol.rs new file mode 100644 index 0000000..38d96b5 --- /dev/null +++ b/msg-sim/src/protocol.rs @@ -0,0 +1,18 @@ +#[derive(Debug, Clone, Copy)] +#[allow(clippy::upper_case_acronyms)] +pub enum Protocol { + TCP, + UDP, + ICMP, +} + +impl From<&str> for Protocol { + fn from(s: &str) -> Self { + match s { + "tcp" => Self::TCP, + "udp" => Self::UDP, + "icmp" => Self::ICMP, + _ => panic!("invalid protocol"), + } + } +} From 4da310aac4196ca872c687b4fbfcd31e18001d9f Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Mon, 8 Jan 2024 14:20:07 +0100 Subject: [PATCH 03/19] doc(sim): update macos sim docs --- msg-sim/README.md | 33 +++++++++++++++++++++++++++------ msg-sim/src/cmd/macos.rs | 6 ++++-- msg-sim/src/lib.rs | 3 ++- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/msg-sim/README.md b/msg-sim/README.md index 5bd03d3..f555f74 100644 --- a/msg-sim/README.md +++ b/msg-sim/README.md @@ -7,19 +7,40 @@ It only works on MacOS and Linux. ## Implementation ### MacOS -* https://gist.github.com/tellyworth/2ce28add99fe743c702c090c8144355e +On MacOS, we use a combination of the `pfctl` and `dnctl` tools. +`pfctl` is a tool to manage the packet filter device. `dnctl` can manage +the dummynet traffic shaper. -* `dnctl` for creating a dummynet pipe +The general flow is as follows: + +* Create a dummynet pipe with `dnctl` and configure it with `bw`, `delay`, `plr` and `noerror` parameters. Example: ```bash -dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror` +sudo dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror ``` -* `pfctl` for creating a rule to match traffic and send it through the pipe +* Use `pfctl` to create a rule to match traffic and send it through the pipe Example: ```bash -echo "dummynet out proto tcp from any to any pipe 1" | sudo pfctl -f - -pfctl -e +# Create an anchor (a named container for rules) +(cat /etc/pf.conf && echo "dummynet-anchor \"msg-sim\"" && \ +echo "anchor \"msg-sim\"") | sudo pfctl -f - + +INTERFACE="lo0" +echo 'dummynet in on $INTERFACE all pipe 1' | sudo pfctl -a msg-sim -f - + +# Enable the packet filter +sudo pfctl -E +``` + +* `pfctl` for removing the rule +```bash +# Apply the default configuration +sudo pfctl -f /etc/pf.conf +# Disable the packet filter +sudo pfctl -d +# Remove the dummynet pipes +sudo dnctl -q flush ``` diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs index 6b12b94..b0cdcc2 100644 --- a/msg-sim/src/cmd/macos.rs +++ b/msg-sim/src/cmd/macos.rs @@ -1,4 +1,6 @@ -//! This module contains all the commands necessary to start and stop a simulation. +//! This module contains all the commands necessary to start and stop a network simulation +//! on MacOS. //! //! ## Implementation -//! Under the hood, this module uses the `pfctl` command. +//! Under the hood, this module builds a dummy network pipe with `dnctl` and `pfctl` and applies +//! the given configuration to it. diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index e393299..e91cbfa 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -13,7 +13,8 @@ pub struct SimConfig { latency: Duration, target_bw: u64, default_bw: u64, - packet_loss: f64, + /// The packet loss rate in percent. + packet_loss_rate: f64, protocols: Vec, } From 6bdffe13e9384b6767b844f083fe4d0e9c2dc8b6 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Tue, 9 Jan 2024 13:04:44 +0100 Subject: [PATCH 04/19] feat(sim): docs, more scaffolding --- msg-sim/README.md | 39 ++++++--- msg-sim/src/cmd/macos.rs | 10 +++ msg-sim/src/dummynet.rs | 180 +++++++++++++++++++++++++++++++++++++++ msg-sim/src/lib.rs | 55 ++++++------ 4 files changed, 249 insertions(+), 35 deletions(-) create mode 100644 msg-sim/src/dummynet.rs diff --git a/msg-sim/README.md b/msg-sim/README.md index f555f74..d72cb93 100644 --- a/msg-sim/README.md +++ b/msg-sim/README.md @@ -1,46 +1,65 @@ # `msg-sim` ## Overview -This crate provides functionality to simulate real-world network conditions over an interface (e.g. `lo0`) for testing and benchmarking purposes. +This crate provides functionality to simulate real-world network conditions +locally to and from a specific endpoint for testing and benchmarking purposes. It only works on MacOS and Linux. ## Implementation ### MacOS On MacOS, we use a combination of the `pfctl` and `dnctl` tools. -`pfctl` is a tool to manage the packet filter device. `dnctl` can manage -the dummynet traffic shaper. +[`pfctl`](https://man.freebsd.org/cgi/man.cgi?query=pfctl&apropos=0&sektion=8&manpath=FreeBSD+14.0-RELEASE+and+Ports&arch=default&format=html) is a tool to manage the packet filter device. [`dnctl`](https://man.freebsd.org/cgi/man.cgi?query=dnctl&sektion=8&format=html) can manage +the [dummynet](http://info.iet.unipi.it/~luigi/papers/20100304-ccr.pdf) traffic shaper. The general flow is as follows: -* Create a dummynet pipe with `dnctl` and configure it with `bw`, `delay`, `plr` and `noerror` parameters. +* Create a dummynet pipe with `dnctl` and configure it with `bw`, `delay`, `plr` Example: ```bash -sudo dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror +sudo dnctl pipe 1 config bw 10Kbit/s delay 50 plr 0.1 +``` + +* Create a loopback alias with `ifconfig` to simulate a different endpoint and +set the MTU to the usual value (1500) + +Example: +```bash +sudo ifconfig lo0 alias 127.0.0.3 up +sudo ifconfig lo0 mtu 1500 ``` * Use `pfctl` to create a rule to match traffic and send it through the pipe Example: ```bash -# Create an anchor (a named container for rules) +# Create an anchor (a named container for rules, close to a namespace) (cat /etc/pf.conf && echo "dummynet-anchor \"msg-sim\"" && \ echo "anchor \"msg-sim\"") | sudo pfctl -f - -INTERFACE="lo0" -echo 'dummynet in on $INTERFACE all pipe 1' | sudo pfctl -a msg-sim -f - +# Create a rule to match traffic from any to the alias and send it through the pipe +echo 'dummynet in from any to 127.0.0.3 pipe 1' | sudo pfctl -a msg-sim -f - # Enable the packet filter sudo pfctl -E ``` -* `pfctl` for removing the rule +* Remove the rules and the pipe ```bash # Apply the default configuration sudo pfctl -f /etc/pf.conf # Disable the packet filter sudo pfctl -d +# Remove the alias & reset the MTU +sudo ifconfig lo0 -alias 127.0.0.3 +sudo ifconfig lo0 mtu 16384 # Remove the dummynet pipes -sudo dnctl -q flush +sudo dnctl pipe delete 1 ``` + +### Questions +- Do we need to create 2 pipes to simulate a bidirectional link? MAN page seems to say so. + +### Linux +On Linux, we use dummy interfaces and `tc` with `netem` to simulate and shape traffic. diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs index b0cdcc2..19c0aa5 100644 --- a/msg-sim/src/cmd/macos.rs +++ b/msg-sim/src/cmd/macos.rs @@ -4,3 +4,13 @@ //! ## Implementation //! Under the hood, this module builds a dummy network pipe with `dnctl` and `pfctl` and applies //! the given configuration to it. + +const PFCTL: &str = "pfctl"; +const DNCTL: &str = "dnctl"; +const IFCONFIG: &str = "ifconfig"; + +pub struct Pfctl; + +pub struct Dnctl; + +pub struct Ifconfig; diff --git a/msg-sim/src/dummynet.rs b/msg-sim/src/dummynet.rs new file mode 100644 index 0000000..d046680 --- /dev/null +++ b/msg-sim/src/dummynet.rs @@ -0,0 +1,180 @@ +use std::{net::IpAddr, process::Command}; + +use crate::protocol::Protocol; + +/// Pipe represents a dummynet pipe. +pub struct Pipe { + /// The ID of the pipe. + pub id: usize, + /// Optional bandwidth cap in Kbps. + pub bandwidth: Option, + /// Optional propagation delay in ms. + pub delay: Option, + /// Optional packet loss rate in percent. + pub plr: Option, +} + +impl Pipe { + /// Creates a new pipe with the given ID. The ID must be unique. + pub fn new(id: usize) -> Self { + Self { + id, + bandwidth: None, + delay: None, + plr: None, + } + } + + /// Set the bandwidth cap of the pipe in Kbps. + pub fn bandwidth(mut self, bandwidth: u64) -> Self { + self.bandwidth = Some(bandwidth); + self + } + + /// Set the propagation delay of the pipe in ms. + pub fn delay(mut self, delay: u64) -> Self { + self.delay = Some(delay); + self + } + + /// Set the packet loss rate of the pipe in percent. + pub fn plr(mut self, plr: f64) -> Self { + self.plr = Some(plr); + self + } + + pub fn id(&self) -> usize { + self.id + } + + /// Builds the command to create the pipe. + pub fn build(&self) -> Command { + let mut cmd = Command::new("sudo"); + + cmd.arg("dnctl") + .arg("pipe") + .arg(self.id.to_string()) + .arg("config"); + + if let Some(bandwidth) = self.bandwidth { + let bw = format!("{}Kbit/s", bandwidth); + + cmd.args(["bw", &bw]); + } + + if let Some(delay) = self.delay { + cmd.args(["delay", &delay.to_string()]); + } + + if let Some(plr) = self.plr { + cmd.args(["plr", &plr.to_string()]); + } + + cmd + } + + /// Builds the command to destroy the pipe. + pub fn destroy(self) -> Command { + let mut cmd = Command::new("sudo"); + cmd.arg("dnctl") + .arg("pipe") + .arg("delete") + .arg(self.id.to_string()); + + cmd + } +} + +/// A wrapper around the `pfctl` command tailored to enable dummynet simulations. +pub struct PacketFilter { + /// The name of the PF anchor to use. + anchor: String, + /// The supported protocols. + protocols: Vec, + + /// The ID of the pipe. + pipe_id: Option, + /// The target endpoint of the pipe. + endpoint: Option, +} + +impl Default for PacketFilter { + fn default() -> Self { + Self { + anchor: "msg-sim".to_string(), + protocols: vec![Protocol::TCP, Protocol::UDP, Protocol::ICMP], + pipe_id: None, + endpoint: None, + } + } +} + +impl PacketFilter { + /// Set the dummynet pipe ID to target. + pub fn pipe_id(mut self, id: usize) -> Self { + self.pipe_id = Some(id); + self + } + + /// Set the target endpoint for the pipe. + pub fn endpoint(mut self, addr: IpAddr) -> Self { + self.endpoint = Some(addr); + self + } + + /// Set the supported protocols. + pub fn protocols(mut self, protocols: Vec) -> Self { + self.protocols = protocols; + self + } + + pub fn anchor(mut self, anchor: String) -> Self { + self.anchor = anchor; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn cmd_to_string(cmd: &Command) -> String { + let mut cmd_str = format!("{}", cmd.get_program().to_string_lossy()); + for arg in cmd.get_args() { + cmd_str.push(' '); + cmd_str.push_str(&arg.to_string_lossy()); + } + + cmd_str + } + + #[test] + fn test_pipe_build_cmd() { + let cmd = Pipe::new(1).bandwidth(10).delay(100).plr(0.1).build(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!( + cmd_str, + "sudo dnctl pipe 1 config bw 10Kbit/s delay 100 plr 0.1" + ); + + let cmd = Pipe::new(2).delay(1000).plr(10.0).build(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!(cmd_str, "sudo dnctl pipe 2 config delay 1000 plr 10"); + + let cmd = Pipe::new(3).build(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!(cmd_str, "sudo dnctl pipe 3 config"); + } + + #[test] + fn test_pipe_destroy_cmd() { + let pipe = Pipe::new(3); + let cmd = pipe.destroy(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!(cmd_str, "sudo dnctl pipe delete 3") + } +} diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index e91cbfa..1f7fb56 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -1,12 +1,21 @@ -use std::{collections::HashMap, time::Duration}; +use std::{collections::HashMap, net::IpAddr, time::Duration}; use protocol::Protocol; mod cmd; mod protocol; +#[cfg(target_os = "macos")] +mod dummynet; + +const KIB: u64 = 1024; +const MIB: u64 = 1024 * KIB; +const GIB: u64 = 1024 * MIB; + /// A type alias for a network device. -pub type Device = String; +pub struct Endpoint { + device: String, +} #[derive(Debug)] pub struct SimConfig { @@ -21,7 +30,7 @@ pub struct SimConfig { #[derive(Default)] pub struct Simulator { /// A map of active simulations. - active_sims: HashMap, + active_sims: HashMap, } impl Simulator { @@ -32,19 +41,16 @@ impl Simulator { } /// Starts a new simulation on the given device according to the config. - pub fn start(&mut self, device: Device, config: SimConfig) { - let mut simulation = Simulation { - device: device.clone(), - config, - }; + pub fn start(&mut self, endpoint: IpAddr, config: SimConfig) { + let mut simulation = Simulation { endpoint, config }; simulation.start(); - self.active_sims.insert(device, simulation); + self.active_sims.insert(endpoint, simulation); } /// Stops the simulation on the given device. - pub fn stop(&mut self, device: Device) { + pub fn stop(&mut self, device: IpAddr) { // This will drop the simulation, which will kill the process. self.active_sims.remove(&device); } @@ -52,29 +58,28 @@ impl Simulator { /// An active simulation. struct Simulation { - device: Device, + endpoint: IpAddr, config: SimConfig, } impl Simulation { /// Starts the simulation. - fn start(&mut self) { - // TODO: start the simulation by executing the right command - #[cfg(target_os = "linux")] - {} + #[cfg(target_os = "linux")] + fn start(&mut self) {} - #[cfg(target_os = "macos")] - {} - } + #[cfg(target_os = "macos")] + fn start(&mut self) {} } impl Drop for Simulation { - fn drop(&mut self) { - // TODO: kill the simulation by executing the right command - #[cfg(target_os = "linux")] - {} + #[cfg(target_os = "linux")] + fn drop(&mut self) {} - #[cfg(target_os = "macos")] - {} - } + #[cfg(target_os = "macos")] + fn drop(&mut self) {} +} + +#[cfg(test)] +mod tests { + use super::*; } From 39b5b1a96e3ba73f1f1851cd5ffc6d50aa537793 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 12:09:24 +0100 Subject: [PATCH 05/19] feat(sim): basic macos dummynet implementation done --- Cargo.lock | 115 ++++++++++++++++++ Cargo.toml | 3 + msg-sim/Cargo.toml | 1 + msg-sim/src/dummynet.rs | 257 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 351 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38376f3..364e361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,6 +508,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "half" version = "1.8.2" @@ -554,6 +560,15 @@ dependencies = [ "str_stack", ] +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "is-terminal" version = "0.4.10" @@ -717,6 +732,9 @@ dependencies = [ [[package]] name = "msg-sim" version = "0.1.1" +dependencies = [ + "pnet", +] [[package]] name = "msg-socket" @@ -784,6 +802,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -935,6 +959,97 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pnet" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "130c5b738eeda2dc5796fe2671e49027e6935e817ab51b930a36ec9e6a206a64" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5854abf0067ebbd3967f7d45ebc8976ff577ff0c7bd101c4973ae3c70f98fe" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417c0becd1b573f6d544f73671070b039051e5ad819cc64aa96377b536128d00" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2637e14d7de974ee2f74393afccbc8704f3e54e6eb31488715e72481d1662cc3" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + [[package]] name = "powerfmt" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 90fc61a..4b59360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,9 @@ flate2 = "1" zstd = "0.13" snap = "1" +# simulation +pnet = "0.34" + [profile.dev] opt-level = 1 overflow-checks = false diff --git a/msg-sim/Cargo.toml b/msg-sim/Cargo.toml index 707a569..3c1595f 100644 --- a/msg-sim/Cargo.toml +++ b/msg-sim/Cargo.toml @@ -12,3 +12,4 @@ repository.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +pnet.workspace = true diff --git a/msg-sim/src/dummynet.rs b/msg-sim/src/dummynet.rs index d046680..502144b 100644 --- a/msg-sim/src/dummynet.rs +++ b/msg-sim/src/dummynet.rs @@ -1,4 +1,8 @@ -use std::{net::IpAddr, process::Command}; +use std::{ + io::{self, Read}, + net::IpAddr, + process::{Command, ExitStatus, Stdio}, +}; use crate::protocol::Protocol; @@ -47,8 +51,7 @@ impl Pipe { self.id } - /// Builds the command to create the pipe. - pub fn build(&self) -> Command { + fn build_cmd(&self) -> Command { let mut cmd = Command::new("sudo"); cmd.arg("dnctl") @@ -73,8 +76,16 @@ impl Pipe { cmd } - /// Builds the command to destroy the pipe. - pub fn destroy(self) -> Command { + /// Builds the command to create the pipe. + pub fn build(&self) -> io::Result<()> { + let status = self.build_cmd().status()?; + + assert_status(status, "Failed to build pipe")?; + + Ok(()) + } + + fn destroy_cmd(&self) -> Command { let mut cmd = Command::new("sudo"); cmd.arg("dnctl") .arg("pipe") @@ -83,38 +94,42 @@ impl Pipe { cmd } + + /// Destroys the pipe. + pub fn destroy(self) -> io::Result<()> { + let status = self.destroy_cmd().status()?; + + assert_status(status, "Failed destroying pipe")?; + + Ok(()) + } } /// A wrapper around the `pfctl` command tailored to enable dummynet simulations. pub struct PacketFilter { + /// The pipe itself. + pipe: Pipe, /// The name of the PF anchor to use. anchor: String, /// The supported protocols. protocols: Vec, - - /// The ID of the pipe. - pipe_id: Option, /// The target endpoint of the pipe. endpoint: Option, + /// The name of the loopback interface + loopback: String, } -impl Default for PacketFilter { - fn default() -> Self { +impl PacketFilter { + /// Creates a new default packet filter from the given [`Pipe`]. + pub fn new(pipe: Pipe) -> Self { Self { + pipe, anchor: "msg-sim".to_string(), protocols: vec![Protocol::TCP, Protocol::UDP, Protocol::ICMP], - pipe_id: None, endpoint: None, + loopback: get_loopback_name(), } } -} - -impl PacketFilter { - /// Set the dummynet pipe ID to target. - pub fn pipe_id(mut self, id: usize) -> Self { - self.pipe_id = Some(id); - self - } /// Set the target endpoint for the pipe. pub fn endpoint(mut self, addr: IpAddr) -> Self { @@ -128,14 +143,191 @@ impl PacketFilter { self } - pub fn anchor(mut self, anchor: String) -> Self { - self.anchor = anchor; + /// Set the name of the anchor. + pub fn anchor(mut self, anchor: impl Into) -> Self { + self.anchor = anchor.into(); self } + + /// Enables the packet filter by executing the correct shell commands. + pub fn enable(&self) -> io::Result<()> { + // Build the pipe. + self.pipe.build()?; + + self.create_loopback_alias()?; + + self.load_pf_config()?; + self.apply_dummynet_rule()?; + + // Enable the packet filter + let status = Command::new("sudo").args(["pfctl", "-E"]).status()?; + + assert_status(status, "Failed to enable packet filter")?; + + Ok(()) + } + + /// Destroys the packet filter by executing the correct shell commands. + pub fn destroy(self) -> io::Result<()> { + let status = Command::new("sudo") + .args(["pfctl", "-f", "/etc/pf.conf"]) + .status()?; + + assert_status(status, "Failed to flush packet filter")?; + + let status = Command::new("sudo").args(["pfctl", "-d"]).status()?; + + assert_status(status, "Failed to disable packet filter")?; + + // Remove the loopback alias + let status = Command::new("sudo") + .args([ + "ifconfig", + &self.loopback, + "-alias", + &self.endpoint.unwrap().to_string(), + ]) + .status()?; + + assert_status(status, "Failed to remove the loopback alias")?; + + // Reset the MTU of the loopback interface + + let status = Command::new("sudo") + .args(["ifconfig", &self.loopback, "mtu", "16384"]) + .status()?; + + assert_status(status, "Failed to reset loopback MTU back to 16384")?; + + // Destroy the pipe + self.pipe.destroy()?; + + Ok(()) + } + + fn create_loopback_alias(&self) -> io::Result<()> { + let status = Command::new("sudo") + .args([ + "ifconfig", + &self.loopback, + "alias", + &self.endpoint.unwrap().to_string(), + ]) + .status()?; + + assert_status(status, "Failed to create loopback alias")?; + + let status = Command::new("sudo") + .args(["ifconfig", &self.loopback, "mtu", "1500"]) + .status()?; + + assert_status(status, "Failed to set loopback MTU to 1500")?; + + Ok(()) + } + + /// Loads pfctl config with anchor by executing this command: + /// `(cat /etc/pf.conf && echo "dummynet-anchor \"msg-sim\"" && + /// echo "anchor \"msg-sim\"") | sudo pfctl -f -` + fn load_pf_config(&self) -> io::Result<()> { + let echo_cmd = format!( + "dummynet-anchor \"{}\"\nanchor \"{}\"", + self.anchor, self.anchor + ); + + let mut cat = Command::new("cat") + .arg("/etc/pf.conf") + .stdout(Stdio::piped()) + .spawn()?; + + let cat_stdout = cat.stdout.take().unwrap(); + + let mut echo = Command::new("echo") + .arg(echo_cmd) + .stdout(Stdio::piped()) + .spawn()?; + + let echo_stdout = echo.stdout.take().unwrap(); + + let mut pfctl = Command::new("sudo") + .arg("pfctl") + .arg("-f") + .arg("-") + .stdin(Stdio::piped()) + .spawn()?; + + let pfctl_stdin = pfctl.stdin.as_mut().unwrap(); + io::copy(&mut cat_stdout.chain(echo_stdout), pfctl_stdin)?; + + let status = pfctl.wait()?; + + assert_status(status, "Failed to load pfctl config")?; + + Ok(()) + } + + /// Applies a rule to match traffic from any to the alias and sends that through the pipe + /// by executing this command: + /// `echo 'dummynet in from any to 127.0.0.3 pipe 1' | sudo pfctl -a msg-sim -f -` + fn apply_dummynet_rule(&self) -> io::Result<()> { + // Ensure endpoint and pipe ID are set + let endpoint = self.endpoint.expect("No endpoint set"); + let pipe_id = self.pipe.id(); + + let echo_command = format!("dummynet in from any to {} pipe {}", endpoint, pipe_id); + + // Set up the echo command + let mut echo = Command::new("echo") + .arg(echo_command) + .stdout(Stdio::piped()) + .spawn()?; + + if let Some(echo_stdout) = echo.stdout.take() { + // Set up the pfctl command + let mut pfctl = Command::new("sudo") + .arg("pfctl") + .arg("-a") + .arg(&self.anchor) + .arg("-f") + .arg("-") + .stdin(echo_stdout) + .spawn()?; + + pfctl.wait()?; + } + + Ok(()) + } +} + +/// Returns the name of the loopback interface. +/// +/// ## Panics +/// Panics if no loopback interface is found. +fn get_loopback_name() -> String { + let interfaces = pnet::datalink::interfaces(); + let loopback = interfaces.into_iter().find(|iface| iface.is_loopback()); + + loopback.expect("No loopback interface").name +} + +/// Assert that the given status is successful, otherwise return an error with the given message. +/// The type of the error will be `io::ErrorKind::Other`. +fn assert_status(status: ExitStatus, error: E) -> io::Result<()> +where + E: Into>, +{ + if !status.success() { + return Err(io::Error::new(io::ErrorKind::Other, error)); + } + + Ok(()) } #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; fn cmd_to_string(cmd: &Command) -> String { @@ -150,7 +342,7 @@ mod tests { #[test] fn test_pipe_build_cmd() { - let cmd = Pipe::new(1).bandwidth(10).delay(100).plr(0.1).build(); + let cmd = Pipe::new(1).bandwidth(10).delay(100).plr(0.1).build_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!( @@ -158,12 +350,12 @@ mod tests { "sudo dnctl pipe 1 config bw 10Kbit/s delay 100 plr 0.1" ); - let cmd = Pipe::new(2).delay(1000).plr(10.0).build(); + let cmd = Pipe::new(2).delay(1000).plr(10.0).build_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!(cmd_str, "sudo dnctl pipe 2 config delay 1000 plr 10"); - let cmd = Pipe::new(3).build(); + let cmd = Pipe::new(3).build_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!(cmd_str, "sudo dnctl pipe 3 config"); @@ -172,9 +364,24 @@ mod tests { #[test] fn test_pipe_destroy_cmd() { let pipe = Pipe::new(3); - let cmd = pipe.destroy(); + let cmd = pipe.destroy_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!(cmd_str, "sudo dnctl pipe delete 3") } + + #[test] + fn dummy_tests() { + let pipe = Pipe::new(3).bandwidth(100).delay(300); + + let endpoint = "127.0.0.2".parse().unwrap(); + let pf = PacketFilter::new(pipe) + .endpoint(endpoint) + .anchor("msg-sim-test"); + + pf.enable().unwrap(); + + std::thread::sleep(Duration::from_secs(20)); + pf.destroy().unwrap(); + } } From a1f6450812856abb353a26307494a5f16625c429 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 12:24:35 +0100 Subject: [PATCH 06/19] feat(sim): implement macos dummynet simulation in simulator --- msg-sim/src/cmd/linux.rs | 0 msg-sim/src/cmd/macos.rs | 16 ------- msg-sim/src/cmd/mod.rs | 5 -- msg-sim/src/dummynet.rs | 4 +- msg-sim/src/lib.rs | 99 ++++++++++++++++++++++++++++------------ 5 files changed, 72 insertions(+), 52 deletions(-) delete mode 100644 msg-sim/src/cmd/linux.rs delete mode 100644 msg-sim/src/cmd/macos.rs delete mode 100644 msg-sim/src/cmd/mod.rs diff --git a/msg-sim/src/cmd/linux.rs b/msg-sim/src/cmd/linux.rs deleted file mode 100644 index e69de29..0000000 diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs deleted file mode 100644 index 19c0aa5..0000000 --- a/msg-sim/src/cmd/macos.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! This module contains all the commands necessary to start and stop a network simulation -//! on MacOS. -//! -//! ## Implementation -//! Under the hood, this module builds a dummy network pipe with `dnctl` and `pfctl` and applies -//! the given configuration to it. - -const PFCTL: &str = "pfctl"; -const DNCTL: &str = "dnctl"; -const IFCONFIG: &str = "ifconfig"; - -pub struct Pfctl; - -pub struct Dnctl; - -pub struct Ifconfig; diff --git a/msg-sim/src/cmd/mod.rs b/msg-sim/src/cmd/mod.rs deleted file mode 100644 index 204fd43..0000000 --- a/msg-sim/src/cmd/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(target_os = "linux")] -pub mod linux; - -#[cfg(target_os = "macos")] -pub mod macos; diff --git a/msg-sim/src/dummynet.rs b/msg-sim/src/dummynet.rs index 502144b..a2e9377 100644 --- a/msg-sim/src/dummynet.rs +++ b/msg-sim/src/dummynet.rs @@ -13,7 +13,7 @@ pub struct Pipe { /// Optional bandwidth cap in Kbps. pub bandwidth: Option, /// Optional propagation delay in ms. - pub delay: Option, + pub delay: Option, /// Optional packet loss rate in percent. pub plr: Option, } @@ -36,7 +36,7 @@ impl Pipe { } /// Set the propagation delay of the pipe in ms. - pub fn delay(mut self, delay: u64) -> Self { + pub fn delay(mut self, delay: u128) -> Self { self.delay = Some(delay); self } diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index 1f7fb56..009d7a7 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -1,29 +1,23 @@ -use std::{collections::HashMap, net::IpAddr, time::Duration}; +use std::{collections::HashMap, io, net::IpAddr, time::Duration}; use protocol::Protocol; -mod cmd; mod protocol; #[cfg(target_os = "macos")] -mod dummynet; - -const KIB: u64 = 1024; -const MIB: u64 = 1024 * KIB; -const GIB: u64 = 1024 * MIB; - -/// A type alias for a network device. -pub struct Endpoint { - device: String, -} +pub mod dummynet; +#[cfg(target_os = "macos")] +use dummynet::{PacketFilter, Pipe}; #[derive(Debug)] -pub struct SimConfig { - latency: Duration, - target_bw: u64, - default_bw: u64, +pub struct SimulationConfig { + /// The latency of the connection. + latency: Option, + /// The bandwidth in Kbps. + bw: Option, /// The packet loss rate in percent. - packet_loss_rate: f64, + plr: Option, + /// The supported protocols. protocols: Vec, } @@ -31,22 +25,36 @@ pub struct SimConfig { pub struct Simulator { /// A map of active simulations. active_sims: HashMap, + /// Simulation ID counter. + sim_id: usize, } impl Simulator { pub fn new() -> Self { Self { active_sims: HashMap::new(), + sim_id: 0, } } - /// Starts a new simulation on the given device according to the config. - pub fn start(&mut self, endpoint: IpAddr, config: SimConfig) { - let mut simulation = Simulation { endpoint, config }; + /// Starts a new simulation on the given endpoint according to the config. + pub fn start(&mut self, endpoint: IpAddr, config: SimulationConfig) -> io::Result { + let id = self.sim_id; - simulation.start(); + let mut simulation = Simulation { + endpoint, + config, + id, + active_pf: None, + }; + + simulation.start()?; + + self.sim_id += 1; self.active_sims.insert(endpoint, simulation); + + Ok(id) } /// Stops the simulation on the given device. @@ -58,8 +66,12 @@ impl Simulator { /// An active simulation. struct Simulation { + id: usize, endpoint: IpAddr, - config: SimConfig, + config: SimulationConfig, + + #[cfg(target_os = "macos")] + active_pf: Option, } impl Simulation { @@ -68,7 +80,37 @@ impl Simulation { fn start(&mut self) {} #[cfg(target_os = "macos")] - fn start(&mut self) {} + fn start(&mut self) -> io::Result<()> { + // Create a dummynet pipe + let mut pipe = Pipe::new(self.id); + + // Configure the pipe according to the simulation config. + if let Some(latency) = self.config.latency { + pipe = pipe.delay(latency.as_millis()); + } + + if let Some(bw) = self.config.bw { + pipe = pipe.bandwidth(bw); + } + + if let Some(plr) = self.config.plr { + pipe = pipe.plr(plr); + } + + let mut pf = PacketFilter::new(pipe) + .anchor(format!("msg-sim-{}", self.id)) + .endpoint(self.endpoint); + + if !self.config.protocols.is_empty() { + pf = pf.protocols(self.config.protocols.clone()); + } + + pf.enable()?; + + self.active_pf = Some(pf); + + Ok(()) + } } impl Drop for Simulation { @@ -76,10 +118,9 @@ impl Drop for Simulation { fn drop(&mut self) {} #[cfg(target_os = "macos")] - fn drop(&mut self) {} -} - -#[cfg(test)] -mod tests { - use super::*; + fn drop(&mut self) { + if let Some(pf) = self.active_pf.take() { + pf.destroy().unwrap(); + } + } } From 52518bac71c108bbc411a8c14cd5ef37593ec224 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 12:30:41 +0100 Subject: [PATCH 07/19] fix(sim): fix linux CI --- msg-sim/src/lib.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index 009d7a7..5eeb7b8 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -41,12 +41,7 @@ impl Simulator { pub fn start(&mut self, endpoint: IpAddr, config: SimulationConfig) -> io::Result { let id = self.sim_id; - let mut simulation = Simulation { - endpoint, - config, - id, - active_pf: None, - }; + let mut simulation = Simulation::new(id, endpoint, config); simulation.start()?; @@ -75,9 +70,21 @@ struct Simulation { } impl Simulation { + fn new(id: usize, endpoint: IpAddr, config: SimulationConfig) -> Self { + Self { + id, + endpoint, + config, + #[cfg(target_os = "macos")] + active_pf: None, + } + } + /// Starts the simulation. #[cfg(target_os = "linux")] - fn start(&mut self) {} + fn start(&mut self) -> io::Result<()> { + Ok(()) + } #[cfg(target_os = "macos")] fn start(&mut self) -> io::Result<()> { From 0e02507d2d4a202d40beef40a6362c64aa919ec1 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 14:26:12 +0100 Subject: [PATCH 08/19] refactor: cargo manifest updates --- Cargo.toml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b59360..17724e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,16 +7,18 @@ members = [ "msg-common", "msg-sim", ] +resolver = "2" [workspace.package] version = "0.1.1" edition = "2021" rust-version = "1.70" # Remember to update .clippy.toml and README.md -license = "MIT OR Apache-2.0" +license = "MIT" description = "A flexible and lightweight messaging library for distributed systems" authors = ["Jonas Bostoen", "Nicolas Racchi"] homepage = "https://github.com/chainbound/msg-rs" repository = "https://github.com/chainbound/msg-rs" +keywords = ["messaging", "distributed", "systems", "networking", "quic", "quinn", "tokio", "async", "simulation", "pnet", "udp", "tcp", "socket"] [workspace.dependencies] msg-wire = { path = "./msg-wire" } @@ -32,26 +34,28 @@ tokio-util = { version = "0.7", features = ["codec"] } futures = "0.3" tokio-stream = { version = "0.1", features = ["sync"] } parking_lot = "0.12" -criterion = { version = "0.5", features = ["async_tokio"] } -pprof = { version = "0.13", features = ["flamegraph", "criterion"] } + +# general bytes = "1" thiserror = "1" tracing = "0.1" +rustc-hash = "1" rand = "0.8" -# transport +# NETWORKING quinn = "0.10" # rustls needs to be the same version as the one used by quinn rustls = { version = "0.21", features = ["quic", "dangerous_configuration"] } rcgen = "0.12" - -# performance -rustc-hash = "1" flate2 = "1" zstd = "0.13" snap = "1" +# benchmarking & profiling +criterion = { version = "0.5", features = ["async_tokio"] } +pprof = { version = "0.13", features = ["flamegraph", "criterion"] } + # simulation pnet = "0.34" From 58359febae6eb4db996ad7c82a45ce359a482b1f Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 14:27:26 +0100 Subject: [PATCH 09/19] chore(sim): clippy for Linux target --- msg-sim/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index 5eeb7b8..023a5e3 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -10,6 +10,7 @@ pub mod dummynet; use dummynet::{PacketFilter, Pipe}; #[derive(Debug)] +#[allow(unused)] pub struct SimulationConfig { /// The latency of the connection. latency: Option, @@ -60,6 +61,7 @@ impl Simulator { } /// An active simulation. +#[allow(unused)] struct Simulation { id: usize, endpoint: IpAddr, From 20c0d3782b6f3a61de305f5a5180885d4a132949 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 4 Jan 2024 16:32:18 +0100 Subject: [PATCH 10/19] feat(sim): scaffold simulation crate --- Cargo.lock | 4 +++ Cargo.toml | 10 ++++++- msg-sim/Cargo.toml | 14 +++++++++ msg-sim/src/lib.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 msg-sim/Cargo.toml create mode 100644 msg-sim/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3ee2841..8139b42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -715,6 +715,10 @@ dependencies = [ "tokio", ] +[[package]] +name = "msg-sim" +version = "0.1.1" + [[package]] name = "msg-socket" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 6c75990..90fc61a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,12 @@ [workspace] -members = ["msg", "msg-socket", "msg-wire", "msg-transport", "msg-common"] +members = [ + "msg", + "msg-socket", + "msg-wire", + "msg-transport", + "msg-common", + "msg-sim", +] [workspace.package] version = "0.1.1" @@ -16,6 +23,7 @@ msg-wire = { path = "./msg-wire" } msg-socket = { path = "./msg-socket" } msg-transport = { path = "./msg-transport" } msg-common = { path = "./msg-common" } +msg-sim = { path = "./msg-sim" } # async async-trait = "0.1" diff --git a/msg-sim/Cargo.toml b/msg-sim/Cargo.toml new file mode 100644 index 0000000..707a569 --- /dev/null +++ b/msg-sim/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "msg-sim" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +description.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs new file mode 100644 index 0000000..fc1fe46 --- /dev/null +++ b/msg-sim/src/lib.rs @@ -0,0 +1,72 @@ +use std::{collections::HashMap, time::Duration}; + +/// A type alias for a network device. +pub type Device = String; + +#[derive(Debug)] +pub struct SimConfig { + latency: Duration, + target_bw: u64, + default_bw: u64, + packet_loss: f64, +} + +pub struct Simulator { + /// A map of active simulations. + active_sims: HashMap, +} + +impl Simulator { + pub fn new() -> Self { + Self { + active_sims: HashMap::new(), + } + } + + /// Starts a new simulation on the given device according to the config. + pub fn start(&mut self, device: Device, config: SimConfig) { + let mut simulation = Simulation { + device: device.clone(), + config, + }; + + simulation.start(); + + self.active_sims.insert(device, simulation); + } + + /// Stops the simulation on the given device. + pub fn stop(&mut self, device: Device) { + // This will drop the simulation, which will kill the process. + self.active_sims.remove(&device); + } +} + +/// An active simulation. +struct Simulation { + device: Device, + config: SimConfig, +} + +impl Simulation { + /// Starts the simulation. + fn start(&mut self) { + // TODO: start the simulation by executing the right command + #[cfg(target_os = "linux")] + {} + + #[cfg(target_os = "macos")] + {} + } +} + +impl Drop for Simulation { + fn drop(&mut self) { + // TODO: kill the simulation by executing the right command + #[cfg(target_os = "linux")] + {} + + #[cfg(target_os = "macos")] + {} + } +} From 1138ed8d0a3acc259d54f987b30a5dc36d348e74 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Sun, 7 Jan 2024 16:58:01 +0100 Subject: [PATCH 11/19] feat(sim): docs, more scaffolding --- msg-sim/README.md | 25 +++++++++++++++++++++++++ msg-sim/src/cmd/linux.rs | 0 msg-sim/src/cmd/macos.rs | 4 ++++ msg-sim/src/cmd/mod.rs | 5 +++++ msg-sim/src/lib.rs | 7 +++++++ msg-sim/src/protocol.rs | 18 ++++++++++++++++++ 6 files changed, 59 insertions(+) create mode 100644 msg-sim/README.md create mode 100644 msg-sim/src/cmd/linux.rs create mode 100644 msg-sim/src/cmd/macos.rs create mode 100644 msg-sim/src/cmd/mod.rs create mode 100644 msg-sim/src/protocol.rs diff --git a/msg-sim/README.md b/msg-sim/README.md new file mode 100644 index 0000000..5bd03d3 --- /dev/null +++ b/msg-sim/README.md @@ -0,0 +1,25 @@ +# `msg-sim` + +## Overview +This crate provides functionality to simulate real-world network conditions over an interface (e.g. `lo0`) for testing and benchmarking purposes. +It only works on MacOS and Linux. + +## Implementation + +### MacOS +* https://gist.github.com/tellyworth/2ce28add99fe743c702c090c8144355e + +* `dnctl` for creating a dummynet pipe + +Example: +```bash +dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror` +``` + +* `pfctl` for creating a rule to match traffic and send it through the pipe + +Example: +```bash +echo "dummynet out proto tcp from any to any pipe 1" | sudo pfctl -f - +pfctl -e +``` diff --git a/msg-sim/src/cmd/linux.rs b/msg-sim/src/cmd/linux.rs new file mode 100644 index 0000000..e69de29 diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs new file mode 100644 index 0000000..6b12b94 --- /dev/null +++ b/msg-sim/src/cmd/macos.rs @@ -0,0 +1,4 @@ +//! This module contains all the commands necessary to start and stop a simulation. +//! +//! ## Implementation +//! Under the hood, this module uses the `pfctl` command. diff --git a/msg-sim/src/cmd/mod.rs b/msg-sim/src/cmd/mod.rs new file mode 100644 index 0000000..204fd43 --- /dev/null +++ b/msg-sim/src/cmd/mod.rs @@ -0,0 +1,5 @@ +#[cfg(target_os = "linux")] +pub mod linux; + +#[cfg(target_os = "macos")] +pub mod macos; diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index fc1fe46..e393299 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -1,5 +1,10 @@ use std::{collections::HashMap, time::Duration}; +use protocol::Protocol; + +mod cmd; +mod protocol; + /// A type alias for a network device. pub type Device = String; @@ -9,8 +14,10 @@ pub struct SimConfig { target_bw: u64, default_bw: u64, packet_loss: f64, + protocols: Vec, } +#[derive(Default)] pub struct Simulator { /// A map of active simulations. active_sims: HashMap, diff --git a/msg-sim/src/protocol.rs b/msg-sim/src/protocol.rs new file mode 100644 index 0000000..38d96b5 --- /dev/null +++ b/msg-sim/src/protocol.rs @@ -0,0 +1,18 @@ +#[derive(Debug, Clone, Copy)] +#[allow(clippy::upper_case_acronyms)] +pub enum Protocol { + TCP, + UDP, + ICMP, +} + +impl From<&str> for Protocol { + fn from(s: &str) -> Self { + match s { + "tcp" => Self::TCP, + "udp" => Self::UDP, + "icmp" => Self::ICMP, + _ => panic!("invalid protocol"), + } + } +} From 583afd56041b557d1b02639eb1321c743a02ea38 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Mon, 8 Jan 2024 14:20:07 +0100 Subject: [PATCH 12/19] doc(sim): update macos sim docs --- msg-sim/README.md | 33 +++++++++++++++++++++++++++------ msg-sim/src/cmd/macos.rs | 6 ++++-- msg-sim/src/lib.rs | 3 ++- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/msg-sim/README.md b/msg-sim/README.md index 5bd03d3..f555f74 100644 --- a/msg-sim/README.md +++ b/msg-sim/README.md @@ -7,19 +7,40 @@ It only works on MacOS and Linux. ## Implementation ### MacOS -* https://gist.github.com/tellyworth/2ce28add99fe743c702c090c8144355e +On MacOS, we use a combination of the `pfctl` and `dnctl` tools. +`pfctl` is a tool to manage the packet filter device. `dnctl` can manage +the dummynet traffic shaper. -* `dnctl` for creating a dummynet pipe +The general flow is as follows: + +* Create a dummynet pipe with `dnctl` and configure it with `bw`, `delay`, `plr` and `noerror` parameters. Example: ```bash -dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror` +sudo dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror ``` -* `pfctl` for creating a rule to match traffic and send it through the pipe +* Use `pfctl` to create a rule to match traffic and send it through the pipe Example: ```bash -echo "dummynet out proto tcp from any to any pipe 1" | sudo pfctl -f - -pfctl -e +# Create an anchor (a named container for rules) +(cat /etc/pf.conf && echo "dummynet-anchor \"msg-sim\"" && \ +echo "anchor \"msg-sim\"") | sudo pfctl -f - + +INTERFACE="lo0" +echo 'dummynet in on $INTERFACE all pipe 1' | sudo pfctl -a msg-sim -f - + +# Enable the packet filter +sudo pfctl -E +``` + +* `pfctl` for removing the rule +```bash +# Apply the default configuration +sudo pfctl -f /etc/pf.conf +# Disable the packet filter +sudo pfctl -d +# Remove the dummynet pipes +sudo dnctl -q flush ``` diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs index 6b12b94..b0cdcc2 100644 --- a/msg-sim/src/cmd/macos.rs +++ b/msg-sim/src/cmd/macos.rs @@ -1,4 +1,6 @@ -//! This module contains all the commands necessary to start and stop a simulation. +//! This module contains all the commands necessary to start and stop a network simulation +//! on MacOS. //! //! ## Implementation -//! Under the hood, this module uses the `pfctl` command. +//! Under the hood, this module builds a dummy network pipe with `dnctl` and `pfctl` and applies +//! the given configuration to it. diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index e393299..e91cbfa 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -13,7 +13,8 @@ pub struct SimConfig { latency: Duration, target_bw: u64, default_bw: u64, - packet_loss: f64, + /// The packet loss rate in percent. + packet_loss_rate: f64, protocols: Vec, } From e7406e255a13e73f674c80eb9b4dcb04f6fa9397 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Tue, 9 Jan 2024 13:04:44 +0100 Subject: [PATCH 13/19] feat(sim): docs, more scaffolding --- msg-sim/README.md | 39 ++++++--- msg-sim/src/cmd/macos.rs | 10 +++ msg-sim/src/dummynet.rs | 180 +++++++++++++++++++++++++++++++++++++++ msg-sim/src/lib.rs | 55 ++++++------ 4 files changed, 249 insertions(+), 35 deletions(-) create mode 100644 msg-sim/src/dummynet.rs diff --git a/msg-sim/README.md b/msg-sim/README.md index f555f74..d72cb93 100644 --- a/msg-sim/README.md +++ b/msg-sim/README.md @@ -1,46 +1,65 @@ # `msg-sim` ## Overview -This crate provides functionality to simulate real-world network conditions over an interface (e.g. `lo0`) for testing and benchmarking purposes. +This crate provides functionality to simulate real-world network conditions +locally to and from a specific endpoint for testing and benchmarking purposes. It only works on MacOS and Linux. ## Implementation ### MacOS On MacOS, we use a combination of the `pfctl` and `dnctl` tools. -`pfctl` is a tool to manage the packet filter device. `dnctl` can manage -the dummynet traffic shaper. +[`pfctl`](https://man.freebsd.org/cgi/man.cgi?query=pfctl&apropos=0&sektion=8&manpath=FreeBSD+14.0-RELEASE+and+Ports&arch=default&format=html) is a tool to manage the packet filter device. [`dnctl`](https://man.freebsd.org/cgi/man.cgi?query=dnctl&sektion=8&format=html) can manage +the [dummynet](http://info.iet.unipi.it/~luigi/papers/20100304-ccr.pdf) traffic shaper. The general flow is as follows: -* Create a dummynet pipe with `dnctl` and configure it with `bw`, `delay`, `plr` and `noerror` parameters. +* Create a dummynet pipe with `dnctl` and configure it with `bw`, `delay`, `plr` Example: ```bash -sudo dnctl pipe 1 config bw 10Kbit/s delay 300 plr 0.1 noerror +sudo dnctl pipe 1 config bw 10Kbit/s delay 50 plr 0.1 +``` + +* Create a loopback alias with `ifconfig` to simulate a different endpoint and +set the MTU to the usual value (1500) + +Example: +```bash +sudo ifconfig lo0 alias 127.0.0.3 up +sudo ifconfig lo0 mtu 1500 ``` * Use `pfctl` to create a rule to match traffic and send it through the pipe Example: ```bash -# Create an anchor (a named container for rules) +# Create an anchor (a named container for rules, close to a namespace) (cat /etc/pf.conf && echo "dummynet-anchor \"msg-sim\"" && \ echo "anchor \"msg-sim\"") | sudo pfctl -f - -INTERFACE="lo0" -echo 'dummynet in on $INTERFACE all pipe 1' | sudo pfctl -a msg-sim -f - +# Create a rule to match traffic from any to the alias and send it through the pipe +echo 'dummynet in from any to 127.0.0.3 pipe 1' | sudo pfctl -a msg-sim -f - # Enable the packet filter sudo pfctl -E ``` -* `pfctl` for removing the rule +* Remove the rules and the pipe ```bash # Apply the default configuration sudo pfctl -f /etc/pf.conf # Disable the packet filter sudo pfctl -d +# Remove the alias & reset the MTU +sudo ifconfig lo0 -alias 127.0.0.3 +sudo ifconfig lo0 mtu 16384 # Remove the dummynet pipes -sudo dnctl -q flush +sudo dnctl pipe delete 1 ``` + +### Questions +- Do we need to create 2 pipes to simulate a bidirectional link? MAN page seems to say so. + +### Linux +On Linux, we use dummy interfaces and `tc` with `netem` to simulate and shape traffic. diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs index b0cdcc2..19c0aa5 100644 --- a/msg-sim/src/cmd/macos.rs +++ b/msg-sim/src/cmd/macos.rs @@ -4,3 +4,13 @@ //! ## Implementation //! Under the hood, this module builds a dummy network pipe with `dnctl` and `pfctl` and applies //! the given configuration to it. + +const PFCTL: &str = "pfctl"; +const DNCTL: &str = "dnctl"; +const IFCONFIG: &str = "ifconfig"; + +pub struct Pfctl; + +pub struct Dnctl; + +pub struct Ifconfig; diff --git a/msg-sim/src/dummynet.rs b/msg-sim/src/dummynet.rs new file mode 100644 index 0000000..d046680 --- /dev/null +++ b/msg-sim/src/dummynet.rs @@ -0,0 +1,180 @@ +use std::{net::IpAddr, process::Command}; + +use crate::protocol::Protocol; + +/// Pipe represents a dummynet pipe. +pub struct Pipe { + /// The ID of the pipe. + pub id: usize, + /// Optional bandwidth cap in Kbps. + pub bandwidth: Option, + /// Optional propagation delay in ms. + pub delay: Option, + /// Optional packet loss rate in percent. + pub plr: Option, +} + +impl Pipe { + /// Creates a new pipe with the given ID. The ID must be unique. + pub fn new(id: usize) -> Self { + Self { + id, + bandwidth: None, + delay: None, + plr: None, + } + } + + /// Set the bandwidth cap of the pipe in Kbps. + pub fn bandwidth(mut self, bandwidth: u64) -> Self { + self.bandwidth = Some(bandwidth); + self + } + + /// Set the propagation delay of the pipe in ms. + pub fn delay(mut self, delay: u64) -> Self { + self.delay = Some(delay); + self + } + + /// Set the packet loss rate of the pipe in percent. + pub fn plr(mut self, plr: f64) -> Self { + self.plr = Some(plr); + self + } + + pub fn id(&self) -> usize { + self.id + } + + /// Builds the command to create the pipe. + pub fn build(&self) -> Command { + let mut cmd = Command::new("sudo"); + + cmd.arg("dnctl") + .arg("pipe") + .arg(self.id.to_string()) + .arg("config"); + + if let Some(bandwidth) = self.bandwidth { + let bw = format!("{}Kbit/s", bandwidth); + + cmd.args(["bw", &bw]); + } + + if let Some(delay) = self.delay { + cmd.args(["delay", &delay.to_string()]); + } + + if let Some(plr) = self.plr { + cmd.args(["plr", &plr.to_string()]); + } + + cmd + } + + /// Builds the command to destroy the pipe. + pub fn destroy(self) -> Command { + let mut cmd = Command::new("sudo"); + cmd.arg("dnctl") + .arg("pipe") + .arg("delete") + .arg(self.id.to_string()); + + cmd + } +} + +/// A wrapper around the `pfctl` command tailored to enable dummynet simulations. +pub struct PacketFilter { + /// The name of the PF anchor to use. + anchor: String, + /// The supported protocols. + protocols: Vec, + + /// The ID of the pipe. + pipe_id: Option, + /// The target endpoint of the pipe. + endpoint: Option, +} + +impl Default for PacketFilter { + fn default() -> Self { + Self { + anchor: "msg-sim".to_string(), + protocols: vec![Protocol::TCP, Protocol::UDP, Protocol::ICMP], + pipe_id: None, + endpoint: None, + } + } +} + +impl PacketFilter { + /// Set the dummynet pipe ID to target. + pub fn pipe_id(mut self, id: usize) -> Self { + self.pipe_id = Some(id); + self + } + + /// Set the target endpoint for the pipe. + pub fn endpoint(mut self, addr: IpAddr) -> Self { + self.endpoint = Some(addr); + self + } + + /// Set the supported protocols. + pub fn protocols(mut self, protocols: Vec) -> Self { + self.protocols = protocols; + self + } + + pub fn anchor(mut self, anchor: String) -> Self { + self.anchor = anchor; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn cmd_to_string(cmd: &Command) -> String { + let mut cmd_str = format!("{}", cmd.get_program().to_string_lossy()); + for arg in cmd.get_args() { + cmd_str.push(' '); + cmd_str.push_str(&arg.to_string_lossy()); + } + + cmd_str + } + + #[test] + fn test_pipe_build_cmd() { + let cmd = Pipe::new(1).bandwidth(10).delay(100).plr(0.1).build(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!( + cmd_str, + "sudo dnctl pipe 1 config bw 10Kbit/s delay 100 plr 0.1" + ); + + let cmd = Pipe::new(2).delay(1000).plr(10.0).build(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!(cmd_str, "sudo dnctl pipe 2 config delay 1000 plr 10"); + + let cmd = Pipe::new(3).build(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!(cmd_str, "sudo dnctl pipe 3 config"); + } + + #[test] + fn test_pipe_destroy_cmd() { + let pipe = Pipe::new(3); + let cmd = pipe.destroy(); + let cmd_str = cmd_to_string(&cmd); + + assert_eq!(cmd_str, "sudo dnctl pipe delete 3") + } +} diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index e91cbfa..1f7fb56 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -1,12 +1,21 @@ -use std::{collections::HashMap, time::Duration}; +use std::{collections::HashMap, net::IpAddr, time::Duration}; use protocol::Protocol; mod cmd; mod protocol; +#[cfg(target_os = "macos")] +mod dummynet; + +const KIB: u64 = 1024; +const MIB: u64 = 1024 * KIB; +const GIB: u64 = 1024 * MIB; + /// A type alias for a network device. -pub type Device = String; +pub struct Endpoint { + device: String, +} #[derive(Debug)] pub struct SimConfig { @@ -21,7 +30,7 @@ pub struct SimConfig { #[derive(Default)] pub struct Simulator { /// A map of active simulations. - active_sims: HashMap, + active_sims: HashMap, } impl Simulator { @@ -32,19 +41,16 @@ impl Simulator { } /// Starts a new simulation on the given device according to the config. - pub fn start(&mut self, device: Device, config: SimConfig) { - let mut simulation = Simulation { - device: device.clone(), - config, - }; + pub fn start(&mut self, endpoint: IpAddr, config: SimConfig) { + let mut simulation = Simulation { endpoint, config }; simulation.start(); - self.active_sims.insert(device, simulation); + self.active_sims.insert(endpoint, simulation); } /// Stops the simulation on the given device. - pub fn stop(&mut self, device: Device) { + pub fn stop(&mut self, device: IpAddr) { // This will drop the simulation, which will kill the process. self.active_sims.remove(&device); } @@ -52,29 +58,28 @@ impl Simulator { /// An active simulation. struct Simulation { - device: Device, + endpoint: IpAddr, config: SimConfig, } impl Simulation { /// Starts the simulation. - fn start(&mut self) { - // TODO: start the simulation by executing the right command - #[cfg(target_os = "linux")] - {} + #[cfg(target_os = "linux")] + fn start(&mut self) {} - #[cfg(target_os = "macos")] - {} - } + #[cfg(target_os = "macos")] + fn start(&mut self) {} } impl Drop for Simulation { - fn drop(&mut self) { - // TODO: kill the simulation by executing the right command - #[cfg(target_os = "linux")] - {} + #[cfg(target_os = "linux")] + fn drop(&mut self) {} - #[cfg(target_os = "macos")] - {} - } + #[cfg(target_os = "macos")] + fn drop(&mut self) {} +} + +#[cfg(test)] +mod tests { + use super::*; } From 0029f47c5410ad2710e9cdec1885b2960034d91f Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 12:09:24 +0100 Subject: [PATCH 14/19] feat(sim): basic macos dummynet implementation done --- Cargo.lock | 115 ++++++++++++++++++ Cargo.toml | 3 + msg-sim/Cargo.toml | 1 + msg-sim/src/dummynet.rs | 257 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 351 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8139b42..d2d16b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,6 +508,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "half" version = "1.8.2" @@ -554,6 +560,15 @@ dependencies = [ "str_stack", ] +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "is-terminal" version = "0.4.10" @@ -718,6 +733,9 @@ dependencies = [ [[package]] name = "msg-sim" version = "0.1.1" +dependencies = [ + "pnet", +] [[package]] name = "msg-socket" @@ -785,6 +803,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -936,6 +960,97 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pnet" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "130c5b738eeda2dc5796fe2671e49027e6935e817ab51b930a36ec9e6a206a64" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cf6fb3ab38b68d01ab2aea03ed3d1132b4868fa4e06285f29f16da01c5f4c" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5854abf0067ebbd3967f7d45ebc8976ff577ff0c7bd101c4973ae3c70f98fe" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688b17499eee04a0408aca0aa5cba5fc86401d7216de8a63fdf7a4c227871804" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea925b72f4bd37f8eab0f221bbe4c78b63498350c983ffa9dd4bcde7e030f56" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a005825396b7fe7a38a8e288dbc342d5034dac80c15212436424fef8ea90ba" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417c0becd1b573f6d544f73671070b039051e5ad819cc64aa96377b536128d00" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2637e14d7de974ee2f74393afccbc8704f3e54e6eb31488715e72481d1662cc3" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + [[package]] name = "powerfmt" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 90fc61a..4b59360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,9 @@ flate2 = "1" zstd = "0.13" snap = "1" +# simulation +pnet = "0.34" + [profile.dev] opt-level = 1 overflow-checks = false diff --git a/msg-sim/Cargo.toml b/msg-sim/Cargo.toml index 707a569..3c1595f 100644 --- a/msg-sim/Cargo.toml +++ b/msg-sim/Cargo.toml @@ -12,3 +12,4 @@ repository.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +pnet.workspace = true diff --git a/msg-sim/src/dummynet.rs b/msg-sim/src/dummynet.rs index d046680..502144b 100644 --- a/msg-sim/src/dummynet.rs +++ b/msg-sim/src/dummynet.rs @@ -1,4 +1,8 @@ -use std::{net::IpAddr, process::Command}; +use std::{ + io::{self, Read}, + net::IpAddr, + process::{Command, ExitStatus, Stdio}, +}; use crate::protocol::Protocol; @@ -47,8 +51,7 @@ impl Pipe { self.id } - /// Builds the command to create the pipe. - pub fn build(&self) -> Command { + fn build_cmd(&self) -> Command { let mut cmd = Command::new("sudo"); cmd.arg("dnctl") @@ -73,8 +76,16 @@ impl Pipe { cmd } - /// Builds the command to destroy the pipe. - pub fn destroy(self) -> Command { + /// Builds the command to create the pipe. + pub fn build(&self) -> io::Result<()> { + let status = self.build_cmd().status()?; + + assert_status(status, "Failed to build pipe")?; + + Ok(()) + } + + fn destroy_cmd(&self) -> Command { let mut cmd = Command::new("sudo"); cmd.arg("dnctl") .arg("pipe") @@ -83,38 +94,42 @@ impl Pipe { cmd } + + /// Destroys the pipe. + pub fn destroy(self) -> io::Result<()> { + let status = self.destroy_cmd().status()?; + + assert_status(status, "Failed destroying pipe")?; + + Ok(()) + } } /// A wrapper around the `pfctl` command tailored to enable dummynet simulations. pub struct PacketFilter { + /// The pipe itself. + pipe: Pipe, /// The name of the PF anchor to use. anchor: String, /// The supported protocols. protocols: Vec, - - /// The ID of the pipe. - pipe_id: Option, /// The target endpoint of the pipe. endpoint: Option, + /// The name of the loopback interface + loopback: String, } -impl Default for PacketFilter { - fn default() -> Self { +impl PacketFilter { + /// Creates a new default packet filter from the given [`Pipe`]. + pub fn new(pipe: Pipe) -> Self { Self { + pipe, anchor: "msg-sim".to_string(), protocols: vec![Protocol::TCP, Protocol::UDP, Protocol::ICMP], - pipe_id: None, endpoint: None, + loopback: get_loopback_name(), } } -} - -impl PacketFilter { - /// Set the dummynet pipe ID to target. - pub fn pipe_id(mut self, id: usize) -> Self { - self.pipe_id = Some(id); - self - } /// Set the target endpoint for the pipe. pub fn endpoint(mut self, addr: IpAddr) -> Self { @@ -128,14 +143,191 @@ impl PacketFilter { self } - pub fn anchor(mut self, anchor: String) -> Self { - self.anchor = anchor; + /// Set the name of the anchor. + pub fn anchor(mut self, anchor: impl Into) -> Self { + self.anchor = anchor.into(); self } + + /// Enables the packet filter by executing the correct shell commands. + pub fn enable(&self) -> io::Result<()> { + // Build the pipe. + self.pipe.build()?; + + self.create_loopback_alias()?; + + self.load_pf_config()?; + self.apply_dummynet_rule()?; + + // Enable the packet filter + let status = Command::new("sudo").args(["pfctl", "-E"]).status()?; + + assert_status(status, "Failed to enable packet filter")?; + + Ok(()) + } + + /// Destroys the packet filter by executing the correct shell commands. + pub fn destroy(self) -> io::Result<()> { + let status = Command::new("sudo") + .args(["pfctl", "-f", "/etc/pf.conf"]) + .status()?; + + assert_status(status, "Failed to flush packet filter")?; + + let status = Command::new("sudo").args(["pfctl", "-d"]).status()?; + + assert_status(status, "Failed to disable packet filter")?; + + // Remove the loopback alias + let status = Command::new("sudo") + .args([ + "ifconfig", + &self.loopback, + "-alias", + &self.endpoint.unwrap().to_string(), + ]) + .status()?; + + assert_status(status, "Failed to remove the loopback alias")?; + + // Reset the MTU of the loopback interface + + let status = Command::new("sudo") + .args(["ifconfig", &self.loopback, "mtu", "16384"]) + .status()?; + + assert_status(status, "Failed to reset loopback MTU back to 16384")?; + + // Destroy the pipe + self.pipe.destroy()?; + + Ok(()) + } + + fn create_loopback_alias(&self) -> io::Result<()> { + let status = Command::new("sudo") + .args([ + "ifconfig", + &self.loopback, + "alias", + &self.endpoint.unwrap().to_string(), + ]) + .status()?; + + assert_status(status, "Failed to create loopback alias")?; + + let status = Command::new("sudo") + .args(["ifconfig", &self.loopback, "mtu", "1500"]) + .status()?; + + assert_status(status, "Failed to set loopback MTU to 1500")?; + + Ok(()) + } + + /// Loads pfctl config with anchor by executing this command: + /// `(cat /etc/pf.conf && echo "dummynet-anchor \"msg-sim\"" && + /// echo "anchor \"msg-sim\"") | sudo pfctl -f -` + fn load_pf_config(&self) -> io::Result<()> { + let echo_cmd = format!( + "dummynet-anchor \"{}\"\nanchor \"{}\"", + self.anchor, self.anchor + ); + + let mut cat = Command::new("cat") + .arg("/etc/pf.conf") + .stdout(Stdio::piped()) + .spawn()?; + + let cat_stdout = cat.stdout.take().unwrap(); + + let mut echo = Command::new("echo") + .arg(echo_cmd) + .stdout(Stdio::piped()) + .spawn()?; + + let echo_stdout = echo.stdout.take().unwrap(); + + let mut pfctl = Command::new("sudo") + .arg("pfctl") + .arg("-f") + .arg("-") + .stdin(Stdio::piped()) + .spawn()?; + + let pfctl_stdin = pfctl.stdin.as_mut().unwrap(); + io::copy(&mut cat_stdout.chain(echo_stdout), pfctl_stdin)?; + + let status = pfctl.wait()?; + + assert_status(status, "Failed to load pfctl config")?; + + Ok(()) + } + + /// Applies a rule to match traffic from any to the alias and sends that through the pipe + /// by executing this command: + /// `echo 'dummynet in from any to 127.0.0.3 pipe 1' | sudo pfctl -a msg-sim -f -` + fn apply_dummynet_rule(&self) -> io::Result<()> { + // Ensure endpoint and pipe ID are set + let endpoint = self.endpoint.expect("No endpoint set"); + let pipe_id = self.pipe.id(); + + let echo_command = format!("dummynet in from any to {} pipe {}", endpoint, pipe_id); + + // Set up the echo command + let mut echo = Command::new("echo") + .arg(echo_command) + .stdout(Stdio::piped()) + .spawn()?; + + if let Some(echo_stdout) = echo.stdout.take() { + // Set up the pfctl command + let mut pfctl = Command::new("sudo") + .arg("pfctl") + .arg("-a") + .arg(&self.anchor) + .arg("-f") + .arg("-") + .stdin(echo_stdout) + .spawn()?; + + pfctl.wait()?; + } + + Ok(()) + } +} + +/// Returns the name of the loopback interface. +/// +/// ## Panics +/// Panics if no loopback interface is found. +fn get_loopback_name() -> String { + let interfaces = pnet::datalink::interfaces(); + let loopback = interfaces.into_iter().find(|iface| iface.is_loopback()); + + loopback.expect("No loopback interface").name +} + +/// Assert that the given status is successful, otherwise return an error with the given message. +/// The type of the error will be `io::ErrorKind::Other`. +fn assert_status(status: ExitStatus, error: E) -> io::Result<()> +where + E: Into>, +{ + if !status.success() { + return Err(io::Error::new(io::ErrorKind::Other, error)); + } + + Ok(()) } #[cfg(test)] mod tests { + use std::time::Duration; + use super::*; fn cmd_to_string(cmd: &Command) -> String { @@ -150,7 +342,7 @@ mod tests { #[test] fn test_pipe_build_cmd() { - let cmd = Pipe::new(1).bandwidth(10).delay(100).plr(0.1).build(); + let cmd = Pipe::new(1).bandwidth(10).delay(100).plr(0.1).build_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!( @@ -158,12 +350,12 @@ mod tests { "sudo dnctl pipe 1 config bw 10Kbit/s delay 100 plr 0.1" ); - let cmd = Pipe::new(2).delay(1000).plr(10.0).build(); + let cmd = Pipe::new(2).delay(1000).plr(10.0).build_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!(cmd_str, "sudo dnctl pipe 2 config delay 1000 plr 10"); - let cmd = Pipe::new(3).build(); + let cmd = Pipe::new(3).build_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!(cmd_str, "sudo dnctl pipe 3 config"); @@ -172,9 +364,24 @@ mod tests { #[test] fn test_pipe_destroy_cmd() { let pipe = Pipe::new(3); - let cmd = pipe.destroy(); + let cmd = pipe.destroy_cmd(); let cmd_str = cmd_to_string(&cmd); assert_eq!(cmd_str, "sudo dnctl pipe delete 3") } + + #[test] + fn dummy_tests() { + let pipe = Pipe::new(3).bandwidth(100).delay(300); + + let endpoint = "127.0.0.2".parse().unwrap(); + let pf = PacketFilter::new(pipe) + .endpoint(endpoint) + .anchor("msg-sim-test"); + + pf.enable().unwrap(); + + std::thread::sleep(Duration::from_secs(20)); + pf.destroy().unwrap(); + } } From b939b562718ac7dca1cb532b02abf5c4ec9fe675 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 12:24:35 +0100 Subject: [PATCH 15/19] feat(sim): implement macos dummynet simulation in simulator --- msg-sim/src/cmd/linux.rs | 0 msg-sim/src/cmd/macos.rs | 16 ------- msg-sim/src/cmd/mod.rs | 5 -- msg-sim/src/dummynet.rs | 4 +- msg-sim/src/lib.rs | 99 ++++++++++++++++++++++++++++------------ 5 files changed, 72 insertions(+), 52 deletions(-) delete mode 100644 msg-sim/src/cmd/linux.rs delete mode 100644 msg-sim/src/cmd/macos.rs delete mode 100644 msg-sim/src/cmd/mod.rs diff --git a/msg-sim/src/cmd/linux.rs b/msg-sim/src/cmd/linux.rs deleted file mode 100644 index e69de29..0000000 diff --git a/msg-sim/src/cmd/macos.rs b/msg-sim/src/cmd/macos.rs deleted file mode 100644 index 19c0aa5..0000000 --- a/msg-sim/src/cmd/macos.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! This module contains all the commands necessary to start and stop a network simulation -//! on MacOS. -//! -//! ## Implementation -//! Under the hood, this module builds a dummy network pipe with `dnctl` and `pfctl` and applies -//! the given configuration to it. - -const PFCTL: &str = "pfctl"; -const DNCTL: &str = "dnctl"; -const IFCONFIG: &str = "ifconfig"; - -pub struct Pfctl; - -pub struct Dnctl; - -pub struct Ifconfig; diff --git a/msg-sim/src/cmd/mod.rs b/msg-sim/src/cmd/mod.rs deleted file mode 100644 index 204fd43..0000000 --- a/msg-sim/src/cmd/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(target_os = "linux")] -pub mod linux; - -#[cfg(target_os = "macos")] -pub mod macos; diff --git a/msg-sim/src/dummynet.rs b/msg-sim/src/dummynet.rs index 502144b..a2e9377 100644 --- a/msg-sim/src/dummynet.rs +++ b/msg-sim/src/dummynet.rs @@ -13,7 +13,7 @@ pub struct Pipe { /// Optional bandwidth cap in Kbps. pub bandwidth: Option, /// Optional propagation delay in ms. - pub delay: Option, + pub delay: Option, /// Optional packet loss rate in percent. pub plr: Option, } @@ -36,7 +36,7 @@ impl Pipe { } /// Set the propagation delay of the pipe in ms. - pub fn delay(mut self, delay: u64) -> Self { + pub fn delay(mut self, delay: u128) -> Self { self.delay = Some(delay); self } diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index 1f7fb56..009d7a7 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -1,29 +1,23 @@ -use std::{collections::HashMap, net::IpAddr, time::Duration}; +use std::{collections::HashMap, io, net::IpAddr, time::Duration}; use protocol::Protocol; -mod cmd; mod protocol; #[cfg(target_os = "macos")] -mod dummynet; - -const KIB: u64 = 1024; -const MIB: u64 = 1024 * KIB; -const GIB: u64 = 1024 * MIB; - -/// A type alias for a network device. -pub struct Endpoint { - device: String, -} +pub mod dummynet; +#[cfg(target_os = "macos")] +use dummynet::{PacketFilter, Pipe}; #[derive(Debug)] -pub struct SimConfig { - latency: Duration, - target_bw: u64, - default_bw: u64, +pub struct SimulationConfig { + /// The latency of the connection. + latency: Option, + /// The bandwidth in Kbps. + bw: Option, /// The packet loss rate in percent. - packet_loss_rate: f64, + plr: Option, + /// The supported protocols. protocols: Vec, } @@ -31,22 +25,36 @@ pub struct SimConfig { pub struct Simulator { /// A map of active simulations. active_sims: HashMap, + /// Simulation ID counter. + sim_id: usize, } impl Simulator { pub fn new() -> Self { Self { active_sims: HashMap::new(), + sim_id: 0, } } - /// Starts a new simulation on the given device according to the config. - pub fn start(&mut self, endpoint: IpAddr, config: SimConfig) { - let mut simulation = Simulation { endpoint, config }; + /// Starts a new simulation on the given endpoint according to the config. + pub fn start(&mut self, endpoint: IpAddr, config: SimulationConfig) -> io::Result { + let id = self.sim_id; - simulation.start(); + let mut simulation = Simulation { + endpoint, + config, + id, + active_pf: None, + }; + + simulation.start()?; + + self.sim_id += 1; self.active_sims.insert(endpoint, simulation); + + Ok(id) } /// Stops the simulation on the given device. @@ -58,8 +66,12 @@ impl Simulator { /// An active simulation. struct Simulation { + id: usize, endpoint: IpAddr, - config: SimConfig, + config: SimulationConfig, + + #[cfg(target_os = "macos")] + active_pf: Option, } impl Simulation { @@ -68,7 +80,37 @@ impl Simulation { fn start(&mut self) {} #[cfg(target_os = "macos")] - fn start(&mut self) {} + fn start(&mut self) -> io::Result<()> { + // Create a dummynet pipe + let mut pipe = Pipe::new(self.id); + + // Configure the pipe according to the simulation config. + if let Some(latency) = self.config.latency { + pipe = pipe.delay(latency.as_millis()); + } + + if let Some(bw) = self.config.bw { + pipe = pipe.bandwidth(bw); + } + + if let Some(plr) = self.config.plr { + pipe = pipe.plr(plr); + } + + let mut pf = PacketFilter::new(pipe) + .anchor(format!("msg-sim-{}", self.id)) + .endpoint(self.endpoint); + + if !self.config.protocols.is_empty() { + pf = pf.protocols(self.config.protocols.clone()); + } + + pf.enable()?; + + self.active_pf = Some(pf); + + Ok(()) + } } impl Drop for Simulation { @@ -76,10 +118,9 @@ impl Drop for Simulation { fn drop(&mut self) {} #[cfg(target_os = "macos")] - fn drop(&mut self) {} -} - -#[cfg(test)] -mod tests { - use super::*; + fn drop(&mut self) { + if let Some(pf) = self.active_pf.take() { + pf.destroy().unwrap(); + } + } } From 8026d9a4e60f9b3df03844ece0ba396e4c246e03 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 12:30:41 +0100 Subject: [PATCH 16/19] fix(sim): fix linux CI --- msg-sim/src/lib.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index 009d7a7..5eeb7b8 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -41,12 +41,7 @@ impl Simulator { pub fn start(&mut self, endpoint: IpAddr, config: SimulationConfig) -> io::Result { let id = self.sim_id; - let mut simulation = Simulation { - endpoint, - config, - id, - active_pf: None, - }; + let mut simulation = Simulation::new(id, endpoint, config); simulation.start()?; @@ -75,9 +70,21 @@ struct Simulation { } impl Simulation { + fn new(id: usize, endpoint: IpAddr, config: SimulationConfig) -> Self { + Self { + id, + endpoint, + config, + #[cfg(target_os = "macos")] + active_pf: None, + } + } + /// Starts the simulation. #[cfg(target_os = "linux")] - fn start(&mut self) {} + fn start(&mut self) -> io::Result<()> { + Ok(()) + } #[cfg(target_os = "macos")] fn start(&mut self) -> io::Result<()> { From 86bcb7dafbe55477c5e63ad815d7d5e292abd2c3 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 14:26:12 +0100 Subject: [PATCH 17/19] refactor: cargo manifest updates --- Cargo.toml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b59360..17724e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,16 +7,18 @@ members = [ "msg-common", "msg-sim", ] +resolver = "2" [workspace.package] version = "0.1.1" edition = "2021" rust-version = "1.70" # Remember to update .clippy.toml and README.md -license = "MIT OR Apache-2.0" +license = "MIT" description = "A flexible and lightweight messaging library for distributed systems" authors = ["Jonas Bostoen", "Nicolas Racchi"] homepage = "https://github.com/chainbound/msg-rs" repository = "https://github.com/chainbound/msg-rs" +keywords = ["messaging", "distributed", "systems", "networking", "quic", "quinn", "tokio", "async", "simulation", "pnet", "udp", "tcp", "socket"] [workspace.dependencies] msg-wire = { path = "./msg-wire" } @@ -32,26 +34,28 @@ tokio-util = { version = "0.7", features = ["codec"] } futures = "0.3" tokio-stream = { version = "0.1", features = ["sync"] } parking_lot = "0.12" -criterion = { version = "0.5", features = ["async_tokio"] } -pprof = { version = "0.13", features = ["flamegraph", "criterion"] } + +# general bytes = "1" thiserror = "1" tracing = "0.1" +rustc-hash = "1" rand = "0.8" -# transport +# NETWORKING quinn = "0.10" # rustls needs to be the same version as the one used by quinn rustls = { version = "0.21", features = ["quic", "dangerous_configuration"] } rcgen = "0.12" - -# performance -rustc-hash = "1" flate2 = "1" zstd = "0.13" snap = "1" +# benchmarking & profiling +criterion = { version = "0.5", features = ["async_tokio"] } +pprof = { version = "0.13", features = ["flamegraph", "criterion"] } + # simulation pnet = "0.34" From 10833b627093446feb1ad5d9868f5946187795d9 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 11 Jan 2024 14:27:26 +0100 Subject: [PATCH 18/19] chore(sim): clippy for Linux target --- msg-sim/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/msg-sim/src/lib.rs b/msg-sim/src/lib.rs index 5eeb7b8..023a5e3 100644 --- a/msg-sim/src/lib.rs +++ b/msg-sim/src/lib.rs @@ -10,6 +10,7 @@ pub mod dummynet; use dummynet::{PacketFilter, Pipe}; #[derive(Debug)] +#[allow(unused)] pub struct SimulationConfig { /// The latency of the connection. latency: Option, @@ -60,6 +61,7 @@ impl Simulator { } /// An active simulation. +#[allow(unused)] struct Simulation { id: usize, endpoint: IpAddr, From c9b19b5fdd334778ab2144779fecf87777d2462c Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 18 Jan 2024 13:52:15 +0100 Subject: [PATCH 19/19] fix: various --- Cargo.lock | 7 ------- msg-sim/src/dummynet.rs | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20902cc..d2d16b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -737,13 +737,6 @@ dependencies = [ "pnet", ] -[[package]] -name = "msg-sim" -version = "0.1.1" -dependencies = [ - "pnet", -] - [[package]] name = "msg-socket" version = "0.1.1" diff --git a/msg-sim/src/dummynet.rs b/msg-sim/src/dummynet.rs index a2e9377..f5ea4b4 100644 --- a/msg-sim/src/dummynet.rs +++ b/msg-sim/src/dummynet.rs @@ -341,6 +341,7 @@ mod tests { } #[test] + #[ignore] fn test_pipe_build_cmd() { let cmd = Pipe::new(1).bandwidth(10).delay(100).plr(0.1).build_cmd(); let cmd_str = cmd_to_string(&cmd); @@ -362,6 +363,7 @@ mod tests { } #[test] + #[ignore] fn test_pipe_destroy_cmd() { let pipe = Pipe::new(3); let cmd = pipe.destroy_cmd(); @@ -371,6 +373,7 @@ mod tests { } #[test] + #[ignore] fn dummy_tests() { let pipe = Pipe::new(3).bandwidth(100).delay(300);