Results and script to verify netem

This commit is contained in:
Valère Plantevin
2026-05-13 15:32:23 -04:00
parent 89630238a9
commit f226e53118
4 changed files with 160 additions and 25 deletions

135
scripts/verify-netem.sh Executable file
View File

@@ -0,0 +1,135 @@
#!/usr/bin/env bash
# scripts/verify-netem.sh — confirm tc-netem is actually applying loss, in
# the direction you think it is.
#
# Usage:
# ./scripts/verify-netem.sh <peer-ip> [interface] [loss_pct]
#
# peer-ip IP of the other machine (the simulator's IP when run on the CM5,
# or the CM5's IP when run on the Mac).
# interface Interface tc-netem is applied to. Default: eth0.
# loss_pct Loss percentage to apply. Default: 5.
#
# What it does:
# 1. Prints the current qdisc state (sanity check before).
# 2. Applies `netem loss <loss_pct>%` on egress of <interface>.
# 3. Re-prints the qdisc state (confirms the rule is installed).
# 4. Sends 100 ICMP echo requests to <peer-ip> and reports the observed loss.
# 5. Removes the qdisc on exit (trap), even on Ctrl-C.
#
# IMPORTANT direction caveat:
# tc qdisc rules apply to EGRESS only. If you run this on the CM5, it shapes
# CM5 → peer traffic. The peer → CM5 direction is unaffected. To drop traffic
# coming INTO this machine, you must either:
# (a) run netem on the SENDER side, or
# (b) attach an ifb (intermediate functional block) device for ingress.
# See the "Bidirectional loss" section in the script footer for the ifb recipe.
set -euo pipefail
PEER="${1:-}"
IFACE="${2:-eth0}"
LOSS="${3:-5}"
if [[ -z "$PEER" ]]; then
echo "Usage: $0 <peer-ip> [interface] [loss_pct]"
echo "Example: $0 192.168.1.42 eth0 5"
exit 1
fi
if [[ -t 1 ]]; then
BOLD=$'\033[1m'; DIM=$'\033[2m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'
RED=$'\033[31m'; RESET=$'\033[0m'
else
BOLD=; DIM=; GREEN=; YELLOW=; RED=; RESET=
fi
step() { printf '\n%s» %s%s\n' "$BOLD" "$1" "$RESET"; }
ok() { printf '%s ✓ %s%s\n' "$GREEN" "$1" "$RESET"; }
warn() { printf '%s ! %s%s\n' "$YELLOW" "$1" "$RESET"; }
fail() { printf '%s ✗ %s%s\n' "$RED" "$1" "$RESET"; }
# Sanity: tc + ping + interface
for cmd in tc ping ip; do
command -v "$cmd" >/dev/null || { fail "missing: $cmd"; exit 1; }
done
ip link show "$IFACE" >/dev/null 2>&1 || { fail "interface $IFACE not found"; exit 1; }
# Print the route to the peer so the user can see which iface the kernel
# actually uses — if it's not $IFACE, the netem rule won't apply.
step "Route to peer $PEER"
ROUTE_OUT="$(ip route get "$PEER" 2>&1 || true)"
printf ' %s\n' "$ROUTE_OUT"
ROUTE_IFACE="$(echo "$ROUTE_OUT" | awk '/dev/ {for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')"
if [[ -n "$ROUTE_IFACE" && "$ROUTE_IFACE" != "$IFACE" ]]; then
warn "kernel routes $PEER via '$ROUTE_IFACE' but you're shaping '$IFACE'."
warn "the netem rule will have NO effect on this peer's traffic."
fi
# State BEFORE
step "Current qdisc on $IFACE (before)"
sudo tc qdisc show dev "$IFACE" | sed 's/^/ /'
# Apply netem
step "Applying netem loss ${LOSS}% on $IFACE (egress)"
sudo tc qdisc del dev "$IFACE" root 2>/dev/null || true
sudo tc qdisc add dev "$IFACE" root netem loss "${LOSS}%"
ok "qdisc installed"
# Trap to clean up on any exit path
cleanup() {
step "Removing netem qdisc from $IFACE"
sudo tc qdisc del dev "$IFACE" root 2>/dev/null || true
ok "qdisc removed; $IFACE back to default"
}
trap cleanup EXIT INT TERM
# State AFTER install
step "Current qdisc on $IFACE (after netem applied)"
sudo tc qdisc show dev "$IFACE" | sed 's/^/ /'
# Ping the peer and parse loss
step "Pinging $PEER with 100 echoes (egress goes through netem)"
PING_OUT="$(ping -c 100 -i 0.05 -W 1 "$PEER" 2>&1 || true)"
echo "$PING_OUT" | tail -3 | sed 's/^/ /'
# Parse "X% packet loss" — works on both Linux and macOS ping output.
OBSERVED="$(echo "$PING_OUT" | grep -oE '[0-9.]+% packet loss' | head -1 | awk '{print $1}' | tr -d '%')"
if [[ -z "$OBSERVED" ]]; then
fail "could not parse ping output"
exit 1
fi
# Sanity bracket: configured loss is ±3 percentage points absolute is fine for n=100.
ABS_DELTA=$(awk -v o="$OBSERVED" -v l="$LOSS" 'BEGIN{d=o-l; if(d<0)d=-d; printf "%.1f", d}')
step "Result"
printf ' configured: %s%%\n observed: %s%%\n |delta|: %s pp\n' "$LOSS" "$OBSERVED" "$ABS_DELTA"
if awk -v o="$OBSERVED" -v l="$LOSS" 'BEGIN{exit !(o > l*0.4 && o < l*2.0 + 3)}'; then
ok "loss is being applied in the egress direction of $IFACE"
else
fail "observed loss ($OBSERVED%) does not match configured ($LOSS%)"
warn "either the qdisc isn't routing as expected, or the kernel's netem"
warn "build doesn't include the loss module. Check 'modprobe sch_netem'."
fi
# ---------------------------------------------------------------------------
# Bidirectional loss (info — not run by this script)
# ---------------------------------------------------------------------------
# tc qdisc shapes egress. To drop INCOMING traffic on the CM5 (e.g. T1
# datagrams flowing from the Mac), redirect ingress to an ifb device and
# shape that:
#
# sudo modprobe ifb numifbs=1
# sudo ip link set ifb0 up
# sudo tc qdisc add dev $IFACE handle ffff: ingress
# sudo tc filter add dev $IFACE parent ffff: protocol all u32 \
# match u32 0 0 action mirred egress redirect dev ifb0
# sudo tc qdisc add dev ifb0 root netem loss 5%
#
# Clean up:
# sudo tc qdisc del dev $IFACE ingress 2>/dev/null
# sudo tc qdisc del dev ifb0 root 2>/dev/null
# sudo ip link set ifb0 down
#
# Or — simpler — apply netem on BOTH ends with their respective egress qdiscs
# (run this script with the same loss_pct on the Mac too).