feat(docker): Add iperf3 testing, config templating, and test improvements

Docker testing infrastructure:
- Add iperf3 bandwidth testing (10s duration, 8 parallel streams)
- Install iperf3 in container, auto-start as daemon alongside FIPS
- Support --live flag for real-time iperf3 output during tests
- Aggregate [SUM] bandwidth reporting with correct units

Configuration templating system:
- Single node.template.yaml replacing per-node hand-written configs
- Topology definitions in configs/topologies/ (mesh.yaml, chain.yaml)
- generate-configs.sh script for automatic config generation
- Integrated into build.sh for regeneration on build
- Generated configs in generated-configs/ (gitignored)

Ping test improvements:
- Reduce to 1 ping per test for faster execution (~10s vs ~30s)
- Show response times (RTT) for each test
- Add Ctrl+C trap for clean exit
- Reduce convergence wait from 5s to 3s
This commit is contained in:
Origami74
2026-02-18 01:01:27 +00:00
committed by Johnathan Corgan
parent d46dc874ef
commit 68b9652aa9
21 changed files with 492 additions and 391 deletions
+3
View File
@@ -53,4 +53,7 @@ fi
echo "Done. Binary at $DOCKER_DIR/fips"
echo ""
echo "Generating node configurations from templates..."
"$SCRIPT_DIR/generate-configs.sh" all
echo ""
echo "Next: cd $DOCKER_DIR && docker compose --profile mesh build"
+155
View File
@@ -0,0 +1,155 @@
#!/bin/bash
# Generate FIPS node configuration files from template and topology definition.
#
# Usage: ./generate-configs.sh [mesh|chain|all]
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_DIR="$SCRIPT_DIR/../configs"
GENERATED_DIR="$SCRIPT_DIR/../generated-configs"
TEMPLATE_FILE="$CONFIG_DIR/node.template.yaml"
# Node data lookup functions
get_nsec() {
case "$1" in
a) echo "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" ;;
b) echo "b102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fb0" ;;
c) echo "c102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fc0" ;;
d) echo "d102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fd0" ;;
e) echo "e102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fe0" ;;
esac
}
get_npub() {
case "$1" in
a) echo "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m" ;;
b) echo "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le" ;;
c) echo "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6" ;;
d) echo "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl" ;;
e) echo "npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4" ;;
esac
}
get_docker_ip() {
case "$1" in
a) echo "172.20.0.10" ;;
b) echo "172.20.0.11" ;;
c) echo "172.20.0.12" ;;
d) echo "172.20.0.13" ;;
e) echo "172.20.0.14" ;;
esac
}
get_mesh_peers() {
case "$1" in
a) echo "d e" ;;
b) echo "c" ;;
c) echo "b d e" ;;
d) echo "a c e" ;;
e) echo "a c d" ;;
esac
}
get_chain_peers() {
case "$1" in
a) echo "b" ;;
b) echo "a c" ;;
c) echo "b d" ;;
d) echo "c e" ;;
e) echo "d" ;;
esac
}
generate_peer_block() {
local peer_id="$1"
cat <<EOF
- npub: "$(get_npub "$peer_id")"
alias: "node-$peer_id"
addresses:
- transport: udp
addr: "$(get_docker_ip "$peer_id"):4000"
connect_policy: auto_connect
EOF
}
generate_config() {
local node_id="$1"
local topology="$2"
local peers="$3"
local node_name=$(echo "$node_id" | tr '[:lower:]' '[:upper:]')
local template
template=$(cat "$TEMPLATE_FILE")
# Generate peers section
local peers_config=""
if [ -n "$peers" ]; then
for peer_id in $peers; do
if [ -n "$peers_config" ]; then
peers_config="$peers_config"$'\n'
fi
peers_config="$peers_config$(generate_peer_block "$peer_id")"
done
else
peers_config=" []"
fi
# Replace template variables
local config="$template"
config="${config//\{\{NODE_NAME\}\}/$node_name}"
config="${config//\{\{TOPOLOGY\}\}/$topology}"
config="${config//\{\{NPUB\}\}/$(get_npub "$node_id")}"
config="${config//\{\{NSEC\}\}/$(get_nsec "$node_id")}"
config="${config//\{\{PEERS\}\}/$peers_config}"
echo "$config"
}
generate_topology() {
local topology="$1"
local output_dir="$GENERATED_DIR/$topology"
echo "Generating $topology topology configs..."
mkdir -p "$output_dir"
for node_id in a b c d e; do
local peers
if [ "$topology" = "mesh" ]; then
peers=$(get_mesh_peers "$node_id")
else
peers=$(get_chain_peers "$node_id")
fi
local output_file="$output_dir/node-$node_id.yaml"
generate_config "$node_id" "$topology" "$peers" > "$output_file"
echo " ✓ Generated $output_file"
done
}
main() {
local requested="${1:-all}"
case "$requested" in
mesh)
generate_topology "mesh"
;;
chain)
generate_topology "chain"
;;
all)
generate_topology "mesh"
generate_topology "chain"
;;
*)
echo "Error: Unknown topology '$requested'"
echo "Usage: $0 [mesh|chain|all]"
exit 1
;;
esac
echo ""
echo "✓ All configurations generated successfully!"
}
main "$@"
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
# End-to-end iperf3 bandwidth test between FIPS nodes via DNS resolution.
# Usage: ./iperf-test.sh [mesh|chain] [--live]
#
# Requires containers to be running:
# docker compose --profile mesh up -d
# ./scripts/iperf-test.sh mesh
# ./scripts/iperf-test.sh mesh --live # Show live iperf3 output
set -e
# Exit entire script on Ctrl+C
trap 'echo ""; echo "Test interrupted"; exit 130' INT
PROFILE="${1:-mesh}"
LIVE_OUTPUT=false
if [ "$2" = "--live" ] || [ "$1" = "--live" ]; then
LIVE_OUTPUT=true
[ "$1" = "--live" ] && PROFILE="mesh"
fi
DURATION=10
PARALLEL=8
PASSED=0
FAILED=0
# Node identities
NPUB_A="npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
NPUB_B="npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
NPUB_C="npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
NPUB_D="npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
NPUB_E="npub1wf8akf8lu2zdkjkmwhl75pqvven654mpv4sz2x2tprl5265mgrzq8nhak4"
# FIPS IPv6 addresses (derived from npubs)
ADDR_A="fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b"
ADDR_B="fd8e:302c:287e:b48d:e5b7:6eee:e8a7:4f1e"
ADDR_C="fdac:a221:4069:5044:e0e5:7a5f:5a0f:4d43"
ADDR_D="fdb6:8411:a191:6d48:c5f7:e5f5:e5a5:2a50"
ADDR_E="fded:7dee:d386:a546:e5f7:e5f5:e5a5:2a50"
iperf_test() {
local server_node="$1"
local client_node="$2"
local dest_npub="$3"
local label="$4"
echo ""
echo "=== $label ==="
# iperf3 server is already running in daemon mode in each container
if [ "$LIVE_OUTPUT" = true ]; then
# Show live output
echo "Running iperf3 test (live output):"
if docker exec "fips-$client_node" iperf3 -c "${dest_npub}.fips" -t "$DURATION" -P "$PARALLEL"; then
PASSED=$((PASSED + 1))
else
echo "FAIL"
FAILED=$((FAILED + 1))
fi
else
# Capture and summarize output
echo -n "Running iperf3 test... "
local output
if output=$(docker exec "fips-$client_node" iperf3 -c "${dest_npub}.fips" -t "$DURATION" -P "$PARALLEL" 2>&1); then
# Check if we got valid results
if echo "$output" | grep -q "sender"; then
# Extract and display results (get SUM line for aggregate bandwidth)
local bandwidth=$(echo "$output" | grep "\[SUM\].*sender" | tail -1 | awk '{print $(NF-2), $(NF-1)}')
echo "OK"
echo "Bandwidth: $bandwidth"
PASSED=$((PASSED + 1))
else
echo "FAIL (no bandwidth data)"
echo "Output: $output"
FAILED=$((FAILED + 1))
fi
else
echo "FAIL"
echo "Error output:"
echo "$output" | head -10
FAILED=$((FAILED + 1))
fi
fi
}
echo "=== FIPS iperf3 Bandwidth Test ($PROFILE topology) ==="
echo ""
# Wait for nodes to converge
echo "Waiting 3s for mesh convergence..."
sleep 3
if [ "$PROFILE" = "mesh" ]; then
# Test key paths in mesh topology
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)"
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)"
fi
echo ""
echo "=== Results: $PASSED passed, $FAILED failed ==="
[ "$FAILED" -eq 0 ] && exit 0 || exit 1
+16 -6
View File
@@ -7,9 +7,12 @@
# ./scripts/ping-test.sh mesh
set -e
# Exit entire script on Ctrl+C
trap 'echo ""; echo "Test interrupted"; exit 130' INT
PROFILE="${1:-mesh}"
COUNT=3
TIMEOUT=10
COUNT=1
TIMEOUT=5
PASSED=0
FAILED=0
@@ -26,8 +29,15 @@ ping_test() {
local label="$3"
echo -n " $label ... "
if docker exec "fips-$from" ping6 -c "$COUNT" -W "$TIMEOUT" "${to_npub}.fips" > /dev/null 2>&1; then
echo "OK"
local output
if output=$(docker exec "fips-$from" ping6 -c "$COUNT" -W "$TIMEOUT" "${to_npub}.fips" 2>&1); then
# Extract round-trip time from ping output
local rtt=$(echo "$output" | grep -oE 'time=[0-9.]+' | cut -d= -f2)
if [ -n "$rtt" ]; then
echo "OK (${rtt}ms)"
else
echo "OK"
fi
PASSED=$((PASSED + 1))
else
echo "FAIL"
@@ -39,8 +49,8 @@ echo "=== FIPS Ping Test ($PROFILE topology) ==="
echo ""
# Wait for nodes to converge
echo "Waiting 5s for mesh convergence..."
sleep 5
echo "Waiting 3s for mesh convergence..."
sleep 3
if [ "$PROFILE" = "mesh" ]; then
# Sparse mesh topology: A-B, B-C, C-D, D-E, E-A, A-D