mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
Implement TCP transport for FIPS enabling firewall traversal and serving as the foundation for future Tor transport. This is the first connection-oriented transport in the system. Key design decisions: - FMP header-based framing: reuses existing 4-byte FMP common prefix for packet boundary recovery with zero framing overhead - Session survives TCP reconnection: Noise/MMP/FSP state bound to npub, not TCP connection; MMP liveness is sole authority for peer death - Connect-on-send: fresh connection on first send, transparent reconnect - close_connection() trait method for cross-connection deduplication cleanup New transport files: - src/transport/tcp/mod.rs: TcpTransport, connection pool, accept loop - src/transport/tcp/stream.rs: FMP-aware stream reader (shared with Tor) Modified: transport trait (close_connection), TcpConfig, TransportHandle match arms, create_transports(), initiate_connection() for connection- oriented links, cross-connection tie-breaker cleanup, design docs. Tree announce loop and TCP stability fixes: - Preserve tree announce rate-limit state across reconnection: carry forward last_tree_announce_sent_ms when a peer reconnects so the rate-limit window isn't reset to zero - Drop oversize TCP packets at sender: pre-send MTU check returns MtuExceeded instead of writing to the stream, preventing receiver-side connection teardown and reset-reconnect cycles Chaos harness: - TCP transport support: tcp_edges/has_tcp/tcp_peers in SimTopology, transport-aware config_gen with per-edge transport type, TCP port 443, pure-TCP node support - Include all non-Ethernet edges in directed_outbound() - Fix netem/links log messages to say "IP-based" instead of "UDP" - Add tcp-chain, tcp-only, and tcp-mesh scenario files Static harness: - Transport-aware config generation (get_default_transport, transport_port) - TCP transport injection via Python post-processing - Add tcp-chain topology and docker-compose profile
255 lines
8.0 KiB
Bash
Executable File
255 lines
8.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Generate FIPS node configuration files from template and topology definition.
|
|
#
|
|
# Usage: ./generate-configs.sh <topology> [mesh-name]
|
|
# topology: mesh, mesh-public, chain, etc.
|
|
# mesh-name: optional; when given, docker node identities are derived
|
|
# deterministically via sha256(mesh-name|node-id)
|
|
|
|
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"
|
|
DERIVE_KEYS="$SCRIPT_DIR/derive-keys.py"
|
|
|
|
# Parse topology YAML to extract node attributes
|
|
# Usage: get_node_attr <topology_file> <node_id> <attr_name>
|
|
get_node_attr() {
|
|
local topology_file="$1"
|
|
local node_id="$2"
|
|
local attr="$3"
|
|
# Handle both docker_ip and external_ip as "address"
|
|
if [ "$attr" = "address" ]; then
|
|
local ip=$(grep -A 10 "^ $node_id:" "$topology_file" | grep "docker_ip:" | head -1 | sed 's/.*: *"*\([^"]*\)".*/\1/')
|
|
if [ -z "$ip" ]; then
|
|
ip=$(grep -A 10 "^ $node_id:" "$topology_file" | grep "external_ip:" | head -1 | sed 's/.*: *"*\([^"]*\)".*/\1/')
|
|
fi
|
|
echo "$ip"
|
|
else
|
|
grep -A 10 "^ $node_id:" "$topology_file" | grep "${attr}:" | head -1 | sed 's/.*: *"*\([^"]*\)".*/\1/'
|
|
fi
|
|
}
|
|
|
|
# Check if a node is external (has external_ip instead of docker_ip)
|
|
is_external_node() {
|
|
local topology_file="$1"
|
|
local node_id="$2"
|
|
local docker_ip=$(grep -A 10 "^ $node_id:" "$topology_file" | grep "docker_ip:" | head -1)
|
|
[ -z "$docker_ip" ]
|
|
}
|
|
|
|
# Get peers list from topology
|
|
get_peers() {
|
|
local topology_file="$1"
|
|
local node_id="$2"
|
|
grep -A 10 "^ $node_id:" "$topology_file" | grep "peers:" | head -1 | \
|
|
sed 's/.*: *\[\(.*\)\].*/\1/' | \
|
|
sed 's/,/ /g' | \
|
|
tr -s ' ' | \
|
|
sed 's/^ *//;s/ *$//'
|
|
}
|
|
|
|
# Get all node IDs from topology file
|
|
get_node_ids() {
|
|
local topology_file="$1"
|
|
grep "^ [a-z][a-z0-9_-]*:" "$topology_file" | sed 's/^ \([a-z][a-z0-9_-]*\):.*/\1/'
|
|
}
|
|
|
|
# Resolve nsec and npub for a node.
|
|
# If MESH_NAME is set and node is not external, derive from mesh-name.
|
|
# Otherwise use the value from the topology YAML.
|
|
# Output: two lines: nsec=<hex>\nnpub=<bech32>
|
|
resolve_keys() {
|
|
local topology_file="$1"
|
|
local node_id="$2"
|
|
|
|
if [ -n "$MESH_NAME" ] && ! is_external_node "$topology_file" "$node_id"; then
|
|
python3 "$DERIVE_KEYS" "$MESH_NAME" "$node_id"
|
|
else
|
|
local nsec
|
|
local npub
|
|
nsec=$(get_node_attr "$topology_file" "$node_id" "nsec")
|
|
npub=$(get_node_attr "$topology_file" "$node_id" "npub")
|
|
echo "nsec=$nsec"
|
|
echo "npub=$npub"
|
|
fi
|
|
}
|
|
|
|
# Get the default transport from topology file (defaults to "udp")
|
|
get_default_transport() {
|
|
local topology_file="$1"
|
|
local transport=$(grep "^default_transport:" "$topology_file" | head -1 | sed 's/.*: *\([a-z]*\).*/\1/')
|
|
echo "${transport:-udp}"
|
|
}
|
|
|
|
# Get the port for a given transport type
|
|
transport_port() {
|
|
local transport="$1"
|
|
case "$transport" in
|
|
tcp) echo "443" ;;
|
|
*) echo "2121" ;;
|
|
esac
|
|
}
|
|
|
|
generate_peer_block() {
|
|
local topology_file="$1"
|
|
local peer_id="$2"
|
|
|
|
local peer_npub="$(get_key RESOLVED_NPUB "$peer_id")"
|
|
local peer_ip=$(get_node_attr "$topology_file" "$peer_id" "address")
|
|
local transport=$(get_default_transport "$topology_file")
|
|
local port=$(transport_port "$transport")
|
|
|
|
cat <<EOF
|
|
- npub: "$peer_npub"
|
|
alias: "node-$peer_id"
|
|
addresses:
|
|
- transport: $transport
|
|
addr: "$peer_ip:$port"
|
|
connect_policy: auto_connect
|
|
EOF
|
|
}
|
|
|
|
generate_config() {
|
|
local node_id="$1"
|
|
local topology_file="$2"
|
|
local output_file="$3"
|
|
|
|
local node_npub
|
|
node_npub="$(get_key RESOLVED_NPUB "$node_id")"
|
|
local node_nsec
|
|
node_nsec="$(get_key RESOLVED_NSEC "$node_id")"
|
|
local peers
|
|
peers=$(get_peers "$topology_file" "$node_id")
|
|
|
|
# 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 "$topology_file" "$peer_id")"
|
|
done
|
|
else
|
|
peers_config=" []"
|
|
fi
|
|
|
|
# Read and process template
|
|
local template=$(cat "$TEMPLATE_FILE")
|
|
local config="$template"
|
|
|
|
config="${config//\{\{NODE_NAME\}\}/$(echo "$node_id" | tr '[:lower:]' '[:upper:]')}"
|
|
config="${config//\{\{TOPOLOGY\}\}/$(basename "$topology_file" .yaml)}"
|
|
config="${config//\{\{NPUB\}\}/$node_npub}"
|
|
config="${config//\{\{NSEC\}\}/$node_nsec}"
|
|
config="${config//\{\{PEERS\}\}/$peers_config}"
|
|
|
|
echo "$config" > "$output_file"
|
|
|
|
# Post-process: inject TCP transport config for TCP topologies
|
|
local transport
|
|
transport=$(get_default_transport "$topology_file")
|
|
if [ "$transport" = "tcp" ]; then
|
|
# Add TCP transport section and remove UDP transport
|
|
python3 -c "
|
|
import yaml, sys
|
|
with open('$output_file') as f:
|
|
cfg = yaml.safe_load(f)
|
|
cfg.setdefault('transports', {})['tcp'] = {'bind_addr': '0.0.0.0:443'}
|
|
cfg.get('transports', {}).pop('udp', None)
|
|
with open('$output_file', 'w') as f:
|
|
yaml.dump(cfg, f, default_flow_style=False, sort_keys=False)
|
|
"
|
|
fi
|
|
}
|
|
|
|
# Key storage for bash 3.2 compatibility (using prefixed variables instead of associative arrays)
|
|
# Usage: set_key NSEC a "value" / get_key NSEC a
|
|
set_key() {
|
|
local prefix="$1"
|
|
local key="$2"
|
|
local value="$3"
|
|
eval "${prefix}_${key}=\"${value}\""
|
|
}
|
|
|
|
get_key() {
|
|
local prefix="$1"
|
|
local key="$2"
|
|
eval "echo \"\$${prefix}_${key}\""
|
|
}
|
|
|
|
generate_topology() {
|
|
local topology_name="$1"
|
|
local topology_file="$CONFIG_DIR/topologies/$topology_name.yaml"
|
|
local output_dir="$GENERATED_DIR/$topology_name"
|
|
|
|
if [ ! -f "$topology_file" ]; then
|
|
echo "Error: Topology file not found: $topology_file"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Generating $topology_name topology configs..."
|
|
if [ -n "$MESH_NAME" ]; then
|
|
echo " Mesh name: $MESH_NAME (deriving docker node identities)"
|
|
fi
|
|
mkdir -p "$output_dir"
|
|
|
|
# Phase 1: resolve keys for all nodes
|
|
for node_id in $(get_node_ids "$topology_file"); do
|
|
local keys=""
|
|
keys=$(resolve_keys "$topology_file" "$node_id")
|
|
set_key RESOLVED_NSEC "$node_id" "$(echo "$keys" | grep "^nsec=" | cut -d= -f2)"
|
|
set_key RESOLVED_NPUB "$node_id" "$(echo "$keys" | grep "^npub=" | cut -d= -f2)"
|
|
done
|
|
|
|
# Phase 2: generate config files for docker nodes
|
|
for node_id in $(get_node_ids "$topology_file"); do
|
|
# Skip external nodes (they don't need Docker config files)
|
|
if is_external_node "$topology_file" "$node_id"; then
|
|
echo " ⚠ Skipping $node_id (external node)"
|
|
continue
|
|
fi
|
|
|
|
local output_file="$output_dir/node-$node_id.yaml"
|
|
generate_config "$node_id" "$topology_file" "$output_file"
|
|
echo " ✓ Generated $output_file"
|
|
done
|
|
|
|
# Phase 3: write npubs.env
|
|
local env_file="$GENERATED_DIR/npubs.env"
|
|
echo "# Generated by generate-configs.sh (topology: $topology_name)" > "$env_file"
|
|
if [ -n "$MESH_NAME" ]; then
|
|
echo "# Mesh name: $MESH_NAME" >> "$env_file"
|
|
fi
|
|
for node_id in $(get_node_ids "$topology_file"); do
|
|
local var_name="NPUB_$(echo "$node_id" | tr '[:lower:]' '[:upper:]')"
|
|
echo "${var_name}=$(get_key RESOLVED_NPUB "$node_id")" >> "$env_file"
|
|
done
|
|
echo " ✓ Generated $env_file"
|
|
}
|
|
|
|
main() {
|
|
local requested="${1:-mesh}"
|
|
|
|
# Support any topology file in the topologies directory
|
|
if [ -f "$CONFIG_DIR/topologies/$requested.yaml" ]; then
|
|
generate_topology "$requested"
|
|
else
|
|
echo "Error: Unknown topology '$requested'"
|
|
echo "Usage: $0 <topology> [mesh-name]"
|
|
echo ""
|
|
echo "Available topologies:"
|
|
ls -1 "$CONFIG_DIR/topologies/" | sed 's/\.yaml$//' | sed 's/^/ - /'
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "✓ All configurations generated successfully!"
|
|
}
|
|
|
|
MESH_NAME="${2:-}"
|
|
main "$@"
|