Skip to content

Commit

Permalink
chore(sim,linux): refactor; start simulation function with ip_tc utils
Browse files Browse the repository at this point in the history
  • Loading branch information
thedevbirb committed Feb 7, 2024
1 parent c866e48 commit 8e72144
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 226 deletions.
117 changes: 117 additions & 0 deletions msg-sim/src/ip_tc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::{io, net::IpAddr, process::Command};

use crate::assert::assert_status;

/// Take the arguments to run a command with `Command`
/// and add the prefix to run it in the namespace environment
#[inline]
fn add_namespace_prefix<'a>(namespace: &'a str, args: Vec<&'a str>) -> Vec<&'a str> {
let mut prefix_args = vec!["ip", "netns", "exec", namespace];
prefix_args.extend(args);
prefix_args
}

#[inline]
pub fn create_namespace(name: &str) -> io::Result<()> {
let status = Command::new("sudo")
.args(["ip", "netns", "add", &name])
.status()?;

assert_status(status, format!("Failed to create namespace {}", name))
}

/// Create Virtual Ethernet (veth) devices and link them
///
/// Note: device name length can be max 15 chars long
#[inline]
pub fn create_veth_pair(name1: &str, name2: &str) -> io::Result<()> {
let status = Command::new("sudo")
.args([
"ip", "link", "add", &name1, "type", "veth", "peer", "name", &name2,
])
.status()?;
assert_status(
status,
format!("Failed to create veth pair {}-{}", name1, name2),
)
}

#[inline]
pub fn move_device_to_namespace(name: &str, namespace: &str) -> io::Result<()> {
let status = Command::new("sudo")
.args(["ip", "link", "set", &name, "netns", &namespace])
.status()?;
assert_status(
status,
format!("Failed to move device {} to namespace {}", name, namespace),
)
}

/// Generate a host IPv4 address from the namespace IPv4 address.
/// This is done by incrementing the last octet of the IP by 1.
#[inline]
pub fn gen_host_ip_address(namespace_ip_addr: &IpAddr) -> String {
let mut ip_host: Vec<u64> = namespace_ip_addr
.to_string()
.split('.')
.map(|octect| octect.parse::<u64>().unwrap())
.collect();
ip_host[3] += 1;
let ip_host = format!(
"{}.{}.{}.{}/24",
ip_host[0], ip_host[1], ip_host[2], ip_host[3]
);
ip_host
}

/// add IP address to device
#[inline]
pub fn add_ip_addr_to_device(
device: &str,
ip_addr: &str,
namespace: Option<&str>,
) -> io::Result<()> {
let mut args = vec!["ip", "addr", "add", ip_addr, "dev", device];
if let Some(namespace) = namespace {
args = add_namespace_prefix(namespace, args)
};
let status = Command::new("sudo").args(args).status()?;
assert_status(
status,
format!("Failed to add IP address {} to device {}", ip_addr, device),
)
}

#[inline]
pub fn spin_up_device(name: &str, namespace: Option<&str>) -> io::Result<()> {
let mut args = vec!["ip", "link", "set", "dev", name, "up"];
if let Some(namespace) = namespace {
args = add_namespace_prefix(namespace, args)
};
let status = Command::new("sudo").args(args).status()?;
assert_status(status, format!("Failed to spin up device {}", name))
}

/// Add the provided network emulation parameters for the device
///
/// These parameters are appended to the following command: `tc qdisc add dev <device_name>`
#[inline]
pub fn add_network_emulation_parameters(
device_name: &str,
parameters: Vec<&str>,
namespace: Option<&str>,
) -> io::Result<()> {
let mut tc_command = vec!["tc", "qdisc", "add", "dev", &device_name];
if let Some(namespace) = namespace {
tc_command = add_namespace_prefix(namespace, tc_command)
};

let err_message = format!(
"Failed to add network emulation parameters {:?} to device {}",
&parameters, device_name
);

tc_command.extend(parameters);
let status = Command::new("sudo").args(tc_command).status()?;
assert_status(status, err_message)
}
225 changes: 36 additions & 189 deletions msg-sim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use dummynet::{PacketFilter, Pipe};
pub mod namespace;

pub mod assert;
use assert::assert_status;
pub mod ip_tc;

