diff --git a/examples/docker-network/README.md b/examples/docker-network/README.md index b2b6a45..03246e7 100644 --- a/examples/docker-network/README.md +++ b/examples/docker-network/README.md @@ -86,12 +86,63 @@ The `iperf-test.sh` script measures bandwidth between nodes using iperf3: ``` The test runs iperf3 with the following parameters: + - Duration: 10 seconds (`-t 10`) - Parallel streams: 8 (`-P 8`) - Protocol: TCP over IPv6 This exercises the full FIPS stack including encryption, routing, and TUN device performance. +## Network Impairment + +The `netem.sh` script simulates adverse network conditions using `tc`/`netem` +on all running containers: + +```bash +./scripts/netem.sh [mesh|chain] [options] +``` + +### Options + +| Option | Description | +|--------|-------------| +| `--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 (requires `--delay`) | +| `--corrupt ` | Bit-level corruption percentage | + +### Presets + +| Preset | Parameters | +|--------|------------| +| `lossy` | 5% loss, 25% correlation | +| `congested` | 50ms delay, 20ms jitter, 2% loss | +| `terrible` | 100ms delay, 40ms jitter, 10% loss, 1% dup, 5% reorder | + +### Examples + +```bash +# Apply 50ms delay with 5% packet loss +./scripts/netem.sh mesh apply --delay 50 --loss 5 + +# Use a preset +./scripts/netem.sh chain apply --preset congested + +# Check current rules +./scripts/netem.sh mesh status + +# Remove all impairment +./scripts/netem.sh mesh remove +``` + +Rules are applied to egress on each container's `eth0` interface. With all +containers impaired equally, both directions of every link see the effect. +The script uses `tc qdisc replace` so it can be re-run safely without +removing rules first. + ## Configuration Management Node configurations are generated from templates to ensure consistency across all nodes: @@ -119,6 +170,7 @@ generated-configs/ # Auto-generated configs (gitignored) ### Topology Files The `configs/topologies/` directory contains YAML files documenting each network topology: + - Node identities (nsec, npub) - Docker IP addresses - Peer connections for each node diff --git a/examples/docker-network/scripts/iperf-test.sh b/examples/docker-network/scripts/iperf-test.sh index 13efbb4..0df6955 100755 --- a/examples/docker-network/scripts/iperf-test.sh +++ b/examples/docker-network/scripts/iperf-test.sh @@ -95,31 +95,31 @@ if [ "$PROFILE" = "mesh" ]; then echo "" echo "Testing mesh topology paths:" - # Direct peer links - iperf_test node-a node-d "$NPUB_D" "A → D (direct peer)" - iperf_test node-a node-e "$NPUB_E" "A → E (direct peer)" - - # Multi-hop paths - iperf_test node-a node-b "$NPUB_B" "A → B (multi-hop)" - iperf_test node-a node-c "$NPUB_C" "A → C (multi-hop)" - - # Reverse test - iperf_test node-e node-a "$NPUB_A" "E → A (direct peer)" + # Direct peer links (client on A, server on D/E) + iperf_test node-d node-a "$NPUB_D" "A → D (direct peer)" + iperf_test node-e node-a "$NPUB_E" "A → E (direct peer)" + + # Multi-hop paths (client on A, server on B/C) + iperf_test node-b node-a "$NPUB_B" "A → B (multi-hop)" + iperf_test node-c node-a "$NPUB_C" "A → C (multi-hop)" + + # Reverse test (client on E, server on A) + iperf_test node-a node-e "$NPUB_A" "E → A (direct peer)" elif [ "$PROFILE" = "chain" ]; then echo "" echo "Testing chain topology paths:" - # Adjacent hop - iperf_test node-a node-b "$NPUB_B" "A → B (1 hop)" - - # Multi-hop tests - iperf_test node-a node-c "$NPUB_C" "A → C (2 hops)" - iperf_test node-a node-d "$NPUB_D" "A → D (3 hops)" - iperf_test node-a node-e "$NPUB_E" "A → E (4 hops)" - - # Reverse multi-hop - iperf_test node-e node-a "$NPUB_A" "E → A (4 hops)" + # Adjacent hop (client on A, server on B) + iperf_test node-b node-a "$NPUB_B" "A → B (1 hop)" + + # Multi-hop tests (client on A, server on C/D/E) + iperf_test node-c node-a "$NPUB_C" "A → C (2 hops)" + iperf_test node-d node-a "$NPUB_D" "A → D (3 hops)" + iperf_test node-e node-a "$NPUB_E" "A → E (4 hops)" + + # Reverse multi-hop (client on E, server on A) + iperf_test node-a node-e "$NPUB_A" "E → A (4 hops)" fi echo "" diff --git a/examples/docker-network/scripts/netem.sh b/examples/docker-network/scripts/netem.sh new file mode 100755 index 0000000..c142d1d --- /dev/null +++ b/examples/docker-network/scripts/netem.sh @@ -0,0 +1,245 @@ +#!/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" + 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" + 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" + 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."