Skip to main content

Crate badnet

Crate badnet 

Source
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.
BadNetBuilder
Builder for a BadNet link.
BadNetConfig
Runtime-configurable impairment parameters for a BadNet link.
GilbertElliot
Parameters for the Gilbert-Elliot two-state Markov burst-loss model.