#!/bin/bash # Network impairment simulation using tc/netem on FIPS Docker containers. # # Usage: ./netem.sh [options] # # Actions: # apply - Apply netem rules to all containers in the profile # remove - Remove netem rules from all containers # status - Show current tc qdisc state on each container # # Options (for apply): # --delay Fixed delay in milliseconds # --jitter Delay variation (requires --delay) # --loss Packet loss percentage # --loss-corr Loss correlation for bursty loss # --duplicate Packet duplication percentage # --reorder Packet reordering probability # --corrupt Bit-level corruption percentage # # Presets (shorthand for common combinations): # --preset lossy 5% loss, 25% correlation # --preset congested 50ms delay, 20ms jitter, 2% loss # --preset terrible 100ms delay, 40ms jitter, 10% loss, 1% dup, 5% reorder # # Examples: # ./netem.sh mesh apply --delay 50 --loss 5 # ./netem.sh chain apply --preset congested # ./netem.sh mesh status # ./netem.sh mesh remove set -e trap 'echo ""; echo "Interrupted"; exit 130' INT NODES="a b c d e" IFACE="eth0" # Defaults DELAY=0 JITTER=0 LOSS=0 LOSS_CORR=0 DUPLICATE=0 REORDER=0 CORRUPT=0 usage() { echo "Usage: $0 [options]" echo "" echo "Actions:" echo " apply - Apply netem rules to all containers" echo " remove - Remove netem rules from all containers" echo " status - Show current tc qdisc on each container" echo "" echo "Options (for apply):" echo " --delay Fixed delay" echo " --jitter Delay variation (requires --delay)" echo " --loss Packet loss" echo " --loss-corr Loss correlation" echo " --duplicate Packet duplication" echo " --reorder Packet reordering" echo " --corrupt Bit-level corruption" echo " --preset Use a named preset (lossy, congested, terrible)" exit 1 } apply_preset() { case "$1" in lossy) LOSS=5 LOSS_CORR=25 ;; congested) DELAY=50 JITTER=20 LOSS=2 ;; terrible) DELAY=100 JITTER=40 LOSS=10 DUPLICATE=1 REORDER=5 ;; *) echo "Error: Unknown preset '$1'" >&2 echo "Available presets: lossy, congested, terrible" >&2 exit 1 ;; esac } # Parse arguments [ $# -lt 2 ] && usage PROFILE="$1" ACTION="$2" shift 2 case "$PROFILE" in mesh|chain) ;; *) echo "Error: Profile must be 'mesh' or 'chain'" >&2; exit 1 ;; esac case "$ACTION" in apply|remove|status) ;; *) echo "Error: Action must be 'apply', 'remove', or 'status'" >&2; exit 1 ;; esac # Parse options while [ $# -gt 0 ]; do case "$1" in --delay) DELAY="$2"; shift 2 ;; --jitter) JITTER="$2"; shift 2 ;; --loss) LOSS="$2"; shift 2 ;; --loss-corr) LOSS_CORR="$2"; shift 2 ;; --duplicate) DUPLICATE="$2"; shift 2 ;; --reorder) REORDER="$2"; shift 2 ;; --corrupt) CORRUPT="$2"; shift 2 ;; --preset) apply_preset "$2"; shift 2 ;; *) echo "Error: Unknown option '$1'" >&2 usage ;; esac done # Build netem parameter string from non-zero values build_netem_params() { local params="" if [ "$DELAY" != "0" ]; then params="delay ${DELAY}ms" if [ "$JITTER" != "0" ]; then params="$params ${JITTER}ms" fi fi if [ "$LOSS" != "0" ]; then params="$params loss ${LOSS}%" if [ "$LOSS_CORR" != "0" ]; then params="$params ${LOSS_CORR}%" fi fi if [ "$DUPLICATE" != "0" ]; then params="$params duplicate ${DUPLICATE}%" fi if [ "$REORDER" != "0" ]; then if [ "$DELAY" = "0" ]; then echo "Error: --reorder requires --delay (reordering needs a delay queue)" >&2 exit 1 fi params="$params reorder ${REORDER}%" fi if [ "$CORRUPT" != "0" ]; then params="$params corrupt ${CORRUPT}%" fi echo "$params" } # Check if a container is running container_running() { docker inspect -f '{{.State.Running}}' "$1" 2>/dev/null | grep -q true } do_apply() { local params params=$(build_netem_params) if [ -z "$params" ]; then echo "Error: No impairment parameters specified" >&2 echo "Use --delay, --loss, --duplicate, --reorder, --corrupt, or --preset" >&2 exit 1 fi echo "=== Applying netem: $params ===" echo "" for node in $NODES; do local container="fips-node-${node}${FIPS_CI_NAME_SUFFIX:-}" echo -n " $container ... " if ! container_running "$container"; then echo "SKIP (not running)" continue fi if docker exec "$container" tc qdisc replace dev "$IFACE" root netem $params 2>&1; then echo "OK" else echo "FAIL" fi done } do_remove() { echo "=== Removing netem rules ===" echo "" for node in $NODES; do local container="fips-node-${node}${FIPS_CI_NAME_SUFFIX:-}" echo -n " $container ... " if ! container_running "$container"; then echo "SKIP (not running)" continue fi # Suppress error if no qdisc exists if docker exec "$container" tc qdisc del dev "$IFACE" root 2>/dev/null; then echo "OK" else echo "OK (no rules)" fi done } do_status() { echo "=== netem status ===" echo "" for node in $NODES; do local container="fips-node-${node}${FIPS_CI_NAME_SUFFIX:-}" echo " $container:" if ! container_running "$container"; then echo " (not running)" continue fi local output output=$(docker exec "$container" tc qdisc show dev "$IFACE" 2>&1) if echo "$output" | grep -q "netem"; then echo " $output" else echo " (no netem rules)" fi done } case "$ACTION" in apply) do_apply ;; remove) do_remove ;; status) do_status ;; esac echo "" echo "Done."