#!/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 [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 %` on egress of . # 3. Re-prints the qdisc state (confirms the rule is installed). # 4. Sends 100 ICMP echo requests to 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 [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).