169 lines
6.6 KiB
Bash
Executable File
169 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# scripts/verify-netem.sh — confirm tc-netem is actually applying loss, in
|
|
# the direction(s) 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.
|
|
#
|
|
# Modes (env var):
|
|
# BIDI=0 (default) Egress-only. Shapes outgoing traffic from <interface>.
|
|
# Use a single ping from this host to verify.
|
|
# BIDI=1 Bidirectional via ifb ingress redirect. Shapes BOTH
|
|
# outgoing AND incoming traffic on <interface>. Pings
|
|
# run from THIS host verify egress; the script also
|
|
# prompts you to ping back FROM THE PEER to verify
|
|
# ingress (the script holds the qdisc up until you press
|
|
# Enter, then tears everything down).
|
|
#
|
|
# What it does:
|
|
# 1. Prints the current qdisc state (sanity check before).
|
|
# 2. Applies the configured netem loss (egress, or egress + ingress).
|
|
# 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. BIDI=1 only: waits for you to run `ping -c 100 <this-host>` from the
|
|
# peer machine and report what it saw.
|
|
# 6. Removes the qdiscs (and brings ifb0 down) on exit, even on Ctrl-C.
|
|
|
|
set -euo pipefail
|
|
|
|
PEER="${1:-}"
|
|
IFACE="${2:-eth0}"
|
|
LOSS="${3:-5}"
|
|
BIDI="${BIDI:-0}"
|
|
IFB_DEV="${IFB_DEV:-ifb0}"
|
|
|
|
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/^/ /'
|
|
|
|
# If bidi, ensure ifb device is up before installing qdiscs.
|
|
if [[ "$BIDI" -eq 1 ]]; then
|
|
step "Bringing up $IFB_DEV (BIDI mode)"
|
|
sudo modprobe ifb numifbs=1 2>/dev/null || true
|
|
if ! ip link show "$IFB_DEV" >/dev/null 2>&1; then
|
|
fail "ifb device $IFB_DEV not present after modprobe; cannot run BIDI mode"
|
|
exit 1
|
|
fi
|
|
sudo ip link set "$IFB_DEV" up
|
|
ok "$IFB_DEV is up"
|
|
fi
|
|
|
|
# Apply netem
|
|
if [[ "$BIDI" -eq 1 ]]; then
|
|
step "Applying netem loss ${LOSS}% on $IFACE (egress + ingress via $IFB_DEV)"
|
|
else
|
|
step "Applying netem loss ${LOSS}% on $IFACE (egress only)"
|
|
fi
|
|
sudo tc qdisc del dev "$IFACE" root 2>/dev/null || true
|
|
sudo tc qdisc del dev "$IFACE" ingress 2>/dev/null || true
|
|
[[ "$BIDI" -eq 1 ]] && sudo tc qdisc del dev "$IFB_DEV" root 2>/dev/null || true
|
|
sudo tc qdisc add dev "$IFACE" root netem loss "${LOSS}%"
|
|
if [[ "$BIDI" -eq 1 ]]; then
|
|
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 "$IFB_DEV"
|
|
sudo tc qdisc add dev "$IFB_DEV" root netem loss "${LOSS}%"
|
|
fi
|
|
ok "qdisc(s) installed"
|
|
|
|
# Trap to clean up on any exit path
|
|
cleanup() {
|
|
step "Removing netem qdiscs"
|
|
sudo tc qdisc del dev "$IFACE" root 2>/dev/null || true
|
|
sudo tc qdisc del dev "$IFACE" ingress 2>/dev/null || true
|
|
if [[ "$BIDI" -eq 1 ]]; then
|
|
sudo tc qdisc del dev "$IFB_DEV" root 2>/dev/null || true
|
|
sudo ip link set "$IFB_DEV" down 2>/dev/null || true
|
|
fi
|
|
ok "qdiscs removed; $IFACE back to default"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# State AFTER install
|
|
step "Current qdisc state (after netem applied)"
|
|
sudo tc qdisc show dev "$IFACE" | sed 's/^/ /'
|
|
if [[ "$BIDI" -eq 1 ]]; then
|
|
sudo tc qdisc show dev "$IFB_DEV" | sed 's/^/ /'
|
|
echo " filter on $IFACE ingress:"
|
|
sudo tc filter show dev "$IFACE" parent ffff: 2>/dev/null | sed 's/^/ /'
|
|
fi
|
|
|
|
# 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
|
|
|
|
if [[ "$BIDI" -eq 1 ]]; then
|
|
step "Now verify the INGRESS direction"
|
|
THIS_IP="$(ip -4 addr show dev "$IFACE" | awk '/inet / {print $2}' | cut -d/ -f1 | head -1)"
|
|
cat <<EOF
|
|
From the peer machine, run:
|
|
ping -c 100 -i 0.05 ${THIS_IP:-<this host\'s IP on $IFACE>}
|
|
You should see ~${LOSS}% packet loss in the peer's ping output. That confirms
|
|
ingress shaping is dropping packets arriving here on $IFACE.
|
|
|
|
Press Enter when done to tear everything down.
|
|
EOF
|
|
read -r _ </dev/tty || true
|
|
fi
|
|
|