Expand description
Simulate bad network conditions on loopback using tc-netem.
badnet provisions a pair of loopback addresses and wires them together
through a Linux traffic-control (tc) pipeline so that any traffic sent
from one address to the other passes through a configurable
tc-netem qdisc. This lets you reproduce packet loss, corruption,
duplication, delay, and reordering in ordinary integration tests without
touching real network interfaces.
§Platform requirements
Linux only. The calling process needs CAP_NET_ADMIN. Grant it with
setcap rather than running the whole process as root — running a test
binary as root gives it unrestricted access to the entire system, which is
far broader than what this library needs:
sudo setcap cap_net_admin+eip target/debug/deps/<test_binary>§Quick start
use std::net::UdpSocket;
use std::time::Duration;
use badnet::BadNet;
// Create a link that drops 10 % of packets.
let net = BadNet::builder()
.loss(0.10)
.build()?;
// Bind sockets to the two ends and communicate normally.
let tx = UdpSocket::bind(format!("{}:0", net.left_addr()))?;
let rx = UdpSocket::bind(format!("{}:9000", net.right_addr()))?;
rx.set_read_timeout(Some(Duration::from_millis(200)))?;
tx.send_to(b"hello", format!("{}:9000", net.right_addr()))?;
// … the link will drop ~10 % of these …
// The tc rules and loopback addresses are removed when `net` is dropped.§Isolation
Each BadNet instance claims a unique address pair and HTB class, so
multiple instances running concurrently do not interfere with each other.
§Reorder and gap
BadNetBuilder::reorder requires BadNetBuilder::delay to have been
called first (enforced at compile time via the typestate on
BadNetBuilder). BadNetBuilder::gap additionally requires
BadNetBuilder::reorder.
use std::time::Duration;
use badnet::BadNet;
// 20 ms delay, 30 % of packets reordered probabilistically.
let net = BadNet::builder()
.delay(Duration::from_millis(20))
.reorder(0.30)
.build()?;
// Every 5th packet reordered deterministically (reorder must be 1.0 for
// a fully deterministic pattern).
let net = BadNet::builder()
.delay(Duration::from_millis(20))
.reorder(1.0)
.gap(5)
.build()?;§Reproducible impairment with seeds (requires the seed cargo feature)
tc-netem uses an internal PRNG to decide which packets to drop, corrupt,
duplicate, or reorder. Passing the same seed on the same kernel version
produces an identical sequence of decisions, making test failures
reproducible. Enable the seed Cargo feature to expose this API:
use badnet::BadNet;
// Seed 42 produces the same loss decisions on every run (requires feature = "seed").
let net = BadNet::builder().seed(42).loss(0.10).build()?;Without the seed feature the seed argument is never forwarded to
iproute2, keeping the library compatible with older iproute2 versions.
§Troubleshooting
§Stale state after a crashed run
BadNet cleans up its TC rules and loopback addresses in its Drop
implementation. However, if the process is killed before Drop runs
(e.g. SIGKILL, or Ctrl-C without a signal handler), the kernel objects
are left behind and the next run will fail with errors like:
tc qdisc add dev lo root handle 1: htb default 1` failed: Error: Exclusivity flag on, cannot modify.
ip addr add 10.0.0.1/32 dev lo` failed: Error: ipv4: Address already assigned.To clean up manually:
sudo tc qdisc del dev lo root 2>/dev/null; \
sudo ip -4 addr show dev lo | awk '/inet 10\./{print $2}' | \
xargs -r -I{} sudo ip addr del {} dev lo§Ctrl-C during long-running binaries
Rust does not run destructors on SIGINT by default. If your binary runs
BadNet instances for an extended period and users may interrupt it, install
a signal handler that performs an orderly shutdown so that Drop is called:
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
unsafe {
libc::signal(libc::SIGINT, handle_sigint as libc::sighandler_t);
}
extern "C" fn handle_sigint(_: libc::c_int) { /* set flag, then std::process::exit(0) */ }A crate such as ctrlc makes this straightforward:
// ctrlc = "3"
ctrlc::set_handler(|| std::process::exit(0)).unwrap();Calling std::process::exit runs destructors, which allows BadNet’s
Drop impl to clean up before the process exits.
Structs§
- BadNet
- A running virtual link backed by loopback addresses and
tc-netem. - BadNet
Builder - Builder for a
BadNetlink. - BadNet
Config - Runtime-configurable impairment parameters for a
BadNetlink. - Gilbert
Elliot - Parameters for the Gilbert-Elliot two-state Markov burst-loss model.