mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-30 07:37:36 +00:00
The NAT lab models the public internet with a bridge in a private range, so nodes behind its NAT routers learn private reflexive addresses that no interface of theirs holds. That is exactly the shape a node should refuse to punch, and only an exemption for nodes whose own STUN result is private let the cone scenario pass. Move the lab's wan bridge to 198.18.0.0/15, the benchmarking range, both as the compose default and as the range local CI claims its per-run wan network from, so the lab's reflexive addresses are public as an internet address is. The shared LAN stays private.
171 lines
4.9 KiB
Bash
Executable File
171 lines
4.9 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
NAT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
ROOT_DIR="$(cd "$NAT_DIR/../.." && pwd)"
|
|
DERIVE_KEYS="$ROOT_DIR/testing/lib/derive_keys.py"
|
|
# Per-run, for the same reason static's and firewall's generators are: this
|
|
# directory is bind-mounted by compose and read back by the suite scripts
|
|
# AFTER the containers are up, so a shared path lets a second run's generator
|
|
# overwrite the npubs a first run is about to ping. Empty suffix renders
|
|
# today's plain path, so a bare invocation is unchanged.
|
|
OUTPUT_DIR="$NAT_DIR/generated-configs${FIPS_CI_NAME_SUFFIX:-}"
|
|
SCENARIO="${1:?usage: generate-configs.sh <cone|symmetric|lan> [mesh-name]}"
|
|
MESH_NAME="${2:-nat-lab-$(date +%s)-$$}"
|
|
|
|
case "$SCENARIO" in
|
|
cone|symmetric|lan|nostr-publish-consume|stun-faults) ;;
|
|
*)
|
|
echo "Unknown scenario: $SCENARIO" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
mkdir -p "$OUTPUT_DIR/$SCENARIO"
|
|
|
|
keys_a="$(python3 "$DERIVE_KEYS" "$MESH_NAME" "a")"
|
|
keys_b="$(python3 "$DERIVE_KEYS" "$MESH_NAME" "b")"
|
|
|
|
nsec_a="$(echo "$keys_a" | awk -F= '/^nsec=/{print $2}')"
|
|
npub_a="$(echo "$keys_a" | awk -F= '/^npub=/{print $2}')"
|
|
nsec_b="$(echo "$keys_b" | awk -F= '/^nsec=/{print $2}')"
|
|
npub_b="$(echo "$keys_b" | awk -F= '/^npub=/{print $2}')"
|
|
|
|
# The two lab bridges. ci-local.sh claims a free /24 for each per run and
|
|
# exports these; unset renders the base compose file's default addresses, so
|
|
# a bare invocation and the GitHub matrix need no claim. The wan default is in
|
|
# 198.18.0.0/15, not a private range, because it stands in for the public
|
|
# internet.
|
|
wan="${NAT_WAN_PREFIX:-198.18.254}"
|
|
lan="${NAT_LAN_PREFIX:-172.31.10}"
|
|
|
|
relay_addr="ws://${wan}.30:7777"
|
|
stun_addr="stun:${wan}.40:3478"
|
|
if [ "$SCENARIO" = "lan" ] || [ "$SCENARIO" = "nostr-publish-consume" ] \
|
|
|| [ "$SCENARIO" = "stun-faults" ]; then
|
|
relay_addr="ws://${lan}.30:7777"
|
|
stun_addr="stun:${lan}.40:3478"
|
|
fi
|
|
|
|
peer_block_a=$(cat <<EOF
|
|
- npub: "$npub_b"
|
|
alias: "node-b"
|
|
addresses:
|
|
- transport: udp
|
|
addr: "nat"
|
|
priority: 1
|
|
EOF
|
|
)
|
|
|
|
peer_block_b=$(cat <<EOF
|
|
- npub: "$npub_a"
|
|
alias: "node-a"
|
|
addresses:
|
|
- transport: udp
|
|
addr: "nat"
|
|
priority: 1
|
|
EOF
|
|
)
|
|
|
|
if [ "$SCENARIO" = "symmetric" ]; then
|
|
peer_block_a="$peer_block_a"$'\n'" - transport: tcp
|
|
addr: \"${wan}.11:8443\"
|
|
priority: 20"
|
|
peer_block_b="$peer_block_b"$'\n'" - transport: tcp
|
|
addr: \"${wan}.10:8443\"
|
|
priority: 20"
|
|
fi
|
|
|
|
write_config() {
|
|
local output_file="$1"
|
|
local nsec="$2"
|
|
local peer_block="$3"
|
|
|
|
cat > "$output_file" <<EOF
|
|
node:
|
|
identity:
|
|
nsec: "$nsec"
|
|
retry:
|
|
max_retries: 3
|
|
base_interval_secs: 2
|
|
max_backoff_secs: 8
|
|
rendezvous:
|
|
nostr:
|
|
enabled: true
|
|
advertise: true
|
|
app: "fips.nat.lab.v1"
|
|
advert_relays:
|
|
- "$relay_addr"
|
|
dm_relays:
|
|
- "$relay_addr"
|
|
stun_servers:
|
|
- "$stun_addr"
|
|
signal_ttl_secs: 30
|
|
attempt_timeout_secs: 6
|
|
# Must stay above signal_ttl_secs plus 60s of clock-skew grace on each
|
|
# side, or config validation refuses to start the node. 180 keeps a 30s
|
|
# margin over the 150s freshness window the 30s TTL implies.
|
|
replay_window_secs: 180
|
|
punch_start_delay_ms: 500
|
|
punch_interval_ms: 100
|
|
punch_duration_ms: 2500
|
|
advert_ttl_secs: 60
|
|
advert_refresh_secs: 20
|
|
|
|
tun:
|
|
enabled: true
|
|
name: fips0
|
|
mtu: 1280
|
|
|
|
dns:
|
|
enabled: true
|
|
port: 5354
|
|
|
|
transports:
|
|
udp:
|
|
bind_addr: "0.0.0.0:2121"
|
|
mtu: 1472
|
|
advertise_on_nostr: true
|
|
public: false
|
|
tcp:
|
|
bind_addr: "0.0.0.0:8443"
|
|
|
|
peers:
|
|
$peer_block
|
|
connect_policy: auto_connect
|
|
auto_reconnect: true
|
|
EOF
|
|
}
|
|
|
|
write_config "$OUTPUT_DIR/$SCENARIO/node-a.yaml" "$nsec_a" "$peer_block_a"
|
|
write_config "$OUTPUT_DIR/$SCENARIO/node-b.yaml" "$nsec_b" "$peer_block_b"
|
|
|
|
# stun-faults runs two real FIPS daemons:
|
|
# stun-fault-node (key "a") — target of tc/iptables faults via the shim
|
|
# stun-fault-peer (key "b") — fault-free peer that publishes a valid
|
|
# overlay advert so the fault-node's
|
|
# traversal actually invokes the STUN client
|
|
# Mutual peering ensures both sides advertise; without a real advert the
|
|
# fault-node would abort at "no overlay advert" and never generate STUN
|
|
# egress. The shim's netem/iptables rules can then meaningfully drop
|
|
# the STUN UDP traffic during Phase 1.
|
|
if [ "$SCENARIO" = "stun-faults" ]; then
|
|
write_config "$OUTPUT_DIR/$SCENARIO/stun-fault-node.yaml" \
|
|
"$nsec_a" "$peer_block_a"
|
|
write_config "$OUTPUT_DIR/$SCENARIO/stun-fault-peer.yaml" \
|
|
"$nsec_b" "$peer_block_b"
|
|
fi
|
|
|
|
cat > "$OUTPUT_DIR/$SCENARIO/npubs.env" <<EOF
|
|
NPUB_A=$npub_a
|
|
NPUB_B=$npub_b
|
|
MESH_NAME=$MESH_NAME
|
|
SCENARIO=$SCENARIO
|
|
EOF
|
|
|
|
echo "Generated NAT lab configs for scenario=$SCENARIO mesh=$MESH_NAME"
|
|
echo "NPUB_A=$npub_a"
|
|
echo "NPUB_B=$npub_b"
|