#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: ./scripts/cir/run_cir_replay_rpki_client.sh \ --cir \ --repo-bytes-db \ --out-dir \ --reference-ccr \ [--build-dir | --rpki-client-bin ] \ [--keep-db] \ [--real-rsync-bin ] EOF } ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" CIR="" REPO_BYTES_DB="" OUT_DIR="" REFERENCE_CCR="" BUILD_DIR="" RPKI_CLIENT_BIN="" KEEP_DB=0 REAL_RSYNC_BIN="${REAL_RSYNC_BIN:-/usr/bin/rsync}" CIR_MATERIALIZE_BIN="${CIR_MATERIALIZE_BIN:-$ROOT_DIR/target/release/cir_materialize}" CIR_EXTRACT_INPUTS_BIN="${CIR_EXTRACT_INPUTS_BIN:-$ROOT_DIR/target/release/cir_extract_inputs}" CCR_TO_COMPARE_VIEWS_BIN="${CCR_TO_COMPARE_VIEWS_BIN:-$ROOT_DIR/target/release/ccr_to_compare_views}" WRAPPER="$ROOT_DIR/scripts/cir/cir-rsync-wrapper" while [[ $# -gt 0 ]]; do case "$1" in --cir) CIR="$2"; shift 2 ;; --repo-bytes-db) REPO_BYTES_DB="$2"; shift 2 ;; --out-dir) OUT_DIR="$2"; shift 2 ;; --reference-ccr) REFERENCE_CCR="$2"; shift 2 ;; --build-dir) BUILD_DIR="$2"; shift 2 ;; --rpki-client-bin) RPKI_CLIENT_BIN="$2"; shift 2 ;; --keep-db) KEEP_DB=1; shift ;; --real-rsync-bin) REAL_RSYNC_BIN="$2"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "unknown argument: $1" >&2; usage; exit 2 ;; esac done [[ -n "$CIR" && -n "$REPO_BYTES_DB" && -n "$OUT_DIR" && -n "$REFERENCE_CCR" ]] || { usage >&2 exit 2 } if [[ -z "$BUILD_DIR" && -z "$RPKI_CLIENT_BIN" ]]; then usage >&2 exit 2 fi if [[ -z "$RPKI_CLIENT_BIN" ]]; then RPKI_CLIENT_BIN="$BUILD_DIR/src/rpki-client" fi if [[ ! -x "$RPKI_CLIENT_BIN" ]]; then echo "rpki-client binary not executable: $RPKI_CLIENT_BIN" >&2 exit 2 fi mkdir -p "$OUT_DIR" if [[ ! -x "$CIR_MATERIALIZE_BIN" || ! -x "$CIR_EXTRACT_INPUTS_BIN" || ! -x "$CCR_TO_COMPARE_VIEWS_BIN" ]]; then ( cd "$ROOT_DIR" cargo build --release --bin cir_materialize --bin cir_extract_inputs --bin ccr_to_compare_views ) fi TMP_ROOT="$OUT_DIR/.tmp" TALS_DIR="$TMP_ROOT/tals" META_JSON="$TMP_ROOT/meta.json" MIRROR_ROOT="$TMP_ROOT/mirror" CACHE_DIR="$TMP_ROOT/cache" OUT_CCR_DIR="$TMP_ROOT/out" RUN_LOG="$OUT_DIR/rpki-client.log" ACTUAL_VRPS="$OUT_DIR/actual-vrps.csv" ACTUAL_VAPS="$OUT_DIR/actual-vaps.csv" ACTUAL_VAPS_META="$OUT_DIR/actual-vaps-meta.json" ACTUAL_VRPS_META="$OUT_DIR/actual-vrps-meta.json" REF_VRPS="$OUT_DIR/reference-vrps.csv" REF_VAPS="$OUT_DIR/reference-vaps.csv" SUMMARY_JSON="$OUT_DIR/compare-summary.json" rm -rf "$TMP_ROOT" mkdir -p "$TMP_ROOT" "$CIR_EXTRACT_INPUTS_BIN" --cir "$CIR" --tals-dir "$TALS_DIR" --meta-json "$META_JSON" python3 - <<'PY' "$TALS_DIR" from pathlib import Path import sys for tal in Path(sys.argv[1]).glob("*.tal"): lines = tal.read_text(encoding="utf-8").splitlines() rsync_uris = [line for line in lines if line.startswith("rsync://")] base64_lines = [] seen_sep = False for line in lines: if seen_sep: if line.strip(): base64_lines.append(line) elif line.strip() == "": seen_sep = True tal.write_text("\n".join(rsync_uris) + "\n\n" + "\n".join(base64_lines) + "\n", encoding="utf-8") PY materialize_cmd=("$CIR_MATERIALIZE_BIN" --cir "$CIR" --repo-bytes-db "$REPO_BYTES_DB" --mirror-root "$MIRROR_ROOT") if [[ "$KEEP_DB" -eq 1 ]]; then materialize_cmd+=(--keep-db) fi "${materialize_cmd[@]}" VALIDATION_EPOCH="$(python3 - <<'PY' "$META_JSON" from datetime import datetime, timezone import json, sys vt = json.load(open(sys.argv[1]))["validationTime"] dt = datetime.fromisoformat(vt.replace("Z", "+00:00")).astimezone(timezone.utc) print(int(dt.timestamp())) PY )" mapfile -t TAL_PATHS < <(python3 - <<'PY' "$META_JSON" import json, sys for item in json.load(open(sys.argv[1], encoding="utf-8"))["talFiles"]: print(item["path"]) PY ) CLIENT_TAL_ARGS=() for tal_path in "${TAL_PATHS[@]}"; do CLIENT_TAL_ARGS+=(-t "$tal_path") done COMPARE_TRUST_ANCHOR="unknown" export CIR_MIRROR_ROOT="$(python3 - <<'PY' "$MIRROR_ROOT" from pathlib import Path import sys print(Path(sys.argv[1]).resolve()) PY )" export REAL_RSYNC_BIN="$REAL_RSYNC_BIN" export CIR_LOCAL_LINK_MODE=1 mkdir -p "$CACHE_DIR" "$OUT_CCR_DIR" chmod -R 0777 "$TMP_ROOT" "$RPKI_CLIENT_BIN" \ -R \ -e "$WRAPPER" \ -P "$VALIDATION_EPOCH" \ "${CLIENT_TAL_ARGS[@]}" \ -d "$CACHE_DIR" \ "$OUT_CCR_DIR" >"$RUN_LOG" 2>&1 if [[ -f "$OUT_CCR_DIR/rpki.ccr" ]]; then "$CCR_TO_COMPARE_VIEWS_BIN" \ --ccr "$OUT_CCR_DIR/rpki.ccr" \ --vrps-out "$ACTUAL_VRPS" \ --vaps-out "$ACTUAL_VAPS" \ --trust-anchor "$COMPARE_TRUST_ANCHOR" else python3 - <<'PY' "$OUT_CCR_DIR/json" "$ACTUAL_VRPS" "$ACTUAL_VAPS" "$COMPARE_TRUST_ANCHOR" import csv import json import sys from pathlib import Path json_path = Path(sys.argv[1]) vrps_out = Path(sys.argv[2]) vaps_out = Path(sys.argv[3]) compare_ta = sys.argv[4] if not json_path.is_file(): raise SystemExit(f"rpki-client output has neither rpki.ccr nor json: {json_path}") data = json.loads(json_path.read_text(encoding="utf-8")) vrps_out.parent.mkdir(parents=True, exist_ok=True) with vrps_out.open("w", newline="", encoding="utf-8") as fh: writer = csv.writer(fh) writer.writerow(["ASN", "IP Prefix", "Max Length", "Trust Anchor"]) for roa in data.get("roas", []): writer.writerow([ f"AS{roa['asn']}", roa["prefix"], str(roa["maxLength"]), compare_ta, ]) with vaps_out.open("w", newline="", encoding="utf-8") as fh: writer = csv.writer(fh) writer.writerow(["Customer ASN", "Providers", "Trust Anchor"]) for aspa in data.get("aspas", []): providers = ";".join(f"AS{item}" for item in sorted(aspa.get("providers", []))) writer.writerow([ f"AS{aspa['customer_asid']}", providers, compare_ta, ]) PY fi python3 - <<'PY' "$ACTUAL_VRPS" "$ACTUAL_VAPS" "$ACTUAL_VRPS_META" "$ACTUAL_VAPS_META" import csv, json, sys def count_rows(path): with open(path, newline="") as f: rows = list(csv.reader(f)) return max(len(rows) - 1, 0) json.dump({"count": count_rows(sys.argv[1])}, open(sys.argv[3], "w"), indent=2) json.dump({"count": count_rows(sys.argv[2])}, open(sys.argv[4], "w"), indent=2) PY "$CCR_TO_COMPARE_VIEWS_BIN" --ccr "$REFERENCE_CCR" --vrps-out "$REF_VRPS" --vaps-out "$REF_VAPS" --trust-anchor "$COMPARE_TRUST_ANCHOR" python3 - <<'PY' "$ACTUAL_VRPS" "$REF_VRPS" "$ACTUAL_VAPS" "$REF_VAPS" "$SUMMARY_JSON" "$META_JSON" import csv, json, sys def rows(path): with open(path, newline="") as f: return list(csv.reader(f))[1:] actual_vrps = {tuple(r) for r in rows(sys.argv[1])} ref_vrps = {tuple(r) for r in rows(sys.argv[2])} actual_vaps = {tuple(r) for r in rows(sys.argv[3])} ref_vaps = {tuple(r) for r in rows(sys.argv[4])} meta = json.load(open(sys.argv[6], encoding="utf-8")) summary = { "compareMode": "trust-anchor-agnostic", "talCount": len(meta["talFiles"]), "talPaths": [item["path"] for item in meta["talFiles"]], "vrps": { "actual": len(actual_vrps), "reference": len(ref_vrps), "match": actual_vrps == ref_vrps, "only_in_actual": sorted(actual_vrps - ref_vrps)[:20], "only_in_reference": sorted(ref_vrps - actual_vrps)[:20], }, "vaps": { "actual": len(actual_vaps), "reference": len(ref_vaps), "match": actual_vaps == ref_vaps, "only_in_actual": sorted(actual_vaps - ref_vaps)[:20], "only_in_reference": sorted(ref_vaps - actual_vaps)[:20], } } with open(sys.argv[5], "w") as f: json.dump(summary, f, indent=2) PY if [[ "$KEEP_DB" -ne 1 ]]; then rm -rf "$TMP_ROOT" fi echo "done: $OUT_DIR"