Skip to content

Commit 2921554

Browse files
committed
chore(sim,linux): refactor start simulation function with ip_tc utils
1 parent c866e48 commit 2921554

File tree

3 files changed

+156
-192
lines changed

3 files changed

+156
-192
lines changed

msg-sim/src/ip_tc.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use std::{io, net::IpAddr, process::Command};
2+
3+
use crate::assert::assert_status;
4+
5+
/// Take the arguments to run a command with `Command`
6+
/// and add the prefix to run it in the namespace environment
7+
#[inline]
8+
fn add_namespace_prefix<'a>(namespace: &'a str, args: Vec<&'a str>) -> Vec<&'a str> {
9+
let mut prefix_args = vec!["ip", "netns", "exec", namespace];
10+
prefix_args.extend(args);
11+
prefix_args
12+
}
13+
14+
#[inline]
15+
pub fn create_namespace(name: &str) -> io::Result<()> {
16+
let status = Command::new("sudo")
17+
.args(["ip", "netns", "add", &name])
18+
.status()?;
19+
20+
assert_status(status, format!("Failed to create namespace {}", name))
21+
}
22+
23+
/// Create Virtual Ethernet (veth) devices and link them
24+
///
25+
/// Note: device name length can be max 15 chars long
26+
#[inline]
27+
pub fn create_veth_pair(name1: &str, name2: &str) -> io::Result<()> {
28+
let status = Command::new("sudo")
29+
.args([
30+
"ip", "link", "add", &name1, "type", "veth", "peer", "name", &name2,
31+
])
32+
.status()?;
33+
assert_status(
34+
status,
35+
format!("Failed to create veth pair {}-{}", name1, name2),
36+
)
37+
}
38+
39+
#[inline]
40+
pub fn move_device_to_namespace(name: &str, namespace: &str) -> io::Result<()> {
41+
let status = Command::new("sudo")
42+
.args(["ip", "link", "set", &name, "netns", &namespace])
43+
.status()?;
44+
assert_status(
45+
status,
46+
format!("Failed to move device {} to namespace {}", name, namespace),
47+
)
48+
}
49+
50+
/// Generate a host IPv4 address from the namespace IPv4 address.
51+
/// This is done by incrementing the last octet of the IP by 1.
52+
#[inline]
53+
pub fn gen_host_ip_address(namespace_ip_addr: &IpAddr) -> String {
54+
let mut ip_host: Vec<u64> = namespace_ip_addr
55+
.to_string()
56+
.split('.')
57+
.map(|octect| octect.parse::<u64>().unwrap())
58+
.collect();
59+
ip_host[3] += 1;
60+
let ip_host = format!(
61+
"{}.{}.{}.{}/24",
62+
ip_host[0], ip_host[1], ip_host[2], ip_host[3]
63+
);
64+
ip_host
65+
}
66+
67+
/// add IP address to device
68+
#[inline]
69+
pub fn add_ip_addr_to_device(
70+
device: &str,
71+
ip_addr: &str,
72+
namespace: Option<&str>,
73+
) -> io::Result<()> {
74+
let mut args = vec!["ip", "addr", "add", ip_addr, "dev", device];
75+
if let Some(namespace) = namespace {
76+
args = add_namespace_prefix(namespace, args)
77+
};
78+
let status = Command::new("sudo").args(args).status()?;
79+
assert_status(
80+
status,
81+
format!("Failed to add IP address {} to device {}", ip_addr, device),
82+
)
83+
}
84+
85+
#[inline]
86+
pub fn spin_up_device(name: &str, namespace: Option<&str>) -> io::Result<()> {
87+
let mut args = vec!["ip", "link", "set", "dev", name, "up"];
88+
if let Some(namespace) = namespace {
89+
args = add_namespace_prefix(namespace, args)
90+
};
91+
let status = Command::new("sudo").args(args).status()?;
92+
assert_status(status, format!("Failed to spin up device {}", name))
93+
}
94+
95+
/// Add the provided network emulation parameters for the device
96+
///
97+
/// These parameters are appended to the following command: `tc qdisc add dev <device_name>`
98+
#[inline]
99+
pub fn add_network_emulation_parameters(
100+
device_name: &str,
101+
parameters: Vec<&str>,
102+
namespace: Option<&str>,
103+
) -> io::Result<()> {
104+
let mut tc_command = vec!["tc", "qdisc", "add", "dev", &device_name];
105+
if let Some(namespace) = namespace {
106+
tc_command = add_namespace_prefix(namespace, tc_command)
107+
};
108+
109+
let err_message = format!(
110+
"Failed to add network emulation parameters {:?} to device {}",
111+
&parameters, device_name
112+
);
113+
114+
tc_command.extend(parameters);
115+
let status = Command::new("sudo").args(tc_command).status()?;
116+
assert_status(status, err_message)
117+
}