#[derive(Debug)]
pub struct SimulationConfig {
Expand Down Expand Up @@ -129,142 +129,24 @@ impl Simulation {
#[cfg(target_os = "linux")]
fn start(&self) -> io::Result<()> {
// Create network namespace
let network_namespace = self.namespace_name();

let status = Command::new("sudo")
.args(["ip", "netns", "add", &network_namespace])
.status()?;

assert_status(status, "Failed to create namespace")?;

// Create Virtual Ethernet (veth) devices and link them
//
// Note: device name length can be max 15 chars long
let network_namespace = self.namespace_name();
let veth_host = self.veth_host_name();
let veth_namespace = self.veth_namespace_name();
let status = Command::new("sudo")
.args([
"ip",
"link",
"add",
&veth_host,
"type",
"veth",
"peer",
"name",
&veth_namespace,
])
.status()?;

assert_status(status, "Failed add veth devices")?;

// Move veth namespace device to its namespace
let status = Command::new("sudo")
.args([
"ip",
"link",
"set",
&veth_namespace,
"netns",
&network_namespace,
])
.status()?;

assert_status(status, "Failed move veth device to network namespace")?;

let ip_namespace = format!("{}/24", self.endpoint);

let mut ip_host: Vec<u64> = self
.endpoint
.to_string()
.split('.')
.map(|octect| octect.parse::<u64>().unwrap())
.collect();
ip_host[3] += 1;
let ip_host = format!(
"{}.{}.{}.{}/24",
ip_host[0], ip_host[1], ip_host[2], ip_host[3]
);
ip_tc::create_namespace(&network_namespace)?;
ip_tc::create_veth_pair(&veth_host, &veth_namespace)?;
ip_tc::move_device_to_namespace(&veth_namespace, &network_namespace)?;

// Associate IP address to host veth device and spin it up
let status = Command::new("sudo")
.args(["ip", "addr", "add", &ip_host, "dev", &veth_host])
.status()?;
assert_status(status, "Failed to associate IP address to host veth device")?;
let status = Command::new("sudo")
.args(["ip", "link", "set", &veth_host, "up"])
.status()?;
assert_status(status, "Failed to set up the host veth device")?;

// Associate IP address to namespaced veth device and spin it up
let status = Command::new("sudo")
.args([
"ip",
"netns",
"exec",
&network_namespace,
"ip",
"addr",
"add",
&ip_namespace,
"dev",
&veth_namespace,
])
.status()?;
assert_status(
status,
"Failed to associate IP address to namespaced veth device",
)?;
let status = Command::new("sudo")
.args([
"ip",
"netns",
"exec",
&network_namespace,
"ip",
"link",
"set",
&veth_namespace,
"up",
])
.status()?;
assert_status(status, "Failed to set up the namespaced veth device")?;

// Spin up also the loopback interface on namespaced environment
let status = Command::new("sudo")
.args([
"ip",
"netns",
"exec",
&network_namespace,
"ip",
"link",
"set",
"lo",
"up",
])
.status()?;
assert_status(status, "Failed to set up the namespaced loopback device")?;
let ip_host = ip_tc::gen_host_ip_address(&self.endpoint);

// Add network emulation parameters (delay, loss) on namespaced veth device
//
// The behaviour is specified on the top-level ("root"),
// with a custom handle for identification
let mut args = vec![
"ip",
"netns",
"exec",
&network_namespace,
"tc",
"qdisc",
"add",
"dev",
&veth_namespace,
"root",
"handle",
"1:",
"netem",
];
ip_tc::add_ip_addr_to_device(&veth_host, &ip_host, None)?;
ip_tc::spin_up_device(&veth_host, None)?;

ip_tc::add_ip_addr_to_device(&veth_namespace, &ip_namespace, Some(&network_namespace))?;
ip_tc::spin_up_device(&veth_namespace, Some(&network_namespace))?;
ip_tc::spin_up_device("lo", Some(&network_namespace))?;

let delay = format!(
"{}ms",
Expand All @@ -274,34 +156,30 @@ impl Simulation {
.as_millis()
);

let loss = format!("{}%", self.config.plr.unwrap_or(0_f64));

// Add delay to the host veth device to match MacOS symmetric behaviour
//
// The behaviour is specified on the top-level ("root"),
// with a custom handle for identification
let mut args = vec!["root", "handle", "1:", "netem"];
if self.config.latency.is_some() {
args.push("delay");
args.push(&delay);
}
ip_tc::add_network_emulation_parameters(&veth_host, args, None)?;

let loss = format!("{}%", self.config.plr.unwrap_or(0_f64));

// Add network emulation parameters (delay, loss) on namespaced veth device
let mut args = vec!["root", "handle", "1:", "netem"];
if self.config.latency.is_some() {
args.push("delay");
args.push(&delay);
}
if (self.config.plr).is_some() {
args.push("loss");
args.push(&loss);
}

let status = Command::new("sudo").args(args).status()?;

assert_status(
status,
"Failed to set delay and loss network emulation parameters to namespaced device",
)?;

// Add delay to the host veth device to match MacOS symmetric behaviour
let status = Command::new("sudo")
.args([
"tc", "qdisc", "add", "dev", &veth_host, "root", "handle", "1:", "netem", "delay",
&delay,
])
.status()?;

assert_status(status, "Failed to set delay to the host veth device")?;
ip_tc::add_network_emulation_parameters(&veth_namespace, args, Some(&network_namespace))?;

// Add bandwidth paramteres on namespaced veth device
//
Expand All @@ -312,47 +190,16 @@ impl Simulation {
let burst = format!("{}kbit", self.config.burst.unwrap_or(32));
let limit = format!("{}", self.config.limit.unwrap_or(10_000));

let status = Command::new("sudo")
.args([
"ip",
"netns",
"exec",
&network_namespace,
"tc",
"qdisc",
"add",
"dev",
&veth_namespace,
"parent",
"1:",
"handle",
"2:",
"tbf",
"rate",
&bandwidth,
"burst",
&burst,
"limit",
&limit,
])
.status()?;

assert_status(
status,
"Failed to set bandwidth parameter to the namespaced device",
)?;
let args = vec![
"parent", "1:", "handle", "2:", "tbf", "rate", &bandwidth, "burst", &burst,
"limit", &limit,
];

// Add bandwidth paramteres on host veth device
let status = Command::new("sudo")
.args([
"tc", "qdisc", "add", "dev", &veth_host, "parent", "1:", "handle", "2:", "tbf",
"rate", &bandwidth, "burst", &burst, "limit", &limit,
])
.status()?;

assert_status(
status,
"Failed to set bandwidth parameter to the host veth device",
ip_tc::add_network_emulation_parameters(&veth_host, args.clone(), None)?;
ip_tc::add_network_emulation_parameters(
&veth_namespace,
args,
Some(&network_namespace),
)?;
}
Ok(())
Expand Down
Loading

0 comments on commit 8e72144

Please sign in to comment.