msg-sim/src/lib.rs

Lines changed: 36 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use dummynet::{PacketFilter, Pipe};
1111
pub mod namespace;
1212

1313
pub mod assert;
14-
use assert::assert_status;
14+
pub mod ip_tc;
1515

1616
#[derive(Debug)]
1717
pub struct SimulationConfig {
@@ -129,142 +129,24 @@ impl Simulation {
129129
#[cfg(target_os = "linux")]
130130
fn start(&self) -> io::Result<()> {
131131
// Create network namespace
132-
let network_namespace = self.namespace_name();
133-
134-
let status = Command::new("sudo")
135-
.args(["ip", "netns", "add", &network_namespace])
136-
.status()?;
137-
138-
assert_status(status, "Failed to create namespace")?;
139132

140-
// Create Virtual Ethernet (veth) devices and link them
141-
//
142-
// Note: device name length can be max 15 chars long
133+
let network_namespace = self.namespace_name();
143134
let veth_host = self.veth_host_name();
144135
let veth_namespace = self.veth_namespace_name();
145-
let status = Command::new("sudo")
146-
.args([
147-
"ip",
148-
"link",
149-
"add",
150-
&veth_host,
151-
"type",
152-
"veth",
153-
"peer",
154-
"name",
155-
&veth_namespace,
156-
])
157-
.status()?;
158-
159-
assert_status(status, "Failed add veth devices")?;
160-
161-
// Move veth namespace device to its namespace
162-
let status = Command::new("sudo")
163-
.args([
164-
"ip",
165-
"link",
166-
"set",
167-
&veth_namespace,
168-
"netns",
169-
&network_namespace,
170-
])
171-
.status()?;
172-
173-
assert_status(status, "Failed move veth device to network namespace")?;
174-
175136
let ip_namespace = format!("{}/24", self.endpoint);
176137

177-
let mut ip_host: Vec<u64> = self
178-
.endpoint
179-
.to_string()
180-
.split('.')
181-
.map(|octect| octect.parse::<u64>().unwrap())
182-
.collect();
183-
ip_host[3] += 1;
184-
let ip_host = format!(
185-
"{}.{}.{}.{}/24",
186-
ip_host[0], ip_host[1], ip_host[2], ip_host[3]
187-
);
138+
ip_tc::create_namespace(&network_namespace)?;
139+
ip_tc::create_veth_pair(&veth_host, &veth_namespace)?;
140+
ip_tc::move_device_to_namespace(&veth_namespace, &network_namespace)?;
188141

189-
// Associate IP address to host veth device and spin it up
190-
let status = Command::new("sudo")
191-
.args(["ip", "addr", "add", &ip_host, "dev", &veth_host])
192-
.status()?;
193-
assert_status(status, "Failed to associate IP address to host veth device")?;
194-
let status = Command::new("sudo")
195-
.args(["ip", "link", "set", &veth_host, "up"])
196-
.status()?;
197-
assert_status(status, "Failed to set up the host veth device")?;
198-
199-
// Associate IP address to namespaced veth device and spin it up
200-
let status = Command::new("sudo")
201-
.args([
202-
"ip",
203-
"netns",
204-
"exec",
205-
&network_namespace,
206-
"ip",
207-
"addr",
208-
"add",
209-
&ip_namespace,
210-
"dev",
211-
&veth_namespace,
212-
])
213-
.status()?;
214-
assert_status(
215-
status,
216-
"Failed to associate IP address to namespaced veth device",
217-
)?;
218-
let status = Command::new("sudo")
219-
.args([
220-
"ip",
221-
"netns",
222-
"exec",
223-
&network_namespace,
224-
"ip",
225-
"link",
226-
"set",
227-
&veth_namespace,
228-
"up",
229-
])
230-
.status()?;
231-
assert_status(status, "Failed to set up the namespaced veth device")?;
232-
233-
// Spin up also the loopback interface on namespaced environment
234-
let status = Command::new("sudo")
235-
.args([
236-
"ip",
237-
"netns",
238-
"exec",
239-
&network_namespace,
240-
"ip",
241-
"link",
242-
"set",
243-
"lo",
244-
"up",
245-
])
246-
.status()?;
247-
assert_status(status, "Failed to set up the namespaced loopback device")?;
142+
let ip_host = ip_tc::gen_host_ip_address(&self.endpoint);
248143

249-
// Add network emulation parameters (delay, loss) on namespaced veth device
250-
//
251-
// The behaviour is specified on the top-level ("root"),
252-
// with a custom handle for identification
253-
let mut args = vec![
254-
"ip",
255-
"netns",
256-
"exec",
257-
&network_namespace,
258-
"tc",
259-
"qdisc",
260-
"add",
261-
"dev",
262-
&veth_namespace,
263-
"root",
264-
"handle",
265-
"1:",
266-
"netem",
267-
];
144+
ip_tc::add_ip_addr_to_device(&veth_host, &ip_host, None)?;
145+
ip_tc::spin_up_device(&veth_host, None)?;
146+
147+
ip_tc::add_ip_addr_to_device(&veth_namespace, &ip_namespace, Some(&network_namespace))?;
148+
ip_tc::spin_up_device(&veth_namespace, Some(&network_namespace))?;
149+
ip_tc::spin_up_device("lo", Some(&network_namespace))?;
268150

269151
let delay = format!(
270152
"{}ms",
@@ -274,34 +156,30 @@ impl Simulation {
274156
.as_millis()
275157
);
276158

159+
let loss = format!("{}%", self.config.plr.unwrap_or(0_f64));
160+
161+
// Add delay to the host veth device to match MacOS symmetric behaviour
162+
//
163+
// The behaviour is specified on the top-level ("root"),
164+
// with a custom handle for identification
165+
let mut args = vec!["root", "handle", "1:", "netem"];
277166
if self.config.latency.is_some() {
278167
args.push("delay");
279168
args.push(&delay);
280169
}
170+
ip_tc::add_network_emulation_parameters(&veth_host, args, None)?;
281171

282-
let loss = format!("{}%", self.config.plr.unwrap_or(0_f64));
283-
172+
// Add network emulation parameters (delay, loss) on namespaced veth device
173+
let mut args = vec!["root", "handle", "1:", "netem"];
174+
if self.config.latency.is_some() {
175+
args.push("delay");
176+
args.push(&delay);
177+
}
284178
if (self.config.plr).is_some() {
285179
args.push("loss");
286180
args.push(&loss);
287181
}
288-
289-
let status = Command::new("sudo").args(args).status()?;
290-
291-
assert_status(
292-
status,
293-
"Failed to set delay and loss network emulation parameters to namespaced device",
294-
)?;
295-
296-
// Add delay to the host veth device to match MacOS symmetric behaviour
297-
let status = Command::new("sudo")
298-
.args([
299-
"tc", "qdisc", "add", "dev", &veth_host, "root", "handle", "1:", "netem", "delay",
300-
&delay,
301-
])
302-
.status()?;
303-
304-
assert_status(status, "Failed to set delay to the host veth device")?;
182+
ip_tc::add_network_emulation_parameters(&veth_namespace, args, Some(&network_namespace))?;
305183

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

315-
let status = Command::new("sudo")
316-
.args([
317-
"ip",
318-
"netns",
319-
"exec",
320-
&network_namespace,
321-
"tc",
322-
"qdisc",
323-
"add",
324-
"dev",
325-
&veth_namespace,
326-
"parent",
327-
"1:",
328-
"handle",
329-
"2:",
330-
"tbf",
331-
"rate",
332-
&bandwidth,
333-
"burst",
334-
&burst,
335-
"limit",
336-
&limit,
337-
])
338-
.status()?;
339-
340-
assert_status(
341-
status,
342-
"Failed to set bandwidth parameter to the namespaced device",
343-
)?;
193+
let args = vec![
194+
"parent", "1:", "handle", "2:", "tbf", "rate", &bandwidth, "burst", &burst,
195+
"limit", &limit,
196+
];
344197

345-
// Add bandwidth paramteres on host veth device
346-
let status = Command::new("sudo")
347-
.args([
348-
"tc", "qdisc", "add", "dev", &veth_host, "parent", "1:", "handle", "2:", "tbf",
349-
"rate", &bandwidth, "burst", &burst, "limit", &limit,
350-
])
351-
.status()?;
352-
353-
assert_status(
354-
status,
355-
"Failed to set bandwidth parameter to the host veth device",
198+
ip_tc::add_network_emulation_parameters(&veth_host, args.clone(), None)?;
199+
ip_tc::add_network_emulation_parameters(
200+
&veth_namespace,
201+
args,
202+
Some(&network_namespace),
356203
)?;
357204
}
358205
Ok(())

0 commit comments

Comments
 (0)