diff --git a/Cargo.lock b/Cargo.lock index dfcf055..6a19179 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,16 +223,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bcder" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7c42c9913f68cf9390a225e81ad56a5c515347287eb98baa710090ca1de86d" -dependencies = [ - "bytes", - "smallvec", -] - [[package]] name = "bcrypt-pbkdf" version = "0.10.0" @@ -2112,7 +2102,6 @@ dependencies = [ "hex", "rand 0.10.0", "rocksdb", - "rpki 0.19.2", "russh", "rustls", "rustls-pemfile", @@ -2132,24 +2121,6 @@ dependencies = [ "x509-parser", ] -[[package]] -name = "rpki" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99010980abb0aee7a5c4b1c04d0f42f74f57ac168adb5d38a99b3517a4d72df9" -dependencies = [ - "base64", - "bcder", - "bytes", - "chrono", - "futures-util", - "log", - "ring", - "tokio", - "untrusted", - "uuid", -] - [[package]] name = "rsa" version = "0.10.0-rc.17" @@ -2938,16 +2909,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index ea318bf..9c84bfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ asn1-rs = "0.7.1" asn1-rs-derive = "0.6.0" asn1 = "0.23.0" arc-swap = "1.7.0" -chrono = "0.4.44" +chrono = { version = "0.4.44", features = ["serde"] } chrono-tz = "0.10" bytes = "1.11.1" tokio = { version = "1.49.0", features = ["full"] } @@ -38,4 +38,3 @@ rustls-pki-types = "1.14.0" socket2 = "0.5" russh = { version = "0.60.0", default-features = false, features = ["ring", "rsa"] } tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } -rpki_rs = { package = "rpki", version = "0.19.2", features = ["rtr", "crypto"] } diff --git a/README.md b/README.md index 9b809be..8d719a2 100644 --- a/README.md +++ b/README.md @@ -1,475 +1,306 @@ # RPKI RTR Server -默认运行平台:Ubuntu/Linux。 +这是一个用 Rust 实现的 RPKI-to-Router (RTR) cache server。服务端从 CCR 文件中读取 RPKI 数据,生成 RTR cache snapshot 和 delta,并通过 RTR 协议提供给路由器或调试客户端。 -## 目录 +当前代码重点覆盖: -- [协议与规范](#协议与规范) - - [RTR](#rtr) - - [SLURM](#slurm) - - [CCR](#ccr) -- [RTR Server](#rtr-server) - - [环境变量](#环境变量) - - [说明](#说明) -- [快速启动](#快速启动) - - [Docker 启动](#docker-启动) - - [本地运行(推荐先用脚本)](#本地运行推荐先用脚本) - - [本地手动运行(最小示例)](#本地手动运行最小示例) -- [CCR 输入说明](#ccr-输入说明) -- [Client 使用](#client-使用) - - [作用与选择](#作用与选择) - - [rtr_debug_client(本地)](#rtr_debug_client本地) - - [rtr_debug_client(Docker)](#rtr_debug_clientdocker) - - [rpki-rs-test-client(Docker)](#rpki-rs-test-clientdocker) - - [FRR(Docker,黑盒客户端)](#frrdocker黑盒客户端) -- [RTR Debug Client](#rtr-debug-client) - - [Client 启动示例](#client-启动示例) -- [存储模型与边界约束(多视图)](#存储模型与边界约束多视图) - - [唯一写入口](#唯一写入口) - - [恢复约束](#恢复约束) - - [边界防回归测试](#边界防回归测试) -- [Deploy Layout](#deploy-layout) - - [Quick Start](#quick-start) - - [Compose 入口](#compose-入口) - - [角色附加命令](#角色附加命令) +- RTR server:TCP 默认启用,可选启用 TLS/mTLS 和进程内 SSH transport。 +- 数据输入:从 CCR 目录选择最新 `.ccr` 文件,解析 VRP 和 VAP/ASPA。 +- 本地策略:可选加载 SLURM 文件,对 CCR 解析出的 payload 做过滤和本地断言。 +- 持久化:使用 RocksDB 保存 RTR cache 状态。 +- 运行报告:输出 source、client、runtime 三类 JSON report。 +- 运行期管理:可选启用 HTTP Admin API,修改部分 runtime config、触发 source reload、管理 SLURM 文件。 +- 调试工具:`rtr_debug_client` 支持 TCP、TLS 和 SSH 连接 RTR server。 -## 协议与规范 +## 协议参考 -### RTR +- RTR: [RFC 6810](https://www.rfc-editor.org/rfc/rfc6810.html), [RFC 8210](https://www.rfc-editor.org/rfc/rfc8210.html) +- SLURM: [RFC 8416](https://www.rfc-editor.org/rfc/rfc8416.html) +- CCR: [draft-ietf-sidrops-rpki-ccr](https://www.ietf.org/archive/id/draft-ietf-sidrops-rpki-ccr-02.html) -- [RFC 6810: The Resource Public Key Infrastructure (RPKI) to Router Protocol](https://www.rfc-editor.org/rfc/rfc6810.html) -- [RFC 8210: The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 1](https://www.rfc-editor.org/rfc/rfc8210.html) -- [Draft: The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 2](https://www.ietf.org/archive/id/draft-ietf-sidrops-8210bis-25.html) +## 项目结构 -### SLURM +| 路径 | 说明 | +| --- | --- | +| `src/main_rtr.rs` | RTR server 入口 | +| `src/rtr/` | RTR 协议、cache、store、server transport、admin、report | +| `src/source/` | CCR/SLURM source 加载流程 | +| `src/slurm/` | SLURM 文件解析、合并和策略应用 | +| `src/data_model/` | RPKI 相关数据结构 | +| `src/bin/rtr_debug_client/` | RTR 调试客户端 | +| `deploy/server/` | server Docker 镜像和 compose | +| `deploy/client/` | debug client Docker 镜像和 compose | +| `deploy/frr/`, `deploy/bird/` | FRR/BIRD 作为 RTR client 的测试部署 | +| `tests/` | 单元和集成测试 | -- [RFC 8416: Simplified Local Internet Number Resource Management with the RPKI (SLURM)](https://www.rfc-editor.org/rfc/rfc8416.html) -- [Draft: ASPA extensions for SLURM](https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-slurm-04.html) +## 构建与测试 -### CCR +```bash +cargo build --bin rpki_rtr +cargo build --bin rtr_debug_client +cargo test +``` -- [Draft: A Concise Cryptographic Representation of RPKI Signed Objects (CCR)](https://www.ietf.org/archive/id/draft-ietf-sidrops-rpki-ccr-02.html) +服务端 binary 名称是 `rpki_rtr`: -## RTR Server +```bash +cargo run --bin rpki_rtr +``` -RTR Server 运行时从 `CCR` 目录中扫描最新的 `.ccr` 文件作为输入源: +默认配置会监听 `0.0.0.0:323`,从 `./data` 查找 CCR 文件,并使用 `./rtr-db` 保存 RocksDB 数据。启动前需要确保 CCR 目录里存在可解析的 `.ccr` 文件。 -- `VRP` -- `VAP (Validated ASPA Payload) / ASPA` +## 数据输入 -相关实现位置: +服务端启动和刷新时会读取 `RPKI_RTR_CCR_DIR` 指向的目录: -- [`src/main.rs`](src/main.rs) -- [`src/source/ccr.rs`](src/source/ccr.rs) - -### 环境变量 - -| 变量名 | 说明 | 默认值 | 示例 | -| --- | --- | --- | --- | -| `RPKI_RTR_ENABLE_TLS` | 是否额外启用 TLS 监听。支持 `true/false`、`1/0`、`yes/no`、`on/off`。 | `false` | `true` | -| `RPKI_RTR_TCP_ADDR` | TCP 监听地址。 | `0.0.0.0:323` | `0.0.0.0:323` | -| `RPKI_RTR_TLS_ADDR` | TLS 监听地址。 | `0.0.0.0:324` | `0.0.0.0:324` | -| `RPKI_RTR_DB_PATH` | RocksDB 路径。 | `./rtr-db` | `./rtr-db` | -| `RPKI_RTR_CCR_DIR` | CCR 目录路径;程序会扫描其中最新的 `.ccr` 文件。 | `./data` | `./data` | -| `RPKI_RTR_SLURM_DIR` | SLURM 目录路径;为空或未设置表示禁用 SLURM。 | `未设置(禁用)` | `./slurm` | -| `RPKI_RTR_TLS_CERT_PATH` | TLS 服务端证书路径。 | `./certs/server.crt` | `./certs/server-dns.crt` | -| `RPKI_RTR_TLS_KEY_PATH` | TLS 服务端私钥路径。 | `./certs/server.key` | `./certs/server-dns.key` | -| `RPKI_RTR_TLS_CLIENT_CA_PATH` | 用于校验 router 客户端证书的 CA 证书路径。 | `./certs/client-ca.crt` | `./certs/client-ca.crt` | -| `RPKI_RTR_ENABLE_SSH` | 是否额外启用进程内原生 SSH 监听。支持 `true/false`、`1/0`、`yes/no`、`on/off`。 | `false` | `true` | -| `RPKI_RTR_SSH_ADDR` | SSH 监听地址。 | `0.0.0.0:22` | `0.0.0.0:22` | -| `RPKI_RTR_SSH_PORT` | SSH 监听端口(仅覆盖 `RPKI_RTR_SSH_ADDR` 中的端口)。 | `22` | `2022` | -| `RPKI_RTR_SSH_HOST_KEY_PATH` | OpenSSH host 私钥路径。 | `./certs/ssh_host_ed25519_key` | `./certs/ssh_host_ed25519_key` | -| `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH` | 允许接入的 router 公钥列表(authorized_keys)。 | `./certs/rtr-authorized_keys` | `./certs/rtr-authorized_keys` | -| `RPKI_RTR_SSH_USERNAME` | SSH 用户名白名单。 | `rpki-rtr` | `rpki-rtr` | -| `RPKI_RTR_SSH_SUBSYSTEM_NAME` | SSH 子系统名称。 | `rpki-rtr` | `rpki-rtr` | -| `RPKI_RTR_SSH_PASSWORD` | 可选的 SSH password 认证口令。未设置时仅允许 publickey;设置后同时允许 publickey 与 password。 | `未设置` | `test-password` | -| `RPKI_RTR_MAX_DELTA` | 最多保留多少条 delta。 | `100` | `100` | -| `RPKI_RTR_PRUNE_DELTA_BY_SNAPSHOT_SIZE` | 是否启用“累计 delta 估算 wire size 不小于 snapshot 时,继续裁剪最老 delta”的策略。 | `false` | `false` | -| `RPKI_RTR_STRICT_CCR_VALIDATION` | 是否对 CCR 中的非法 VRP / VAP 采用严格模式;`true` 表示整份 CCR 拒绝,`false` 表示跳过非法项并告警。 | `false` | `false` | -| `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS` | 数据源刷新周期:扫描 CCR/SLURM 并尝试更新缓存的间隔,单位秒,必须 `>= 1`。 | `300` | `30` | -| `RPKI_RTR_REFRESH_INTERVAL_SECS` | 旧变量名(已兼容,建议迁移到 `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS`)。 | `300` | `300` | -| `RPKI_RTR_TIMING_REFRESH_SECS` | RTR EndOfData timing 的 `refresh` 字段,单位秒。 | `3600` | `300` | -| `RPKI_RTR_TIMING_RETRY_SECS` | RTR EndOfData timing 的 `retry` 字段,单位秒。 | `600` | `60` | -| `RPKI_RTR_TIMING_EXPIRE_SECS` | RTR EndOfData timing 的 `expire` 字段,单位秒(需大于 refresh/retry)。 | `7200` | `1800` | -| `RPKI_RTR_MAX_CONNECTIONS` | 最大并发 RTR 客户端连接数。 | `512` | `512` | -| `RPKI_RTR_NOTIFY_QUEUE_SIZE` | Serial Notify 广播队列大小。 | `1024` | `1024` | -| `RPKI_RTR_TCP_KEEPALIVE_SECS` | TCP keepalive 时间,单位秒;设为 `0` 表示禁用。 | `60` | `60` | -| `RPKI_RTR_WARN_INSECURE_TCP` | 纯 TCP 模式下是否输出不安全警告。 | `true` | `true` | -| `RPKI_RTR_REQUIRE_TLS_SERVER_DNS_NAME_SAN` | 严格模式:TLS 服务端证书不包含 `subjectAltName dNSName` 时拒绝启动。 | `false` | `false` | -| `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS` | `rtr-runtime-*.json` 周期写入间隔,单位秒,必须 `>= 1`。 | `300` | `300` | -| `RPKI_RTR_REPORT_HISTORY_LIMIT` | `report/` 目录下每类报告文件的滚动保留数量。 | `10` | `10` | -| `RPKI_RTR_TIMEZONE` | 日志和 JSON report 时间使用的 IANA 时区。 | `Asia/Shanghai` | `Asia/Shanghai` | -| `RPKI_RTR_ADMIN_ADDR` | Runtime admin config HTTP 监听地址;空值表示不启动。 | 未设置(禁用) | `127.0.0.1:8323` | -| `RPKI_RTR_ADMIN_TOKEN` | Admin config 接口 Bearer token。非 loopback 监听地址必须设置。 | 未设置 | `change-me` | - -### 说明 - -- 纯 TCP 模式只应部署在受信任、可控网络中。 -- TLS 模式要求客户端证书认证。 -- 当前输入源是 `CCR` 目录,不再是单独的文本 VRP / ASPA / Router Key 文件。 -- 刷新时会重新扫描 `RPKI_RTR_CCR_DIR`,并选取文件名排序最新的 `.ccr` 文件。 -- `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS`(旧名 `RPKI_RTR_REFRESH_INTERVAL_SECS`)控制的是“服务端多长时间扫描并更新数据源”,不是 RTR EndOfData 的 timing。 -- RTR 客户端在 EndOfData 中看到的 `refresh/retry/expire` 由 `RPKI_RTR_TIMING_REFRESH_SECS`、`RPKI_RTR_TIMING_RETRY_SECS`、`RPKI_RTR_TIMING_EXPIRE_SECS` 控制。 -- `RPKI_RTR_STRICT_CCR_VALIDATION=false` 时,CCR 中的非法 VRP / VAP 会被跳过并输出 warning;`true` 时整份 CCR 更新失败。 -- `RPKI_RTR_TCP_KEEPALIVE_SECS=0` 表示关闭 keepalive;非零值表示在整个连接生命周期内启用 keepalive。 -- `RPKI_RTR_PRUNE_DELTA_BY_SNAPSHOT_SIZE=true` 时,除了 `RPKI_RTR_MAX_DELTA` 的固定条数裁剪外,还会在累计 delta 估算 wire size 不小于 snapshot 时继续删除最老 delta。 - -### Runtime Admin Config - -`POST /admin/rtr/config` 用于在服务运行中动态修改一部分运行时配置。接口默认关闭;设置 `RPKI_RTR_ADMIN_ADDR` 后才会启动。完整接口说明见 [`docs/rtr-admin-api.md`](docs/rtr-admin-api.md)。 - -### Runtime Reports - -服务会在 `report/` 目录下写入分类型 JSON 文件,并按 -`RPKI_RTR_REPORT_HISTORY_LIMIT` 循环保留: - -- `rtr-source-*.json`:CCR/SLURM source、fingerprint、refresh 状态、数据质量、cache snapshot/delta 统计。 -- `rtr-clients-*.json`:client 连接数、连接方式统计。启动时写一次,连接数变化时更新。 -- `rtr-runtime-*.json`:进程 RSS、服务状态、当前生效 runtime configuration。启动时写一次,并按 `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS` 周期更新。 +1. 如果该目录下存在包含 `.ccr` 文件的子目录,先按子目录名排序选择最新子目录。 +2. 在选中的目录中按文件名排序选择最新 `.ccr` 文件。 +3. 从 CCR 中解析 VRP 和 VAP/ASPA。 +4. 如果配置了 `RPKI_RTR_SLURM_DIR`,加载其中启用的 `*.slurm` 文件并应用本地策略。 ## 快速启动 -### Docker 启动 +### Docker 运行 + +推荐使用 `deploy/server/build-run.sh` 和 `deploy/client/build-run.sh` 分别管理 server 与 debug client: +以下命令默认从仓库根目录执行。 + +```bash +chmod +x deploy/server/build-run.sh deploy/client/build-run.sh +./deploy/server/build-run.sh up --mode tcp +./deploy/client/build-run.sh up --mode tcp +./deploy/client/build-run.sh down +./deploy/server/build-run.sh down +``` + +更多 action、mode 和环境变量说明见 `deploy/README.md`。 + +### 跨架构构建 +示例:x86 开发机本地构建 ARM 镜像,打包后传到 ARM 部署机。 + +```bash +./deploy/server/build-run.sh buildx \ + --platform linux/arm64 \ + --server-image rpki-rtr:arm64 + +./deploy/client/build-run.sh buildx \ + --platform linux/arm64 \ + --client-image rpki-rtr-debug-client:arm64 + +mkdir -p dist/images +docker save rpki-rtr:arm64 -o dist/images/rpki-rtr-arm64.tar +docker save rpki-rtr-debug-client:arm64 -o dist/images/rpki-rtr-debug-client-arm64.tar + +scp dist/images/*.tar user@arm-host:/tmp/ +``` + +ARM 部署机加载镜像并运行: + +```bash +docker load -i /tmp/rpki-rtr-arm64.tar +docker load -i /tmp/rpki-rtr-debug-client-arm64.tar + +SERVER_IMAGE=rpki-rtr:arm64 \ +BUILD=0 ./deploy/server/build-run.sh up --mode tcp + +CLIENT_IMAGE=rpki-rtr-debug-client:arm64 \ +BUILD=0 ./deploy/client/build-run.sh up --mode tcp +``` ```bash docker compose -f deploy/server/docker-compose.yml up -d --build -docker compose -f deploy/server/docker-compose.yml logs -f rpki-rtr docker compose -f deploy/server/docker-compose.yml down ``` -### 本地运行(推荐先用脚本) +TLS/mTLS 示例: ```bash -docker compose -f deploy/server/docker-compose.yml up -d --build -docker compose -f deploy/server/docker-compose.yml logs -f rpki-rtr -docker compose -f deploy/server/docker-compose.yml down +docker compose -f deploy/server/docker-compose.tls.yml up -d --build +docker compose -f deploy/server/docker-compose.tls.yml down ``` -### 本地手动运行(最小示例) +## 传输模式 -纯 TCP: +TCP 默认启用。TLS 和 SSH 是附加监听,不会替代 TCP。 -```bash -docker compose -f deploy/server/docker-compose.yml up -d --build -``` - -TLS / mutual TLS: - -```bash -docker compose -f deploy/server/docker-compose.yml -f deploy/server/docker-compose.tls.yml up -d --build -``` - -## CCR 输入说明 - -当前会从 `RPKI_RTR_CCR_DIR` 指向的目录中扫描最新 `.ccr` 文件,并从中提取: - -- `VRP` -- `VAP (Validated ASPA Payload) / ASPA` - -测试样例可参考: - -- [`data/20260324T000037Z-sng1.ccr`](data/20260324T000037Z-sng1.ccr) - -## Client 使用 - -客户端的作用是从不同角度验证 RTR 服务端行为,覆盖“协议调试、自动化测试、黑盒互通”三类需求。 - -### 作用与选择 - -| Client | 主要作用 | 适用场景 | +| 模式 | 默认地址 | 启用方式 | | --- | --- | --- | -| `rtr_debug_client` | 面向协议交互调试,便于手动发起 `reset/serial` 并观察响应 | 开发联调、定位会话/报文问题、快速复现 | -| `rpki-rs-test-client` | 基于 `rpki-rs` 客户端 API 做自动化验证 | CI/CD、回归测试、批量步骤校验 | -| `FRR` | 真实路由软件黑盒接入,验证与生产侧客户端互通 | 互操作验证、运维演练、端到端验证 | +| TCP | `0.0.0.0:323` | 默认启用 | +| TLS/mTLS | `0.0.0.0:324` | `RPKI_RTR_ENABLE_TLS=true` | +| SSH | `0.0.0.0:22` | `RPKI_RTR_ENABLE_SSH=true` | -建议使用顺序: +TLS server 会加载服务端证书、私钥和 client CA。SSH server 使用 OpenSSH host key 和 authorized_keys 文件;`RPKI_RTR_SSH_AUTH_MODE` 支持 `key`、`password`、`both`。 -1. 先用 `rtr_debug_client` 快速确认服务可连通、协议版本和基础响应正常。 -2. 再用 `rpki-rs-test-client` 做可重复的自动化步骤校验。 -3. 最后用 `FRR` 做黑盒互通验证,确认真实客户端接入行为。 +## 主要环境变量 -### rtr_debug_client(Docker) +布尔值支持 `true/false`、`1/0`、`yes/no`、`on/off`。 + +| 变量 | 默认值 | 说明 | +| --- | --- | --- | +| `RPKI_RTR_ENABLE_TLS` | `false` | 是否启用 TLS/mTLS 监听 | +| `RPKI_RTR_ENABLE_SSH` | `false` | 是否启用 SSH 监听 | +| `RPKI_RTR_TCP_ADDR` | `0.0.0.0:323` | TCP 监听地址 | +| `RPKI_RTR_TLS_ADDR` | `0.0.0.0:324` | TLS 监听地址 | +| `RPKI_RTR_SSH_ADDR` | `0.0.0.0:22` | SSH 监听地址 | +| `RPKI_RTR_SSH_PORT` | 未设置 | 仅覆盖 `RPKI_RTR_SSH_ADDR` 中的端口 | +| `RPKI_RTR_DB_PATH` | `./rtr-db` | RocksDB 路径 | +| `RPKI_RTR_CCR_DIR` | `./data` | CCR 输入目录 | +| `RPKI_RTR_SLURM_DIR` | 未设置 | SLURM 目录;空值表示禁用 | +| `RPKI_RTR_REPORT_DIR` | `./report` | JSON report 输出目录 | +| `RPKI_RTR_MAX_DELTA` | `100` | 每个 RTR 版本保留的最大 delta 数,必须大于 0 | +| `RPKI_RTR_PRUNE_DELTA_BY_SNAPSHOT_SIZE` | `false` | 是否按 snapshot wire size 继续裁剪 delta window | +| `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS` | `300` | source 刷新间隔,单位秒 | +| `RPKI_RTR_REFRESH_INTERVAL_SECS` | 未设置 | 旧变量名,仍兼容;建议使用 `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS` | +| `RPKI_RTR_TIMING_REFRESH_SECS` | `3600` | RTR EndOfData `refresh` 字段 | +| `RPKI_RTR_TIMING_RETRY_SECS` | `600` | RTR EndOfData `retry` 字段 | +| `RPKI_RTR_TIMING_EXPIRE_SECS` | `7200` | RTR EndOfData `expire` 字段,必须大于 refresh 和 retry | +| `RPKI_RTR_MAX_CONNECTIONS` | `512` | 最大并发 RTR 连接数 | +| `RPKI_RTR_MAX_CONCURRENT_HANDSHAKES` | `128` | 最大并发握手数,不能大于最大连接数 | +| `RPKI_RTR_NOTIFY_QUEUE_SIZE` | `1024` | Serial Notify 广播队列大小 | +| `RPKI_RTR_TCP_KEEPALIVE_SECS` | `60` | TCP keepalive;设为 `0` 表示禁用 | +| `RPKI_RTR_WARN_INSECURE_TCP` | `true` | TCP 模式是否输出安全提示 | +| `RPKI_RTR_REQUIRE_TLS_SERVER_DNS_NAME_SAN` | `false` | 是否要求 TLS 服务端证书包含 dNSName SAN | +| `RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH` | `true` | 是否校验 TLS client 证书 SAN IP 与 peer IP 匹配 | +| `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS` | `300` | runtime report 周期 | +| `RPKI_RTR_REPORT_HISTORY_LIMIT` | `10` | 每类 report 滚动保留数量 | +| `RPKI_RTR_TIMEZONE` | `Asia/Shanghai` | 日志和 report 使用的时区 | +| `RPKI_RTR_ADMIN_ADDR` | 未设置 | Admin API 监听地址;未设置时关闭 | +| `RPKI_RTR_ADMIN_TOKEN` | 未设置 | Admin API Bearer token;非 loopback 监听时必须设置 | + +TLS 相关路径: + +| 变量 | 默认值 | +| --- | --- | +| `RPKI_RTR_TLS_CERT_PATH` | `./certs/server.crt` | +| `RPKI_RTR_TLS_KEY_PATH` | `./certs/server.key` | +| `RPKI_RTR_TLS_CLIENT_CA_PATH` | `./certs/client-ca.crt` | + +SSH 相关配置: + +| 变量 | 默认值 | +| --- | --- | +| `RPKI_RTR_SSH_HOST_KEY_PATH` | `./certs/ssh_host_ed25519_key` | +| `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH` | `./certs/rtr-authorized_keys` | +| `RPKI_RTR_SSH_USERNAME` | `rpki-rtr` | +| `RPKI_RTR_SSH_SUBSYSTEM_NAME` | `rpki-rtr` | +| `RPKI_RTR_SSH_AUTH_MODE` | `key` | +| `RPKI_RTR_SSH_PASSWORD` | 未设置 | + +## 运行报告 + +服务会在 `RPKI_RTR_REPORT_DIR` 下写入 JSON report,并按 `RPKI_RTR_REPORT_HISTORY_LIMIT` 滚动保留: + +- `rtr-source-*.json`:CCR/SLURM source、fingerprint、刷新状态、数据质量、cache 统计。 +- `rtr-clients-*.json`:client 连接数和连接方式统计。 +- `rtr-runtime-*.json`:进程状态、服务状态和当前生效的 runtime config。 + +## Admin API + +Admin API 默认关闭。设置 `RPKI_RTR_ADMIN_ADDR` 后启用: ```bash -docker compose -f deploy/client/docker-compose.yml up --build -docker compose -f deploy/client/docker-compose.yml logs -f rtr-debug-client -docker compose -f deploy/client/docker-compose.yml down +export RPKI_RTR_ADMIN_ADDR=127.0.0.1:8323 +export RPKI_RTR_ADMIN_TOKEN=change-me ``` -多实例压测/联调: +如果监听非 loopback 地址,例如 `0.0.0.0:8323`,必须设置 `RPKI_RTR_ADMIN_TOKEN`。设置 token 后,请求需要携带: + +```http +Authorization: Bearer change-me +``` + +当前实现的主要接口: + +| 方法 | 路径 | 说明 | +| --- | --- | --- | +| `GET` | `/admin/rtr/health` | 健康检查和能力开关 | +| `GET` | `/admin/rtr/config` | 查看 runtime config | +| `POST` | `/admin/rtr/config` | 修改部分 runtime config | +| `GET` | `/admin/rtr/logs/tail` | 读取 stdout/stderr 日志 tail | +| `POST` | `/admin/rtr/slurm/reload` | 触发 source reload | +| `GET` | `/admin/rtr/slurm/files` | 列出 SLURM 文件 | +| `POST` | `/admin/rtr/slurm/files` | 新增或覆盖 SLURM 文件 | +| `GET` | `/admin/rtr/slurm/files/{name}` | 读取 SLURM 文件 | +| `PUT` | `/admin/rtr/slurm/files/{name}` | 新增或覆盖指定 SLURM 文件 | +| `DELETE` | `/admin/rtr/slurm/files/{name}` | 删除 SLURM 文件 | +| `POST` | `/admin/rtr/slurm/files/{name}/enable` | 启用 `.slurm.disabled` 文件 | +| `POST` | `/admin/rtr/slurm/files/{name}/disable` | 禁用 `.slurm` 文件 | + +示例: ```bash -docker compose -f deploy/client/docker-compose.clients.yml up -d -docker compose -f deploy/client/docker-compose.clients.yml logs -f -docker compose -f deploy/client/docker-compose.clients.yml down +curl http://127.0.0.1:8323/admin/rtr/config \ + -H "Authorization: Bearer change-me" + +curl -X POST http://127.0.0.1:8323/admin/rtr/config \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer change-me" \ + -d '{"max_delta": 6, "source_refresh_interval_seconds": 60}' ``` -### rpki-rs-test-client(Docker) +## 调试客户端 + +`rtr_debug_client` 用于手动发起 Reset Query 或 Serial Query,并观察服务端返回的 PDU。 + +TCP: ```bash -docker compose -f deploy/rpki-rs-client/docker-compose.yml up --build -docker compose -f deploy/rpki-rs-client/docker-compose.yml run --rm \ - rpki-rs-test-client 127.0.0.1:323 2 reset --steps 1 --assert-min-records 1 -docker compose -f deploy/rpki-rs-client/docker-compose.yml down -``` - -说明: -- 适合自动化场景:可作为流水线中的协议检查器。 -- 适合回归校验:通过参数化步骤(`--steps`)和断言(如 `--assert-min-records`)稳定验证行为。 - -### FRR(Docker,黑盒客户端) - -```bash -docker compose -f deploy/frr/docker-compose.yml up -d -docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection" -docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table" -docker compose -f deploy/frr/docker-compose.yml down -``` - -说明: -- 适合真实互通验证:FRR 更接近生产客户端行为。 -- 适合观察业务结果:可直接查看缓存连接状态和前缀表结果。 - -## RTR Debug Client - -调试用 RTR client 位于: - -- [`src/bin/rtr_debug_client/main.rs`](src/bin/rtr_debug_client/main.rs) - -说明文档位于: - -- [`src/bin/rtr_debug_client/README.md`](src/bin/rtr_debug_client/README.md) - -### Client 启动示例 - -连接纯 TCP RTR server: - -```sh cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 reset +cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 serial 42 100 ``` -连接 TLS RTR server: +TLS: -```sh +```bash cargo run --bin rtr_debug_client -- \ 127.0.0.1:324 1 reset \ --tls \ - --ca-cert ./certs/client-ca.crt \ + --ca-cert tests/fixtures/tls/client-ca.crt \ --server-name localhost \ - --client-cert ./certs/client-good.crt \ - --client-key ./certs/client-good.key + --client-cert tests/fixtures/tls/client-good.crt \ + --client-key tests/fixtures/tls/client-good.key ``` -如果要在收到错误后继续自动轮询,可以加: - -```sh ---keep-after-error -``` - -## 存储模型与边界约束(多视图) - -当前 RTR cache 持久化采用三视图模型(按协议版本拆分): - -- v0:仅 `RouteOrigin` -- v1:`RouteOrigin + RouterKey` -- v2:`RouteOrigin + RouterKey + Aspa` - -RocksDB 中的核心状态按版本独立保存: - -- `session_id_v0/v1/v2` -- `serial_v0/v1/v2` -- `current_v0/v1/v2`(snapshot) -- `delta_min_v0/v1/v2`、`delta_max_v0/v1/v2` -- delta key 采用 `D + version + serial_be` - -### 唯一写入口 - -为保证 `session_id/serial/snapshot/delta_window` 的一致性,生产代码只允许通过: - -- `RtrStore::save_cache_state_versioned(...)` - -且该入口应仅由: - -- `src/rtr/cache/store.rs` - -发起调用(即由 cache 层统一批量原子写入)。 - -### 恢复约束 - -重启恢复时,按版本读取并校验: - -- `availability` -- `snapshot_for_version(version)` -- `session_id_for_version(version)` -- `serial_for_version(version)` -- `delta_window_for_version(version)` 与 `load_delta_window_for_version(...)` - -只有三视图状态完整且自洽时才走 store 恢复;否则回退到输入源加载流程。 - -### 边界防回归测试 - -新增边界测试用于限制写调用点,防止后续出现绕写路径: - -- `tests/test_store_boundary.rs` - -可单独执行: - -```sh -cargo test --test test_store_boundary -- --nocapture -``` - -## Deploy Layout - -`deploy/` 目录按角色拆分为四套部署与测试入口: - -- `server/`: 本仓库 RTR Server(`src/main.rs`)容器化部署 -- `client/`: 本仓库 `rtr_debug_client` 容器化部署 -- `rpki-rs-client/`: 基于外部 `rpki-rs` client API 的测试客户端容器化部署 -- `frr/`: FRR 作为黑盒 RTR Client 的配置与 compose - -### Quick Start - -最短路径(本地容器环境): +SSH: ```bash -docker compose -f deploy/server/docker-compose.yml up -d --build -docker compose -f deploy/server/docker-compose.yml logs -f rpki-rtr -docker compose -f deploy/server/docker-compose.yml down +cargo run --bin rtr_debug_client -- \ + 127.0.0.1:22 1 reset \ + --ssh \ + --ssh-user rpki-rtr \ + --ssh-key certs/rtr-client.key \ + --ssh-server-key certs/ssh_host_rsa_key.pub ``` -### Compose 入口 +SSH password auth 也已实现;SSH 模式下 `--ssh-key` 和 `--ssh-password` 二选一,同时必须通过 `--ssh-known-hosts` 或 `--ssh-server-key` 做 host key 校验。 -| 角色 | Compose 文件 | +更多参数见 `src/bin/rtr_debug_client/README.md`。 + +## 部署入口 + +| 用途 | Compose 文件 | | --- | --- | | Server | `deploy/server/docker-compose.yml` | -| Debug Client(单实例) | `deploy/client/docker-compose.yml` | -| Debug Client(多实例) | `deploy/client/docker-compose.clients.yml` | -| rpki-rs Client | `deploy/rpki-rs-client/docker-compose.yml` | -| FRR Client | `deploy/frr/docker-compose.yml` | +| Server TCP 示例 | `deploy/server/docker-compose.tcp.yml` | +| Server TLS 示例 | `deploy/server/docker-compose.tls.yml` | +| Server SSH 示例 | `deploy/server/docker-compose.ssh.yml` | +| Debug client | `deploy/client/docker-compose.yml` | +| Debug client 多实例 | `deploy/client/docker-compose.clients.yml` | +| FRR client | `deploy/frr/docker-compose.yml` | +| BIRD client | `deploy/bird/docker-compose.yml` | -通用操作模板: +通用操作: ```bash -docker compose -f up -d --build -docker compose -f logs -f -docker compose -f down +COMPOSE_FILE=deploy/server/docker-compose.yml +docker compose -f "${COMPOSE_FILE}" up -d --build +docker compose -f "${COMPOSE_FILE}" ps +docker compose -f "${COMPOSE_FILE}" down ``` -### 角色附加命令 +## 开发说明 -rpki-rs Client(覆盖默认参数运行): - -```bash -docker compose -f deploy/rpki-rs-client/docker-compose.yml run --rm \ - rpki-rs-test-client 127.0.0.1:323 2 reset --steps 1 --assert-min-records 1 -``` - -FRR Client(检查连接与前缀): - -```bash -docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection" -docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table" -``` - -更多部署细节: - -- `deploy/server/DEPLOYMENT.md` -- `deploy/frr/README.md` -- `deploy/frr/README.zh.md` - -## 传输模式说明(TCP / TLS / 原生 SSH) - -当前 `rpki` 进程内支持三种传输: - -- TCP(默认开启) -- TLS(可选开启,mTLS) -- 原生 SSH(可选开启,进程内实现,不再使用外部 `sshd Subsystem` 桥接) - -### TCP 模式 - -默认监听: -- `RPKI_RTR_TCP_ADDR=0.0.0.0:323` - -最小启动(仅 TCP): - -```sh -export RPKI_RTR_ENABLE_TLS=false -export RPKI_RTR_ENABLE_SSH=false -cargo run --bin rpki -``` - -说明: -- 仅建议部署在受信任、可控网络中。 - -### TLS 模式(mTLS) - -相关环境变量: -- `RPKI_RTR_ENABLE_TLS=true` -- `RPKI_RTR_TLS_ADDR`(默认 `0.0.0.0:324`) -- `RPKI_RTR_TLS_CERT_PATH` -- `RPKI_RTR_TLS_KEY_PATH` -- `RPKI_RTR_TLS_CLIENT_CA_PATH` - -最小启动(TCP + TLS): - -```sh -export RPKI_RTR_ENABLE_TLS=true -export RPKI_RTR_ENABLE_SSH=false -export RPKI_RTR_TLS_CERT_PATH=./certs/server-dns.crt -export RPKI_RTR_TLS_KEY_PATH=./certs/server-dns.key -export RPKI_RTR_TLS_CLIENT_CA_PATH=./certs/client-ca.crt -cargo run --bin rpki -``` - -### 原生 SSH 模式(进程内) - -与 `draft-ietf-sidrops-8210bis-25` 对齐要点: -- 使用 SSHv2 -- 使用 subsystem(默认 `rpki-rtr`) -- 使用 public key 认证 -- 服务端拒绝 `none` -- 服务端默认不启用 `password`,配置 `RPKI_RTR_SSH_PASSWORD` 后可选启用(draft 中为 MAY) - -相关环境变量: -- `RPKI_RTR_ENABLE_SSH=true` -- `RPKI_RTR_SSH_ADDR`(默认 `0.0.0.0:22`) -- `RPKI_RTR_SSH_PORT`(默认 `22`,设置后会覆盖 `RPKI_RTR_SSH_ADDR` 的端口) -- `RPKI_RTR_SSH_HOST_KEY_PATH`(OpenSSH host 私钥) -- `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH`(允许接入的 router 公钥列表) -- `RPKI_RTR_SSH_USERNAME`(默认 `rpki-rtr`) -- `RPKI_RTR_SSH_SUBSYSTEM_NAME`(默认 `rpki-rtr`) -- `RPKI_RTR_SSH_PASSWORD`(可选;设置后启用 password 认证) - -密钥准备示例: - -```sh -ssh-keygen -t ed25519 -N '' -f ./certs/ssh_host_ed25519_key -ssh-keygen -t ed25519 -N '' -f ./certs/rtr_client_ed25519_key -cp ./certs/rtr_client_ed25519_key.pub ./certs/rtr-authorized_keys -``` - -最小启动(TCP + SSH): - -```sh -export RPKI_RTR_ENABLE_TLS=false -export RPKI_RTR_ENABLE_SSH=true -export RPKI_RTR_SSH_ADDR=0.0.0.0:22 -# or only override the port: -# export RPKI_RTR_SSH_PORT=2022 -export RPKI_RTR_SSH_HOST_KEY_PATH=./certs/ssh_host_ed25519_key -export RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH=./certs/rtr-authorized_keys -export RPKI_RTR_SSH_USERNAME=rpki-rtr -export RPKI_RTR_SSH_SUBSYSTEM_NAME=rpki-rtr -# 可选:启用 password 认证(同时仍支持 publickey) -# export RPKI_RTR_SSH_PASSWORD=test-password -cargo run --bin rpki -``` - -连通性检查(OpenSSH): - -```sh -ssh -i ./certs/rtr_client_ed25519_key -p 22 -s rpki-rtr@127.0.0.1 rpki-rtr -``` - -说明: -- 该命令主要用于验证 SSH 子系统通道可建立,不等价于完整 RTR 协议回归测试。 +- 代码中的 RTR cache 按协议版本维护状态;v0 只包含 Route Origin,v1 包含 Route Origin 和 Router Key,v2 包含 Route Origin、Router Key 和 ASPA。 +- 当前 source pipeline 从 CCR 生成 Route Origin 和 ASPA,再可选应用 SLURM。 +- `RtrStore::save_cache_state_versioned(...)` 是 cache 状态写入 RocksDB 的核心入口,相关边界测试在 `tests/test_store_boundary.rs`。 +- Admin API 的日志 tail 依赖部署入口写出的 stdout/stderr 日志文件;容器入口脚本会把日志写到 `/app/logs`。 diff --git a/deploy/README.md b/deploy/README.md index 3adfc11..8582e4d 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -4,11 +4,97 @@ - `server/`: 本仓库 RTR Server(`src/main.rs`)容器化部署 - `client/`: 本仓库 `rtr_debug_client` 容器化部署 -- `rpki-rs-client/`: 基于外部 `rpki-rs` client API 的测试客户端容器化部署 - `frr/`: FRR 作为黑盒 RTR Client 的配置与 compose --- +## 0) Build and Run Scripts + +`deploy/server/build-run.sh` 只管理 server,`deploy/client/build-run.sh` 只管理 debug client。两个脚本默认 action 都是 `up`,默认 mode 都是 `tcp`。 + +常用命令: + +以下命令默认从仓库根目录执行。 + +```bash +chmod +x deploy/server/build-run.sh deploy/client/build-run.sh +./deploy/server/build-run.sh up --mode tcp +./deploy/client/build-run.sh up --mode tcp +./deploy/client/build-run.sh up --clients multi +./deploy/client/build-run.sh down +./deploy/server/build-run.sh down +``` + +常用参数: + +| 参数/变量 | server | client | 说明 | +| --- | --- | --- | --- | +| `--mode base\|tcp\|tls\|ssh`, `MODE` | yes | yes | 选择 compose 模式,默认 `tcp`。 | +| `--clients single\|multi`, `CLIENTS` | no | yes | 单 client 或 5 client,默认 `single`。 | +| `--no-build`, `BUILD=0` | yes | yes | 只运行已有镜像,不执行 compose build。 | +| `--server-image TAG`, `SERVER_IMAGE` | yes | no | server 镜像名;会传给 compose 的 `RPKI_RTR_SERVER_IMAGE`。 | +| `--client-image TAG`, `CLIENT_IMAGE` | no | yes | client 镜像名;会传给 compose 的 `RPKI_RTR_CLIENT_IMAGE`。 | +| `--platform VALUE`, `TARGET_PLATFORM` | yes | yes | `buildx` 目标平台,默认 `linux/arm64`。 | +| `--push`, `BUILDX_PUSH=1` | yes | yes | `buildx` 构建后推送到 registry;不设置时加载到本地 Docker。 | +| `--skip-base-pull`, `SKIP_BASE_IMAGE_PULL=1` | yes | yes | 跳过 buildx 前的基础镜像预拉取。 | + +支持的 action 包括 `up`、`build`、`buildx`、`rebuild`、`down`/`stop`、`restart`、`logs`、`ps`。完整帮助见: + +```bash +./deploy/server/build-run.sh --help +./deploy/client/build-run.sh --help +``` + +### x86 Build, ARM Deploy + +x86 开发机上用 Docker Buildx 分别构建 ARM server/client 镜像。不加 `--push` 时,脚本会把单平台镜像加载到本地 Docker,随后可用 `docker save` 打包传到 ARM 机器。buildx 前会自动按目标平台预拉取 `rust:1.89-bookworm` 和 `debian:bookworm-slim`。 + +```bash +docker buildx create --use --name rpki-builder +docker buildx inspect --bootstrap + +./deploy/server/build-run.sh buildx \ + --platform linux/arm64 \ + --server-image rpki-rtr:arm64 + +./deploy/client/build-run.sh buildx \ + --platform linux/arm64 \ + --client-image rpki-rtr-debug-client:arm64 + +mkdir -p dist/images +docker save rpki-rtr:arm64 -o dist/images/rpki-rtr-arm64.tar +docker save rpki-rtr-debug-client:arm64 -o dist/images/rpki-rtr-debug-client-arm64.tar +``` + +把 tar 包传到 ARM 部署机: + +```bash +scp dist/images/*.tar user@arm-host:/tmp/ +``` + +ARM 部署机上加载镜像并运行,不再本地构建: + +```bash +docker load -i /tmp/rpki-rtr-arm64.tar +docker load -i /tmp/rpki-rtr-debug-client-arm64.tar + +SERVER_IMAGE=rpki-rtr:arm64 \ +BUILD=0 ./deploy/server/build-run.sh up --mode tcp + +CLIENT_IMAGE=rpki-rtr-debug-client:arm64 \ +BUILD=0 ./deploy/client/build-run.sh up --mode tcp +``` + +如果不使用脚本,也可以通过 compose 环境变量指定镜像: + +```bash +export RPKI_RTR_SERVER_IMAGE=rpki-rtr:arm64 +docker compose --env-file deploy/server/.env \ + -f deploy/server/docker-compose.tcp.yml up -d --no-build +``` + +--- + ## 1) Server 路径: @@ -117,46 +203,7 @@ docker compose -f deploy/client/docker-compose.clients.yml logs -f --- -## 3) rpki-rs Client - -路径: -- `deploy/rpki-rs-client/Dockerfile` -- `deploy/rpki-rs-client/docker-compose.yml` - -单独 build 镜像: - -```bash -docker build -f deploy/rpki-rs-client/Dockerfile -t rpki-rs-test-client:latest . -``` - -默认启动(自动 serial 测试): - -```bash -docker compose -f deploy/rpki-rs-client/docker-compose.yml up --build -``` - -覆盖默认参数运行: - -```bash -docker compose -f deploy/rpki-rs-client/docker-compose.yml run --rm \ - rpki-rs-test-client 127.0.0.1:323 2 reset --steps 1 --assert-min-records 1 -``` - -停止: - -```bash -docker compose -f deploy/rpki-rs-client/docker-compose.yml down -``` - -日志: - -```bash -docker compose -f deploy/rpki-rs-client/docker-compose.yml logs -f rpki-rs-test-client -``` - ---- - -## 4) FRR Client +## 3) FRR Client 路径: - `deploy/frr/docker-compose.yml` @@ -192,7 +239,7 @@ docker compose -f deploy/frr/docker-compose.yml logs -f frr-rpki-client --- -## 5) BIRD Client +## 4) BIRD Client 路径: - `deploy/bird/Dockerfile` diff --git a/deploy/client/build-run.sh b/deploy/client/build-run.sh new file mode 100644 index 0000000..b2a46fd --- /dev/null +++ b/deploy/client/build-run.sh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)" + +MODE="${MODE:-tcp}" +ACTION="up" +DETACH="${DETACH:-1}" +BUILD="${BUILD:-1}" +CLIENTS="${CLIENTS:-single}" +TARGET_PLATFORM="${TARGET_PLATFORM:-linux/arm64}" +CLIENT_IMAGE="${CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest}}" +BUILDX_PUSH="${BUILDX_PUSH:-0}" +BUILDX_BUILDER="${BUILDX_BUILDER:-}" +SKIP_BASE_IMAGE_PULL="${SKIP_BASE_IMAGE_PULL:-0}" + +usage() { + cat <<'EOF' +Usage: + deploy/client/build-run.sh [action] [options] + +Actions: + up Build and run debug client (default) + build Build debug client image + buildx Build debug client image with docker buildx + rebuild Rebuild image without cache, then run client + down|stop Stop client compose stack + restart Stop, then build and run client + logs Follow client logs + ps Show client compose status + +Options: + --mode MODE base|tcp|tls|ssh (default: tcp) + --clients MODE single|multi (default: single) + --platform VALUE Target platform for buildx (default: linux/arm64) + --client-image TAG + Client image tag for buildx and compose + --push Push buildx image instead of loading it locally + --builder NAME buildx builder name + --skip-base-pull Do not pull Dockerfile base images before buildx + --foreground Run single client in foreground + --no-build Start without --build + -h|--help Show this help +EOF +} + +die() { + echo "error: $*" >&2 + exit 1 +} + +has_compose() { + docker compose version >/dev/null 2>&1 +} + +has_buildx() { + docker buildx version >/dev/null 2>&1 +} + +pull_base_images() { + [[ "${SKIP_BASE_IMAGE_PULL}" == "1" ]] && return + + local platform image + local -a platforms images + IFS=',' read -r -a platforms <<< "${TARGET_PLATFORM}" + images=(rust:1.89-bookworm debian:bookworm-slim) + + for platform in "${platforms[@]}"; do + for image in "${images[@]}"; do + docker pull --platform "${platform}" "${image}" + done + done +} + +compose_files() { + if [[ "${CLIENTS}" == "multi" ]]; then + [[ "${MODE}" == "tcp" || "${MODE}" == "base" ]] || die "--clients multi is only defined for tcp/base mode" + printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.clients.yml" + return + fi + + case "${MODE}" in + base) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.yml" ;; + tcp) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tcp.yml" ;; + tls) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tls.yml" ;; + ssh) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.ssh.yml" ;; + *) die "unsupported mode '${MODE}', expected base|tcp|tls|ssh" ;; + esac +} + +compose_args() { + local -a args files + args=() + [[ -f "${SCRIPT_DIR}/.env" ]] && args+=(--env-file "${SCRIPT_DIR}/.env") + mapfile -t files < <(compose_files) + args+=("${files[@]}") + printf '%s\n' "${args[@]}" +} + +compose_run() { + local -a args + mapfile -t args < <(compose_args) + docker compose "${args[@]}" "$@" +} + +up_client() { + local -a up_args + up_args=(up) + [[ "${CLIENTS}" == "multi" || "${DETACH}" == "1" ]] && up_args+=(-d) + [[ "${BUILD}" == "1" && "${CLIENTS}" != "multi" ]] && up_args+=(--build) + compose_run "${up_args[@]}" +} + +build_client() { + if [[ "${CLIENTS}" == "multi" ]]; then + docker build "$@" -f "${SCRIPT_DIR}/Dockerfile" -t "${CLIENT_IMAGE}" "${REPO_ROOT}" + else + compose_run build "$@" + fi +} + +buildx_client() { + has_buildx || die "docker buildx is not available" + if [[ "${TARGET_PLATFORM}" == *,* && "${BUILDX_PUSH}" != "1" ]]; then + die "multi-platform buildx output requires --push" + fi + + local -a output_args builder_args + [[ "${BUILDX_PUSH}" == "1" ]] && output_args=(--push) || output_args=(--load) + [[ -n "${BUILDX_BUILDER}" ]] && builder_args=(--builder "${BUILDX_BUILDER}") || builder_args=() + + pull_base_images + + docker buildx build "${builder_args[@]}" \ + --platform "${TARGET_PLATFORM}" \ + -f "${SCRIPT_DIR}/Dockerfile" \ + -t "${CLIENT_IMAGE}" \ + "${output_args[@]}" \ + "${REPO_ROOT}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + up|build|buildx|rebuild|down|stop|restart|logs|ps) + ACTION="$1" + shift + ;; + --mode) + [[ $# -ge 2 ]] || die "--mode requires a value" + MODE="$2" + shift 2 + ;; + --clients) + [[ $# -ge 2 ]] || die "--clients requires a value" + CLIENTS="$2" + shift 2 + ;; + --platform) + [[ $# -ge 2 ]] || die "--platform requires a value" + TARGET_PLATFORM="$2" + shift 2 + ;; + --client-image) + [[ $# -ge 2 ]] || die "--client-image requires a value" + CLIENT_IMAGE="$2" + shift 2 + ;; + --push) + BUILDX_PUSH=1 + shift + ;; + --builder) + [[ $# -ge 2 ]] || die "--builder requires a value" + BUILDX_BUILDER="$2" + shift 2 + ;; + --skip-base-pull) + SKIP_BASE_IMAGE_PULL=1 + shift + ;; + --foreground) + DETACH=0 + shift + ;; + --no-build) + BUILD=0 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown argument '$1'" + ;; + esac +done + +[[ "${CLIENTS}" == "single" || "${CLIENTS}" == "multi" ]] || die "unsupported clients '${CLIENTS}', expected single|multi" +export RPKI_RTR_CLIENT_IMAGE="${CLIENT_IMAGE}" +if [[ "${ACTION}" == "buildx" ]]; then + has_buildx || die "docker buildx is not available" +else + has_compose || die "docker compose is not available" +fi + +cd "${REPO_ROOT}" + +case "${ACTION}" in + up) + up_client + ;; + build) + build_client + ;; + buildx) + buildx_client + ;; + rebuild) + build_client --no-cache + up_client + ;; + down|stop) + compose_run down + ;; + restart) + compose_run down + up_client + ;; + logs) + compose_run logs -f + ;; + ps) + compose_run ps + ;; +esac diff --git a/deploy/client/docker-compose.clients.yml b/deploy/client/docker-compose.clients.yml index 51bb780..b7aba96 100644 --- a/deploy/client/docker-compose.clients.yml +++ b/deploy/client/docker-compose.clients.yml @@ -2,7 +2,7 @@ version: "3.9" services: rtr-client-1: - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: ["${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tcp:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"] volumes: - ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs @@ -11,7 +11,7 @@ services: - rpki_net rtr-client-2: - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: ["${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tcp:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"] volumes: - ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs @@ -20,7 +20,7 @@ services: - rpki_net rtr-client-3: - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: ["${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tcp:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"] volumes: - ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs @@ -29,7 +29,7 @@ services: - rpki_net rtr-client-4: - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: ["${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tcp:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"] volumes: - ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs @@ -38,7 +38,7 @@ services: - rpki_net rtr-client-5: - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: ["${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tcp:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"] volumes: - ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs diff --git a/deploy/client/docker-compose.ssh.password.yml b/deploy/client/docker-compose.ssh.password.yml index ecf809e..0d4bc05 100644 --- a/deploy/client/docker-compose.ssh.password.yml +++ b/deploy/client/docker-compose.ssh.password.yml @@ -5,7 +5,7 @@ services: build: context: ../.. dockerfile: deploy/client/Dockerfile - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: [ "${RPKI_RTR_SERVER_ADDR:-rpki-rtr-ssh:22}", diff --git a/deploy/client/docker-compose.ssh.yml b/deploy/client/docker-compose.ssh.yml index 24d1fda..3b477e5 100644 --- a/deploy/client/docker-compose.ssh.yml +++ b/deploy/client/docker-compose.ssh.yml @@ -5,7 +5,7 @@ services: build: context: ../.. dockerfile: deploy/client/Dockerfile - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: [ "${RPKI_RTR_SERVER_ADDR:-rpki-rtr-ssh:22}", diff --git a/deploy/client/docker-compose.tcp.yml b/deploy/client/docker-compose.tcp.yml index dd24dec..3d3cb86 100644 --- a/deploy/client/docker-compose.tcp.yml +++ b/deploy/client/docker-compose.tcp.yml @@ -3,7 +3,7 @@ services: build: context: ../.. dockerfile: deploy/client/Dockerfile - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: ["${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tcp:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"] volumes: - ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs diff --git a/deploy/client/docker-compose.tls.yml b/deploy/client/docker-compose.tls.yml index cc49cde..4c88d83 100644 --- a/deploy/client/docker-compose.tls.yml +++ b/deploy/client/docker-compose.tls.yml @@ -5,7 +5,7 @@ services: build: context: ../.. dockerfile: deploy/client/Dockerfile - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: [ "${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tls:324}", diff --git a/deploy/client/docker-compose.yml b/deploy/client/docker-compose.yml index dd24dec..3d3cb86 100644 --- a/deploy/client/docker-compose.yml +++ b/deploy/client/docker-compose.yml @@ -3,7 +3,7 @@ services: build: context: ../.. dockerfile: deploy/client/Dockerfile - image: rpki-rtr-debug-client:latest + image: ${RPKI_RTR_CLIENT_IMAGE:-rpki-rtr-debug-client:latest} command: ["${RPKI_RTR_SERVER_ADDR:-rpki-rtr-tcp:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"] volumes: - ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs diff --git a/deploy/server/.env b/deploy/server/.env index 516bbb9..577c017 100644 --- a/deploy/server/.env +++ b/deploy/server/.env @@ -16,7 +16,6 @@ RPKI_RTR_DB_PATH=/app/rtr-db RPKI_RTR_REPORT_DIR=/app/report # Core runtime knobs. -RPKI_RTR_STRICT_CCR_VALIDATION=false RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS=300 RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS=300 RPKI_RTR_REPORT_HISTORY_LIMIT=10 diff --git a/deploy/server/DEPLOYMENT.md b/deploy/server/DEPLOYMENT.md index be83ca1..ae13258 100644 --- a/deploy/server/DEPLOYMENT.md +++ b/deploy/server/DEPLOYMENT.md @@ -35,7 +35,7 @@ The container runs `rpki` directly as PID 1. ## Runtime Configuration via `.env` -- Core: `RPKI_RTR_STRICT_CCR_VALIDATION`, `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS`, `RPKI_RTR_MAX_DELTA`, `RPKI_RTR_MAX_CONCURRENT_HANDSHAKES`, `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS`, `RPKI_RTR_REPORT_HISTORY_LIMIT`, `RPKI_RTR_TIMEZONE`, `RPKI_RTR_ADMIN_ADDR`, `RPKI_RTR_ADMIN_TOKEN`, `RUST_LOG` +- Core: `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS`, `RPKI_RTR_MAX_DELTA`, `RPKI_RTR_MAX_CONCURRENT_HANDSHAKES`, `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS`, `RPKI_RTR_REPORT_HISTORY_LIMIT`, `RPKI_RTR_TIMEZONE`, `RPKI_RTR_ADMIN_ADDR`, `RPKI_RTR_ADMIN_TOKEN`, `RUST_LOG` - TCP mode: `RPKI_RTR_MAX_CONNECTIONS` - TLS mode: `RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH`, `RPKI_RTR_TLS_CERT_PATH`, `RPKI_RTR_TLS_KEY_PATH`, `RPKI_RTR_TLS_CLIENT_CA_PATH`, `RPKI_RTR_TLS_CERTS_HOST_DIR` - SSH mode: `RPKI_RTR_SSH_HOST_PORT`, `RPKI_RTR_SSH_CONTAINER_PORT`, `RPKI_RTR_SSH_AUTH_MODE`, `RPKI_RTR_SSH_USERNAME`, `RPKI_RTR_SSH_SUBSYSTEM_NAME`, `RPKI_RTR_SSH_HOST_KEY_PATH`, `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH`, `RPKI_RTR_SSH_KEYS_VOLUME`, `RPKI_RTR_SSH_CERTS_HOST_DIR` diff --git a/deploy/server/Dockerfile b/deploy/server/Dockerfile index 565b2bc..066dd07 100644 --- a/deploy/server/Dockerfile +++ b/deploy/server/Dockerfile @@ -2,6 +2,8 @@ FROM rust:1.89-bookworm AS builder WORKDIR /build +ENV CARGO_BUILD_JOBS=2 + RUN set -eux; \ cat > /etc/apt/sources.list.d/debian.sources <<'EOF' Types: deb @@ -75,7 +77,6 @@ ENV RPKI_RTR_ENABLE_TLS=false \ RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS=300 \ RPKI_RTR_REPORT_HISTORY_LIMIT=10 \ RPKI_RTR_REFRESH_INTERVAL_SECS=300 \ - RPKI_RTR_STRICT_CCR_VALIDATION=false \ RPKI_RTR_ADMIN_ADDR="" \ RPKI_RTR_ADMIN_TOKEN="" diff --git a/deploy/server/build-run.sh b/deploy/server/build-run.sh new file mode 100644 index 0000000..d3e7437 --- /dev/null +++ b/deploy/server/build-run.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)" + +MODE="${MODE:-tcp}" +ACTION="up" +DETACH="${DETACH:-1}" +BUILD="${BUILD:-1}" +TARGET_PLATFORM="${TARGET_PLATFORM:-linux/arm64}" +SERVER_IMAGE="${SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE:-rpki-rtr:latest}}" +BUILDX_PUSH="${BUILDX_PUSH:-0}" +BUILDX_BUILDER="${BUILDX_BUILDER:-}" +SKIP_BASE_IMAGE_PULL="${SKIP_BASE_IMAGE_PULL:-0}" + +usage() { + cat <<'EOF' +Usage: + deploy/server/build-run.sh [action] [options] + +Actions: + up Build and start server (default) + build Build server image with docker compose + buildx Build server image with docker buildx + rebuild Rebuild image without cache, then start server + down|stop Stop server compose stack + restart Stop, then build and start server + logs Follow server logs + ps Show server compose status + +Options: + --mode MODE base|tcp|tls|ssh (default: tcp) + --platform VALUE Target platform for buildx (default: linux/arm64) + --server-image TAG + Server image tag for buildx and compose + --push Push buildx image instead of loading it locally + --builder NAME buildx builder name + --skip-base-pull Do not pull Dockerfile base images before buildx + --foreground Run server in foreground + --no-build Start without --build + -h|--help Show this help +EOF +} + +die() { + echo "error: $*" >&2 + exit 1 +} + +has_compose() { + docker compose version >/dev/null 2>&1 +} + +has_buildx() { + docker buildx version >/dev/null 2>&1 +} + +pull_base_images() { + [[ "${SKIP_BASE_IMAGE_PULL}" == "1" ]] && return + + local platform image + local -a platforms images + IFS=',' read -r -a platforms <<< "${TARGET_PLATFORM}" + images=(rust:1.89-bookworm debian:bookworm-slim) + + for platform in "${platforms[@]}"; do + for image in "${images[@]}"; do + docker pull --platform "${platform}" "${image}" + done + done +} + +compose_files() { + case "${MODE}" in + base) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.yml" ;; + tcp) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tcp.yml" ;; + tls) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tls.yml" ;; + ssh) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.ssh.yml" ;; + *) die "unsupported mode '${MODE}', expected base|tcp|tls|ssh" ;; + esac +} + +compose_args() { + local -a args files + args=() + [[ -f "${SCRIPT_DIR}/.env" ]] && args+=(--env-file "${SCRIPT_DIR}/.env") + mapfile -t files < <(compose_files) + args+=("${files[@]}") + printf '%s\n' "${args[@]}" +} + +compose_run() { + local -a args + mapfile -t args < <(compose_args) + docker compose "${args[@]}" "$@" +} + +up_server() { + local -a up_args + up_args=(up) + [[ "${DETACH}" == "1" ]] && up_args+=(-d) + [[ "${BUILD}" == "1" ]] && up_args+=(--build) + compose_run "${up_args[@]}" +} + +buildx_server() { + has_buildx || die "docker buildx is not available" + if [[ "${TARGET_PLATFORM}" == *,* && "${BUILDX_PUSH}" != "1" ]]; then + die "multi-platform buildx output requires --push" + fi + + local -a output_args builder_args + [[ "${BUILDX_PUSH}" == "1" ]] && output_args=(--push) || output_args=(--load) + [[ -n "${BUILDX_BUILDER}" ]] && builder_args=(--builder "${BUILDX_BUILDER}") || builder_args=() + + pull_base_images + + docker buildx build "${builder_args[@]}" \ + --platform "${TARGET_PLATFORM}" \ + -f "${SCRIPT_DIR}/Dockerfile" \ + -t "${SERVER_IMAGE}" \ + "${output_args[@]}" \ + "${REPO_ROOT}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + up|build|buildx|rebuild|down|stop|restart|logs|ps) + ACTION="$1" + shift + ;; + --mode) + [[ $# -ge 2 ]] || die "--mode requires a value" + MODE="$2" + shift 2 + ;; + --platform) + [[ $# -ge 2 ]] || die "--platform requires a value" + TARGET_PLATFORM="$2" + shift 2 + ;; + --server-image) + [[ $# -ge 2 ]] || die "--server-image requires a value" + SERVER_IMAGE="$2" + shift 2 + ;; + --push) + BUILDX_PUSH=1 + shift + ;; + --builder) + [[ $# -ge 2 ]] || die "--builder requires a value" + BUILDX_BUILDER="$2" + shift 2 + ;; + --skip-base-pull) + SKIP_BASE_IMAGE_PULL=1 + shift + ;; + --foreground) + DETACH=0 + shift + ;; + --no-build) + BUILD=0 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "unknown argument '$1'" + ;; + esac +done + +export RPKI_RTR_SERVER_IMAGE="${SERVER_IMAGE}" +if [[ "${ACTION}" == "buildx" ]]; then + has_buildx || die "docker buildx is not available" +else + has_compose || die "docker compose is not available" +fi + +cd "${REPO_ROOT}" + +case "${ACTION}" in + up) + up_server + ;; + build) + compose_run build + ;; + buildx) + buildx_server + ;; + rebuild) + compose_run build --no-cache + up_server + ;; + down|stop) + compose_run down + ;; + restart) + compose_run down + up_server + ;; + logs) + compose_run logs -f + ;; + ps) + compose_run ps + ;; +esac diff --git a/deploy/server/docker-compose.ssh.yml b/deploy/server/docker-compose.ssh.yml index c156c79..8258673 100644 --- a/deploy/server/docker-compose.ssh.yml +++ b/deploy/server/docker-compose.ssh.yml @@ -5,7 +5,7 @@ services: build: context: ../.. dockerfile: deploy/server/Dockerfile - image: rpki-rtr:latest + image: ${RPKI_RTR_SERVER_IMAGE:-rpki-rtr:latest} container_name: rpki-rtr-ssh restart: no ports: @@ -32,7 +32,6 @@ services: RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}" RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}" RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}" - RPKI_RTR_STRICT_CCR_VALIDATION: "${RPKI_RTR_STRICT_CCR_VALIDATION:-false}" RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}" RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}" RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}" diff --git a/deploy/server/docker-compose.tcp.yml b/deploy/server/docker-compose.tcp.yml index a407873..f4c081e 100644 --- a/deploy/server/docker-compose.tcp.yml +++ b/deploy/server/docker-compose.tcp.yml @@ -5,7 +5,7 @@ services: build: context: ../.. dockerfile: deploy/server/Dockerfile - image: rpki-rtr:latest + image: ${RPKI_RTR_SERVER_IMAGE:-rpki-rtr:latest} container_name: rpki-rtr-tcp restart: no ports: @@ -22,7 +22,6 @@ services: RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}" RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}" RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}" - RPKI_RTR_STRICT_CCR_VALIDATION: "${RPKI_RTR_STRICT_CCR_VALIDATION:-false}" RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-60}" RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}" RPKI_RTR_MAX_CONNECTIONS: "${RPKI_RTR_MAX_CONNECTIONS:-100000}" diff --git a/deploy/server/docker-compose.tls.yml b/deploy/server/docker-compose.tls.yml index 56063e3..690bcd5 100644 --- a/deploy/server/docker-compose.tls.yml +++ b/deploy/server/docker-compose.tls.yml @@ -3,7 +3,7 @@ services: build: context: ../.. dockerfile: deploy/server/Dockerfile - image: rpki-rtr:latest + image: ${RPKI_RTR_SERVER_IMAGE:-rpki-rtr:latest} container_name: rpki-rtr-tls restart: no ports: @@ -26,7 +26,6 @@ services: RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}" RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}" RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}" - RPKI_RTR_STRICT_CCR_VALIDATION: "${RPKI_RTR_STRICT_CCR_VALIDATION:-false}" RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}" RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}" RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}" diff --git a/deploy/server/docker-compose.yml b/deploy/server/docker-compose.yml index f8fa6e5..be60aa9 100644 --- a/deploy/server/docker-compose.yml +++ b/deploy/server/docker-compose.yml @@ -5,7 +5,7 @@ services: build: context: ../.. dockerfile: deploy/server/Dockerfile - image: rpki-rtr:latest + image: ${RPKI_RTR_SERVER_IMAGE:-rpki-rtr:latest} container_name: rpki-rtr restart: no ports: @@ -25,7 +25,6 @@ services: RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}" RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}" RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}" - RPKI_RTR_STRICT_CCR_VALIDATION: "${RPKI_RTR_STRICT_CCR_VALIDATION:-false}" RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}" RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}" RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}" diff --git a/docs/rtr-admin-api.md b/docs/rtr-admin-api.md index 56079c4..317ce0e 100644 --- a/docs/rtr-admin-api.md +++ b/docs/rtr-admin-api.md @@ -105,7 +105,6 @@ curl http://127.0.0.1:8323/admin/rtr/config \ "source_refresh_interval_seconds": 300, "runtime_report_interval_seconds": 300, "report_history_limit": 10, - "strict_ccr_validation": false, "timezone": "Asia/Shanghai", "timing": { "refresh": 3600, @@ -127,7 +126,6 @@ curl http://127.0.0.1:8323/admin/rtr/config \ | `source_refresh_interval_seconds` | integer | CCR/SLURM source refresh 间隔,单位秒,必须 `>= 1`。 | | `runtime_report_interval_seconds` | integer | `rtr-runtime-*.json` 周期写入间隔,单位秒,必须 `>= 1`。 | | `report_history_limit` | integer | 每类 report 文件滚动保留数量,必须 `>= 1`。 | -| `strict_ccr_validation` | boolean | 是否严格校验 CCR 中的非法 VRP/VAP,影响下一次 source refresh。 | | `timezone` | string | IANA 时区名,例如 `Asia/Shanghai`、`UTC`、`Europe/London`。 | | `timing.refresh` | integer | RTR EndOfData `refresh`,单位秒。 | | `timing.retry` | integer | RTR EndOfData `retry`,单位秒。 | @@ -153,7 +151,6 @@ curl -X POST http://127.0.0.1:8323/admin/rtr/config \ "source_refresh_interval_seconds": 300, "runtime_report_interval_seconds": 300, "report_history_limit": 10, - "strict_ccr_validation": false, "timezone": "Asia/Shanghai", "timing": { "refresh": 3600, diff --git a/scripts/start-rtr-server-tcp.sh b/scripts/start-rtr-server-tcp.sh index eb6aebd..e24fa01 100644 --- a/scripts/start-rtr-server-tcp.sh +++ b/scripts/start-rtr-server-tcp.sh @@ -12,7 +12,6 @@ export RPKI_RTR_DB_PATH export RPKI_RTR_CCR_DIR : "${RPKI_RTR_MAX_DELTA:=100}" -: "${RPKI_RTR_STRICT_CCR_VALIDATION:=false}" : "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:=300}" : "${RPKI_RTR_TIMING_REFRESH_SECS:=3600}" : "${RPKI_RTR_TIMING_RETRY_SECS:=600}" @@ -20,7 +19,6 @@ export RPKI_RTR_CCR_DIR : "${RPKI_RTR_MAX_CONNECTIONS:=512}" : "${RPKI_RTR_NOTIFY_QUEUE_SIZE:=1024}" export RPKI_RTR_MAX_DELTA -export RPKI_RTR_STRICT_CCR_VALIDATION export RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS export RPKI_RTR_TIMING_REFRESH_SECS export RPKI_RTR_TIMING_RETRY_SECS diff --git a/scripts/start-rtr-server-tls.sh b/scripts/start-rtr-server-tls.sh index 5f47995..1946de9 100644 --- a/scripts/start-rtr-server-tls.sh +++ b/scripts/start-rtr-server-tls.sh @@ -21,7 +21,6 @@ export RPKI_RTR_TLS_KEY_PATH export RPKI_RTR_TLS_CLIENT_CA_PATH : "${RPKI_RTR_MAX_DELTA:=100}" -: "${RPKI_RTR_STRICT_CCR_VALIDATION:=false}" : "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:=300}" : "${RPKI_RTR_TIMING_REFRESH_SECS:=3600}" : "${RPKI_RTR_TIMING_RETRY_SECS:=600}" @@ -29,7 +28,6 @@ export RPKI_RTR_TLS_CLIENT_CA_PATH : "${RPKI_RTR_MAX_CONNECTIONS:=512}" : "${RPKI_RTR_NOTIFY_QUEUE_SIZE:=1024}" export RPKI_RTR_MAX_DELTA -export RPKI_RTR_STRICT_CCR_VALIDATION export RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS export RPKI_RTR_TIMING_REFRESH_SECS export RPKI_RTR_TIMING_RETRY_SECS diff --git a/scripts/start-rtr-server.sh b/scripts/start-rtr-server.sh index 5940eba..1946de9 100644 --- a/scripts/start-rtr-server.sh +++ b/scripts/start-rtr-server.sh @@ -10,10 +10,8 @@ export RPKI_RTR_TLS_ADDR : "${RPKI_RTR_DB_PATH:=./rtr-db}" : "${RPKI_RTR_CCR_DIR:=./data}" -: "${RPKI_RTR_STRICT_CCR_VALIDATION:=false}" export RPKI_RTR_DB_PATH export RPKI_RTR_CCR_DIR -export RPKI_RTR_STRICT_CCR_VALIDATION : "${RPKI_RTR_TLS_CERT_PATH:=./certs/server-dns.crt}" : "${RPKI_RTR_TLS_KEY_PATH:=./certs/server-dns.key}" diff --git a/src/bin/slurm_apply_client.rs b/src/bin/slurm_apply_client.rs index eabc9c6..eb46abb 100644 --- a/src/bin/slurm_apply_client.rs +++ b/src/bin/slurm_apply_client.rs @@ -15,7 +15,6 @@ use rpki::source::ccr::{ struct Cli { ccr_path: PathBuf, slurm_path: PathBuf, - strict_ccr: bool, dump_payloads: bool, } @@ -44,7 +43,7 @@ fn main() -> Result<()> { .with_context(|| format!("failed to load CCR snapshot: {}", cli.ccr_path.display()))?; let slurm = load_slurm(&cli.slurm_path)?; - let conversion = snapshot_to_payloads_with_options(&snapshot, cli.strict_ccr)?; + let conversion = snapshot_to_payloads_with_options(&snapshot, true)?; let payloads = slurm.apply(&conversion.payloads); let (input_vrp_count, input_vap_count) = count_vrps_and_vaps(&conversion.payloads); let (output_vrp_count, output_vap_count) = count_vrps_and_vaps(&payloads); @@ -78,13 +77,11 @@ fn load_slurm(path: &PathBuf) -> Result { } fn parse_args(args: impl Iterator) -> Result { - let mut strict_ccr = false; let mut dump_payloads = false; let mut positionals = Vec::new(); for arg in args { match arg.as_str() { - "--strict-ccr" => strict_ccr = true, "--dump-payloads" => dump_payloads = true, "-h" | "--help" => { print_help(); @@ -102,7 +99,6 @@ fn parse_args(args: impl Iterator) -> Result { ccr_path: find_latest_ccr_file("data") .context("failed to find latest .ccr in ./data for default run")?, slurm_path: PathBuf::from("data/example.slurm"), - strict_ccr, dump_payloads, }); } @@ -117,14 +113,13 @@ fn parse_args(args: impl Iterator) -> Result { Ok(Cli { ccr_path: PathBuf::from(&positionals[0]), slurm_path: PathBuf::from(&positionals[1]), - strict_ccr, dump_payloads, }) } fn print_help() { eprintln!( - "Usage: cargo run --bin slurm_apply_client -- [--strict-ccr] [--dump-payloads] " + "Usage: cargo run --bin slurm_apply_client -- [--dump-payloads] " ); eprintln!(); eprintln!("Reads a CCR snapshot, converts it into payloads, applies SLURM, and prints JSON."); diff --git a/src/main_rtr.rs b/src/main_rtr.rs index 4da7593..6855cdf 100644 --- a/src/main_rtr.rs +++ b/src/main_rtr.rs @@ -1,28 +1,17 @@ -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Instant; - use anyhow::Result; -use arc_swap::ArcSwap; use chrono::Utc; use tokio::sync::mpsc; -use tokio::task::JoinHandle; -use tracing::{info, warn}; +use tracing::info; use rpki::rtr::admin::{ - AdminState, LogTailConfig, RuntimeConfigHandle, SourceReloadHandle, SourceReloadResult, - spawn_admin_config_server, + AdminState, LogTailConfig, RuntimeConfigHandle, SourceReloadHandle, spawn_admin_config_server, }; -use rpki::rtr::cache::{RtrCache, SharedRtrCache, Snapshot}; -use rpki::rtr::config::{AppConfig, RuntimeConfig, log_startup_config}; -use rpki::rtr::report::{ReportConfiguration, ReportContext, current_rss_mib}; -use rpki::rtr::server::{RtrNotifier, RtrService, RtrServiceStats, RunningRtrService}; -use rpki::rtr::store::RtrStore; +use rpki::rtr::bootstrap::{init_shared_cache, open_store, start_servers}; +use rpki::rtr::config::{AppConfig, log_startup_config}; +use rpki::rtr::report::{ReportConfiguration, ReportContext}; +use rpki::rtr::runtime::spawn_refresh_task; +use rpki::rtr::server::RtrService; use rpki::slurm::admin::SlurmAdmin; -use rpki::source::pipeline::{ - PayloadLoadConfig, SourceFingerprint, latest_sources_fingerprint, - load_payloads_from_latest_sources_with_report, -}; #[tokio::main] async fn main() -> Result<()> { @@ -36,7 +25,6 @@ async fn main() -> Result<()> { config.report_history_limit, config.max_delta, config.prune_delta_by_snapshot_size, - config.strict_ccr_validation, config.timezone, ( config.timing.refresh, @@ -92,488 +80,6 @@ async fn main() -> Result<()> { Ok(()) } -fn open_store(config: &AppConfig) -> Result { - info!("opening RTR store: {}", config.db_path); - RtrStore::open(&config.db_path) -} - -fn init_shared_cache( - config: &AppConfig, - store: &RtrStore, - report_context: &ReportContext, -) -> Result { - let payload_load_config = PayloadLoadConfig { - ccr_dir: config.ccr_dir.clone(), - slurm_dir: config.slurm_dir.clone(), - strict_ccr_validation: config.strict_ccr_validation, - }; - let source_to_delta_started = Instant::now(); - let report_context_for_loader = report_context.clone(); - let initial_cache = RtrCache::default().init( - store, - config.max_delta, - config.prune_delta_by_snapshot_size, - config.timing, - || { - let load = load_payloads_from_latest_sources_with_report(&payload_load_config)?; - info!( - "RTR source-to-delta timing: phase=startup_load_complete, ccr_dir={}, payload_count={}, elapsed_ms={}", - payload_load_config.ccr_dir, - load.payloads.len(), - source_to_delta_started.elapsed().as_millis() - ); - report_context_for_loader.record_refresh_success( - Utc::now(), - source_to_delta_started.elapsed().as_millis(), - true, - load.source, - load.quality, - ); - Ok(load.payloads) - }, - )?; - info!( - "RTR source-to-delta timing: phase=startup_cache_init_complete, ccr_dir={}, serials={:?}, elapsed_ms={}", - payload_load_config.ccr_dir, - initial_cache.serials(), - source_to_delta_started.elapsed().as_millis() - ); - - let shared_cache: SharedRtrCache = Arc::new(ArcSwap::from_pointee(initial_cache)); - let cache = shared_cache.load_full(); - info!( - "cache initialized: session_ids={:?}, serials={:?}", - cache.session_ids(), - cache.serials() - ); - - Ok(shared_cache) -} - -fn start_servers(config: &AppConfig, service: &RtrService) -> RunningRtrService { - if config.enable_tls && config.enable_ssh { - info!("starting TCP, TLS and SSH RTR servers"); - service.spawn_tcp_tls_and_ssh_from_pem_and_openssh( - config.tcp_addr, - config.tls_addr, - config.ssh_addr, - &config.tls_cert_path, - &config.tls_key_path, - &config.tls_client_ca_path, - &config.ssh_host_key_path, - &config.ssh_authorized_keys_path, - &config.ssh_username, - &config.ssh_subsystem_name, - config.ssh_auth_mode, - config.ssh_password.as_deref(), - ) - } else if config.enable_tls { - info!("starting TCP and TLS RTR servers"); - service.spawn_tcp_and_tls_from_pem( - config.tcp_addr, - config.tls_addr, - &config.tls_cert_path, - &config.tls_key_path, - &config.tls_client_ca_path, - ) - } else if config.enable_ssh { - info!("starting TCP and SSH RTR servers"); - service.spawn_tcp_and_ssh_from_openssh( - config.tcp_addr, - config.ssh_addr, - &config.ssh_host_key_path, - &config.ssh_authorized_keys_path, - &config.ssh_username, - &config.ssh_subsystem_name, - config.ssh_auth_mode, - config.ssh_password.as_deref(), - ) - } else { - info!("starting TCP RTR server"); - service.spawn_tcp_only(config.tcp_addr) - } -} - -fn spawn_refresh_task( - config: &AppConfig, - runtime_config: RuntimeConfigHandle, - mut source_reload_rx: mpsc::Receiver, - shared_cache: SharedRtrCache, - store: RtrStore, - notifier: RtrNotifier, - service_stats: RtrServiceStats, - report_context: ReportContext, -) -> JoinHandle<()> { - let report_dir = PathBuf::from(&config.report_dir); - let mut payload_load_config = PayloadLoadConfig { - ccr_dir: config.ccr_dir.clone(), - slurm_dir: config.slurm_dir.clone(), - strict_ccr_validation: config.strict_ccr_validation, - }; - let initial_runtime_config = runtime_config.current(); - - tokio::spawn(async move { - let mut active_runtime_config = initial_runtime_config; - let mut config_rx = runtime_config.subscribe(); - let mut interval = tokio::time::interval(std::time::Duration::from_secs( - active_runtime_config.source_refresh_interval_seconds, - )); - let mut last_fingerprint: Option = None; - report_context.write_source_or_warn( - &report_dir, - "startup", - &shared_cache, - ¬ifier, - &service_stats, - ); - report_context.write_clients_or_warn( - &report_dir, - "startup", - &shared_cache, - ¬ifier, - &service_stats, - ); - report_context.write_runtime_or_warn( - &report_dir, - "startup", - &shared_cache, - ¬ifier, - &service_stats, - ); - let mut runtime_interval = tokio::time::interval_at( - tokio::time::Instant::now() - + std::time::Duration::from_secs( - active_runtime_config.runtime_report_interval_seconds, - ), - std::time::Duration::from_secs(active_runtime_config.runtime_report_interval_seconds), - ); - runtime_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - let mut client_change_rx = service_stats.subscribe_connection_changes(); - - loop { - tokio::select! { - changed = config_rx.changed() => { - match changed { - Ok(()) => { - let next_config = config_rx.borrow().clone(); - apply_runtime_config_update( - &mut active_runtime_config, - next_config, - &shared_cache, - &store, - &report_context, - &mut payload_load_config, - &mut interval, - &mut runtime_interval, - ); - report_context.write_runtime_or_warn(&report_dir, "admin_config_changed", &shared_cache, ¬ifier, &service_stats); - } - Err(_) => { - warn!("RTR runtime config change channel closed"); - } - } - continue; - } - changed = client_change_rx.changed() => { - match changed { - Ok(()) => { - report_context.write_clients_or_warn(&report_dir, "clients_changed", &shared_cache, ¬ifier, &service_stats); - } - Err(_) => { - warn!("RTR client connection change channel closed"); - } - } - continue; - } - _ = runtime_interval.tick() => { - log_cache_memory_stats("periodic_observe", &shared_cache, ¬ifier); - report_context.write_runtime_or_warn(&report_dir, "runtime_periodic", &shared_cache, ¬ifier, &service_stats); - continue; - } - _ = interval.tick() => {} - command = source_reload_rx.recv() => { - let Some(command) = command else { - warn!("RTR source reload admin channel closed"); - continue; - }; - let result = perform_source_refresh( - command.phase, - command.force, - &payload_load_config, - &shared_cache, - &store, - ¬ifier, - &service_stats, - &report_context, - &report_dir, - &mut last_fingerprint, - ); - let _ = command.respond_to.send(result.map_err(|err| err.to_string())); - continue; - } - } - let _ = perform_source_refresh( - "refresh_complete", - false, - &payload_load_config, - &shared_cache, - &store, - ¬ifier, - &service_stats, - &report_context, - &report_dir, - &mut last_fingerprint, - ); - } - }) -} - -#[allow(clippy::too_many_arguments)] -fn perform_source_refresh( - phase: &'static str, - force: bool, - payload_load_config: &PayloadLoadConfig, - shared_cache: &SharedRtrCache, - store: &RtrStore, - notifier: &RtrNotifier, - service_stats: &RtrServiceStats, - report_context: &ReportContext, - report_dir: &PathBuf, - last_fingerprint: &mut Option, -) -> Result { - let source_to_delta_started = Instant::now(); - let attempted_at = Utc::now(); - - let current_fingerprint = match latest_sources_fingerprint(payload_load_config) { - Ok(fp) => fp, - Err(err) => { - report_context.record_refresh_failure( - attempted_at, - source_to_delta_started.elapsed().as_millis(), - &err, - ); - warn!( - "failed to fingerprint CCR/SLURM sources from {}: {:?} (source_to_delta_elapsed_ms={})", - payload_load_config.ccr_dir, - err, - source_to_delta_started.elapsed().as_millis() - ); - report_context.write_source_or_warn( - report_dir, - "refresh_failed", - shared_cache, - notifier, - service_stats, - ); - return Err(err); - } - }; - report_context.record_source_fingerprint(current_fingerprint.clone()); - - if !force && last_fingerprint.as_ref() == Some(¤t_fingerprint) { - report_context - .record_refresh_unchanged(attempted_at, source_to_delta_started.elapsed().as_millis()); - info!( - "RTR source refresh skipped: source files unchanged (ccr_path={}, slurm_file_count={}, elapsed_ms={})", - current_fingerprint.ccr.path, - current_fingerprint.slurm_files.len(), - source_to_delta_started.elapsed().as_millis() - ); - log_cache_memory_stats("refresh_skipped_unchanged", shared_cache, notifier); - report_context.write_source_or_warn( - report_dir, - "refresh_skipped_unchanged", - shared_cache, - notifier, - service_stats, - ); - return Ok(SourceReloadResult { - phase, - changed: false, - skipped_unchanged: true, - payload_count: None, - serials: shared_cache.load_full().serials(), - }); - } - - let load = match load_payloads_from_latest_sources_with_report(payload_load_config) { - Ok(load) => load, - Err(err) => { - report_context.record_refresh_failure( - attempted_at, - source_to_delta_started.elapsed().as_millis(), - &err, - ); - warn!( - "failed to reload CCR/SLURM payloads from {}: {:?} (source_to_delta_elapsed_ms={})", - payload_load_config.ccr_dir, - err, - source_to_delta_started.elapsed().as_millis() - ); - report_context.write_source_or_warn( - report_dir, - "refresh_failed", - shared_cache, - notifier, - service_stats, - ); - return Err(err); - } - }; - - let source = load.source; - let quality = load.quality; - let payloads = load.payloads; - let payload_count = payloads.len(); - let source_snapshot = Snapshot::from_payloads(payloads); - let old_cache = shared_cache.load_full(); - let old_serial = old_cache.serial_for_version(2); - let mut next_cache = old_cache.as_ref().clone(); - let updated = match next_cache.update_with_snapshot(source_snapshot, store) { - Ok(()) => { - let new_serial = next_cache.serial_for_version(2); - shared_cache.store(std::sync::Arc::new(next_cache)); - if new_serial != old_serial { - info!( - "RTR cache refresh applied: ccr_dir={}, payload_count={}, old_serial={}, new_serial={}", - payload_load_config.ccr_dir, payload_count, old_serial, new_serial - ); - true - } else { - info!( - "RTR cache refresh found no change: ccr_dir={}, payload_count={}, serial={}", - payload_load_config.ccr_dir, payload_count, old_serial - ); - false - } - } - Err(err) => { - report_context.record_refresh_failure( - attempted_at, - source_to_delta_started.elapsed().as_millis(), - &err, - ); - warn!("RTR cache update failed: {:?}", err); - report_context.write_source_or_warn( - report_dir, - "refresh_failed", - shared_cache, - notifier, - service_stats, - ); - return Err(err); - } - }; - report_context.record_refresh_success( - attempted_at, - source_to_delta_started.elapsed().as_millis(), - updated, - source, - quality, - ); - info!( - "RTR source-to-delta timing: phase={}, ccr_dir={}, payload_count={}, changed={}, elapsed_ms={}", - phase, - payload_load_config.ccr_dir, - payload_count, - updated, - source_to_delta_started.elapsed().as_millis() - ); - - if updated { - let listener_count = notifier.notify_cache_updated(); - info!( - "RTR cache updated, notify signal emitted to session listeners: listener_count={}", - listener_count - ); - } - log_cache_memory_stats(phase, shared_cache, notifier); - report_context.write_source_or_warn(report_dir, phase, shared_cache, notifier, service_stats); - last_fingerprint.replace(current_fingerprint); - - Ok(SourceReloadResult { - phase, - changed: updated, - skipped_unchanged: false, - payload_count: Some(payload_count), - serials: shared_cache.load_full().serials(), - }) -} - -fn apply_runtime_config_update( - active: &mut RuntimeConfig, - next: RuntimeConfig, - shared_cache: &SharedRtrCache, - store: &RtrStore, - report_context: &ReportContext, - payload_load_config: &mut PayloadLoadConfig, - refresh_interval: &mut tokio::time::Interval, - runtime_interval: &mut tokio::time::Interval, -) { - let old = active.clone(); - *active = next.clone(); - payload_load_config.strict_ccr_validation = next.strict_ccr_validation; - report_context.update_runtime_config(&next); - - { - let old_cache = shared_cache.load_full(); - let mut next_cache = old_cache.as_ref().clone(); - next_cache.update_runtime_config( - next.max_delta, - next.prune_delta_by_snapshot_size, - next.timing(), - store, - ); - shared_cache.store(std::sync::Arc::new(next_cache)); - } - - if old.source_refresh_interval_seconds != next.source_refresh_interval_seconds { - *refresh_interval = tokio::time::interval(std::time::Duration::from_secs( - next.source_refresh_interval_seconds, - )); - refresh_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - } - if old.runtime_report_interval_seconds != next.runtime_report_interval_seconds { - *runtime_interval = tokio::time::interval_at( - tokio::time::Instant::now() - + std::time::Duration::from_secs(next.runtime_report_interval_seconds), - std::time::Duration::from_secs(next.runtime_report_interval_seconds), - ); - runtime_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - } - - info!( - "RTR runtime config applied: max_delta={}, prune_delta_by_snapshot_size={}, source_refresh_interval_seconds={}, runtime_report_interval_seconds={}, report_history_limit={}, strict_ccr_validation={}, timezone={}, timing=({}, {}, {})", - next.max_delta, - next.prune_delta_by_snapshot_size, - next.source_refresh_interval_seconds, - next.runtime_report_interval_seconds, - next.report_history_limit, - next.strict_ccr_validation, - next.timezone, - next.timing.refresh, - next.timing.retry, - next.timing.expire - ); -} - -fn log_cache_memory_stats(phase: &str, shared_cache: &SharedRtrCache, notifier: &RtrNotifier) { - let cache = shared_cache.load_full(); - let stats = cache.memory_stats(); - let rss_mib = current_rss_mib(); - - info!( - "RTR memory observe: phase={}, listener_count={}, serials={:?}, snapshot_payload_counts={:?}, delta_lengths={:?}, delta_payload_counts={:?}, rss_mib={:?}", - phase, - notifier.listener_count(), - stats.serials, - stats.snapshot_payload_counts, - stats.delta_lengths, - stats.delta_payload_counts, - rss_mib - ); -} - async fn wait_for_shutdown() -> Result<()> { tokio::signal::ctrl_c().await?; info!("shutdown signal received"); diff --git a/src/rtr/bootstrap.rs b/src/rtr/bootstrap.rs new file mode 100644 index 0000000..2987bf3 --- /dev/null +++ b/src/rtr/bootstrap.rs @@ -0,0 +1,115 @@ +use std::sync::Arc; +use std::time::Instant; + +use anyhow::Result; +use arc_swap::ArcSwap; +use chrono::Utc; +use tracing::info; + +use crate::rtr::cache::{RtrCache, SharedRtrCache}; +use crate::rtr::config::AppConfig; +use crate::rtr::report::ReportContext; +use crate::rtr::server::{RtrService, RunningRtrService}; +use crate::rtr::store::RtrStore; +use crate::source::pipeline::{PayloadLoadConfig, load_payloads_from_latest_sources_with_report}; + +pub fn open_store(config: &AppConfig) -> Result { + info!("opening RTR store: {}", config.db_path); + RtrStore::open(&config.db_path) +} + +pub fn init_shared_cache( + config: &AppConfig, + store: &RtrStore, + report_context: &ReportContext, +) -> Result { + let payload_load_config = PayloadLoadConfig { + ccr_dir: config.ccr_dir.clone(), + slurm_dir: config.slurm_dir.clone(), + }; + let source_to_delta_started = Instant::now(); + let report_context_for_loader = report_context.clone(); + let initial_cache = RtrCache::default().init( + store, + config.max_delta, + config.prune_delta_by_snapshot_size, + config.timing, + || { + let load = load_payloads_from_latest_sources_with_report(&payload_load_config)?; + info!( + "RTR source-to-delta timing: phase=startup_load_complete, ccr_dir={}, payload_count={}, elapsed_ms={}", + payload_load_config.ccr_dir, + load.payloads.len(), + source_to_delta_started.elapsed().as_millis() + ); + report_context_for_loader.record_refresh_success( + Utc::now(), + source_to_delta_started.elapsed().as_millis(), + true, + load.source, + load.quality, + ); + Ok(load.payloads) + }, + )?; + info!( + "RTR source-to-delta timing: phase=startup_cache_init_complete, ccr_dir={}, serials={:?}, elapsed_ms={}", + payload_load_config.ccr_dir, + initial_cache.serials(), + source_to_delta_started.elapsed().as_millis() + ); + + let shared_cache: SharedRtrCache = Arc::new(ArcSwap::from_pointee(initial_cache)); + let cache = shared_cache.load_full(); + info!( + "cache initialized: session_ids={:?}, serials={:?}", + cache.session_ids(), + cache.serials() + ); + + Ok(shared_cache) +} + +pub fn start_servers(config: &AppConfig, service: &RtrService) -> RunningRtrService { + if config.enable_tls && config.enable_ssh { + info!("starting TCP, TLS and SSH RTR servers"); + service.spawn_tcp_tls_and_ssh_from_pem_and_openssh( + config.tcp_addr, + config.tls_addr, + config.ssh_addr, + &config.tls_cert_path, + &config.tls_key_path, + &config.tls_client_ca_path, + &config.ssh_host_key_path, + &config.ssh_authorized_keys_path, + &config.ssh_username, + &config.ssh_subsystem_name, + config.ssh_auth_mode, + config.ssh_password.as_deref(), + ) + } else if config.enable_tls { + info!("starting TCP and TLS RTR servers"); + service.spawn_tcp_and_tls_from_pem( + config.tcp_addr, + config.tls_addr, + &config.tls_cert_path, + &config.tls_key_path, + &config.tls_client_ca_path, + ) + } else if config.enable_ssh { + info!("starting TCP and SSH RTR servers"); + service.spawn_tcp_and_ssh_from_openssh( + config.tcp_addr, + config.ssh_addr, + &config.ssh_host_key_path, + &config.ssh_authorized_keys_path, + &config.ssh_username, + &config.ssh_subsystem_name, + config.ssh_auth_mode, + config.ssh_password.as_deref(), + ) + } else { + info!("starting TCP RTR server"); + service.spawn_tcp_only(config.tcp_addr) + } +} diff --git a/src/rtr/config.rs b/src/rtr/config.rs index 7a916c4..1f0c934 100644 --- a/src/rtr/config.rs +++ b/src/rtr/config.rs @@ -34,7 +34,6 @@ pub struct AppConfig { pub max_delta: u8, pub prune_delta_by_snapshot_size: bool, - pub strict_ccr_validation: bool, pub source_refresh_interval: Duration, pub report_dir: String, pub runtime_report_interval: Duration, @@ -54,7 +53,6 @@ pub struct RuntimeConfig { pub source_refresh_interval_seconds: u64, pub runtime_report_interval_seconds: u64, pub report_history_limit: usize, - pub strict_ccr_validation: bool, pub timezone: String, pub timing: RuntimeTimingConfig, } @@ -73,7 +71,6 @@ pub struct RuntimeConfigPatch { pub source_refresh_interval_seconds: Option, pub runtime_report_interval_seconds: Option, pub report_history_limit: Option, - pub strict_ccr_validation: Option, pub timezone: Option, pub timing: Option, } @@ -109,7 +106,6 @@ impl Default for AppConfig { max_delta: 100, prune_delta_by_snapshot_size: false, - strict_ccr_validation: false, source_refresh_interval: Duration::from_secs(300), report_dir: "./report".to_string(), runtime_report_interval: Duration::from_secs(300), @@ -227,9 +223,6 @@ impl AppConfig { config.prune_delta_by_snapshot_size = parse_bool(&value, "RPKI_RTR_PRUNE_DELTA_BY_SNAPSHOT_SIZE")?; } - if let Some(value) = env_var("RPKI_RTR_STRICT_CCR_VALIDATION")? { - config.strict_ccr_validation = parse_bool(&value, "RPKI_RTR_STRICT_CCR_VALIDATION")?; - } if let Some(value) = env_var("RPKI_RTR_REPORT_DIR")? { config.report_dir = value; } @@ -371,7 +364,6 @@ impl AppConfig { source_refresh_interval_seconds: self.source_refresh_interval.as_secs(), runtime_report_interval_seconds: self.runtime_report_interval.as_secs(), report_history_limit: self.report_history_limit, - strict_ccr_validation: self.strict_ccr_validation, timezone: format_timezone(self.timezone), timing: RuntimeTimingConfig::from(self.timing), } @@ -417,9 +409,6 @@ impl RuntimeConfig { } next.report_history_limit = value; } - if let Some(value) = patch.strict_ccr_validation { - next.strict_ccr_validation = value; - } if let Some(value) = patch.timezone { next.timezone = format_timezone(parse_timezone(&value, "timezone")?); } @@ -499,7 +488,7 @@ pub fn log_startup_config(config: &AppConfig) { config.slurm_dir.as_deref().unwrap_or("disabled") ); info!("max_delta={}", config.max_delta); - info!("strict_ccr_validation={}", config.strict_ccr_validation); + info!("strict_ccr_validation=true"); info!( "source_refresh_interval_secs={}", config.source_refresh_interval.as_secs() diff --git a/src/rtr/mod.rs b/src/rtr/mod.rs index 0356dd8..7121e11 100644 --- a/src/rtr/mod.rs +++ b/src/rtr/mod.rs @@ -1,10 +1,12 @@ pub mod admin; +pub mod bootstrap; pub mod cache; pub mod config; pub mod error_type; pub mod payload; pub mod pdu; pub mod report; +pub mod runtime; pub mod server; pub mod session; pub mod state; diff --git a/src/rtr/report.rs b/src/rtr/report.rs index 7c1e930..b58aa98 100644 --- a/src/rtr/report.rs +++ b/src/rtr/report.rs @@ -23,7 +23,6 @@ pub struct ReportConfiguration { report_history_limit: usize, max_delta: u8, prune_delta_by_snapshot_size: bool, - strict_ccr_validation: bool, timezone: String, timing: TimingReport, } @@ -42,7 +41,6 @@ impl ReportConfiguration { report_history_limit: usize, max_delta: u8, prune_delta_by_snapshot_size: bool, - strict_ccr_validation: bool, timezone: Tz, timing: (u32, u32, u32), ) -> Self { @@ -52,7 +50,6 @@ impl ReportConfiguration { report_history_limit, max_delta, prune_delta_by_snapshot_size, - strict_ccr_validation, timezone: format_timezone(timezone), timing: TimingReport { refresh: timing.0, @@ -169,7 +166,6 @@ impl ReportContext { config.report_history_limit, config.max_delta, config.prune_delta_by_snapshot_size, - config.strict_ccr_validation, timezone, ( config.timing.refresh, diff --git a/src/rtr/runtime.rs b/src/rtr/runtime.rs new file mode 100644 index 0000000..6f6634e --- /dev/null +++ b/src/rtr/runtime.rs @@ -0,0 +1,394 @@ +use std::path::PathBuf; +use std::time::Instant; + +use anyhow::Result; +use chrono::Utc; +use tokio::sync::mpsc; +use tokio::task::JoinHandle; +use tracing::{info, warn}; + +use crate::rtr::admin::{RuntimeConfigHandle, SourceReloadCommand, SourceReloadResult}; +use crate::rtr::cache::{SharedRtrCache, Snapshot}; +use crate::rtr::config::{AppConfig, RuntimeConfig}; +use crate::rtr::report::{ReportContext, current_rss_mib}; +use crate::rtr::server::{RtrNotifier, RtrServiceStats}; +use crate::rtr::store::RtrStore; +use crate::source::pipeline::{ + PayloadLoadConfig, SourceFingerprint, latest_sources_fingerprint, + load_payloads_from_latest_sources_with_report, +}; + +pub fn spawn_refresh_task( + config: &AppConfig, + runtime_config: RuntimeConfigHandle, + mut source_reload_rx: mpsc::Receiver, + shared_cache: SharedRtrCache, + store: RtrStore, + notifier: RtrNotifier, + service_stats: RtrServiceStats, + report_context: ReportContext, +) -> JoinHandle<()> { + let report_dir = PathBuf::from(&config.report_dir); + let payload_load_config = PayloadLoadConfig { + ccr_dir: config.ccr_dir.clone(), + slurm_dir: config.slurm_dir.clone(), + }; + let initial_runtime_config = runtime_config.current(); + + tokio::spawn(async move { + let mut active_runtime_config = initial_runtime_config; + let mut config_rx = runtime_config.subscribe(); + let mut interval = tokio::time::interval(std::time::Duration::from_secs( + active_runtime_config.source_refresh_interval_seconds, + )); + let mut last_fingerprint: Option = None; + report_context.write_source_or_warn( + &report_dir, + "startup", + &shared_cache, + ¬ifier, + &service_stats, + ); + report_context.write_clients_or_warn( + &report_dir, + "startup", + &shared_cache, + ¬ifier, + &service_stats, + ); + report_context.write_runtime_or_warn( + &report_dir, + "startup", + &shared_cache, + ¬ifier, + &service_stats, + ); + let mut runtime_interval = tokio::time::interval_at( + tokio::time::Instant::now() + + std::time::Duration::from_secs( + active_runtime_config.runtime_report_interval_seconds, + ), + std::time::Duration::from_secs(active_runtime_config.runtime_report_interval_seconds), + ); + runtime_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut client_change_rx = service_stats.subscribe_connection_changes(); + + loop { + tokio::select! { + changed = config_rx.changed() => { + match changed { + Ok(()) => { + let next_config = config_rx.borrow().clone(); + apply_runtime_config_update( + &mut active_runtime_config, + next_config, + &shared_cache, + &store, + &report_context, + &mut interval, + &mut runtime_interval, + ); + report_context.write_runtime_or_warn(&report_dir, "admin_config_changed", &shared_cache, ¬ifier, &service_stats); + } + Err(_) => { + warn!("RTR runtime config change channel closed"); + } + } + continue; + } + changed = client_change_rx.changed() => { + match changed { + Ok(()) => { + report_context.write_clients_or_warn(&report_dir, "clients_changed", &shared_cache, ¬ifier, &service_stats); + } + Err(_) => { + warn!("RTR client connection change channel closed"); + } + } + continue; + } + _ = runtime_interval.tick() => { + log_cache_memory_stats("periodic_observe", &shared_cache, ¬ifier); + report_context.write_runtime_or_warn(&report_dir, "runtime_periodic", &shared_cache, ¬ifier, &service_stats); + continue; + } + _ = interval.tick() => {} + command = source_reload_rx.recv() => { + let Some(command) = command else { + warn!("RTR source reload admin channel closed"); + continue; + }; + let result = perform_source_refresh( + command.phase, + command.force, + &payload_load_config, + &shared_cache, + &store, + ¬ifier, + &service_stats, + &report_context, + &report_dir, + &mut last_fingerprint, + ); + let _ = command.respond_to.send(result.map_err(|err| err.to_string())); + continue; + } + } + let _ = perform_source_refresh( + "refresh_complete", + false, + &payload_load_config, + &shared_cache, + &store, + ¬ifier, + &service_stats, + &report_context, + &report_dir, + &mut last_fingerprint, + ); + } + }) +} + +#[allow(clippy::too_many_arguments)] +fn perform_source_refresh( + phase: &'static str, + force: bool, + payload_load_config: &PayloadLoadConfig, + shared_cache: &SharedRtrCache, + store: &RtrStore, + notifier: &RtrNotifier, + service_stats: &RtrServiceStats, + report_context: &ReportContext, + report_dir: &PathBuf, + last_fingerprint: &mut Option, +) -> Result { + let source_to_delta_started = Instant::now(); + let attempted_at = Utc::now(); + + let current_fingerprint = match latest_sources_fingerprint(payload_load_config) { + Ok(fp) => fp, + Err(err) => { + report_context.record_refresh_failure( + attempted_at, + source_to_delta_started.elapsed().as_millis(), + &err, + ); + warn!( + "failed to fingerprint CCR/SLURM sources from {}: {:?} (source_to_delta_elapsed_ms={})", + payload_load_config.ccr_dir, + err, + source_to_delta_started.elapsed().as_millis() + ); + report_context.write_source_or_warn( + report_dir, + "refresh_failed", + shared_cache, + notifier, + service_stats, + ); + return Err(err); + } + }; + report_context.record_source_fingerprint(current_fingerprint.clone()); + + if !force && last_fingerprint.as_ref() == Some(¤t_fingerprint) { + report_context + .record_refresh_unchanged(attempted_at, source_to_delta_started.elapsed().as_millis()); + info!( + "RTR source refresh skipped: source files unchanged (ccr_path={}, slurm_file_count={}, elapsed_ms={})", + current_fingerprint.ccr.path, + current_fingerprint.slurm_files.len(), + source_to_delta_started.elapsed().as_millis() + ); + log_cache_memory_stats("refresh_skipped_unchanged", shared_cache, notifier); + report_context.write_source_or_warn( + report_dir, + "refresh_skipped_unchanged", + shared_cache, + notifier, + service_stats, + ); + return Ok(SourceReloadResult { + phase, + changed: false, + skipped_unchanged: true, + payload_count: None, + serials: shared_cache.load_full().serials(), + }); + } + + let load = match load_payloads_from_latest_sources_with_report(payload_load_config) { + Ok(load) => load, + Err(err) => { + report_context.record_refresh_failure( + attempted_at, + source_to_delta_started.elapsed().as_millis(), + &err, + ); + warn!( + "failed to reload CCR/SLURM payloads from {}: {:?} (source_to_delta_elapsed_ms={})", + payload_load_config.ccr_dir, + err, + source_to_delta_started.elapsed().as_millis() + ); + report_context.write_source_or_warn( + report_dir, + "refresh_failed", + shared_cache, + notifier, + service_stats, + ); + return Err(err); + } + }; + + let source = load.source; + let quality = load.quality; + let payloads = load.payloads; + let payload_count = payloads.len(); + let source_snapshot = Snapshot::from_payloads(payloads); + let old_cache = shared_cache.load_full(); + let old_serial = old_cache.serial_for_version(2); + let mut next_cache = old_cache.as_ref().clone(); + let updated = match next_cache.update_with_snapshot(source_snapshot, store) { + Ok(()) => { + let new_serial = next_cache.serial_for_version(2); + shared_cache.store(std::sync::Arc::new(next_cache)); + if new_serial != old_serial { + info!( + "RTR cache refresh applied: ccr_dir={}, payload_count={}, old_serial={}, new_serial={}", + payload_load_config.ccr_dir, payload_count, old_serial, new_serial + ); + true + } else { + info!( + "RTR cache refresh found no change: ccr_dir={}, payload_count={}, serial={}", + payload_load_config.ccr_dir, payload_count, old_serial + ); + false + } + } + Err(err) => { + report_context.record_refresh_failure( + attempted_at, + source_to_delta_started.elapsed().as_millis(), + &err, + ); + warn!("RTR cache update failed: {:?}", err); + report_context.write_source_or_warn( + report_dir, + "refresh_failed", + shared_cache, + notifier, + service_stats, + ); + return Err(err); + } + }; + report_context.record_refresh_success( + attempted_at, + source_to_delta_started.elapsed().as_millis(), + updated, + source, + quality, + ); + info!( + "RTR source-to-delta timing: phase={}, ccr_dir={}, payload_count={}, changed={}, elapsed_ms={}", + phase, + payload_load_config.ccr_dir, + payload_count, + updated, + source_to_delta_started.elapsed().as_millis() + ); + + if updated { + let listener_count = notifier.notify_cache_updated(); + info!( + "RTR cache updated, notify signal emitted to session listeners: listener_count={}", + listener_count + ); + } + log_cache_memory_stats(phase, shared_cache, notifier); + report_context.write_source_or_warn(report_dir, phase, shared_cache, notifier, service_stats); + last_fingerprint.replace(current_fingerprint); + + Ok(SourceReloadResult { + phase, + changed: updated, + skipped_unchanged: false, + payload_count: Some(payload_count), + serials: shared_cache.load_full().serials(), + }) +} + +fn apply_runtime_config_update( + active: &mut RuntimeConfig, + next: RuntimeConfig, + shared_cache: &SharedRtrCache, + store: &RtrStore, + report_context: &ReportContext, + refresh_interval: &mut tokio::time::Interval, + runtime_interval: &mut tokio::time::Interval, +) { + let old = active.clone(); + *active = next.clone(); + report_context.update_runtime_config(&next); + + { + let old_cache = shared_cache.load_full(); + let mut next_cache = old_cache.as_ref().clone(); + next_cache.update_runtime_config( + next.max_delta, + next.prune_delta_by_snapshot_size, + next.timing(), + store, + ); + shared_cache.store(std::sync::Arc::new(next_cache)); + } + + if old.source_refresh_interval_seconds != next.source_refresh_interval_seconds { + *refresh_interval = tokio::time::interval(std::time::Duration::from_secs( + next.source_refresh_interval_seconds, + )); + refresh_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + } + if old.runtime_report_interval_seconds != next.runtime_report_interval_seconds { + *runtime_interval = tokio::time::interval_at( + tokio::time::Instant::now() + + std::time::Duration::from_secs(next.runtime_report_interval_seconds), + std::time::Duration::from_secs(next.runtime_report_interval_seconds), + ); + runtime_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + } + + info!( + "RTR runtime config applied: max_delta={}, prune_delta_by_snapshot_size={}, source_refresh_interval_seconds={}, runtime_report_interval_seconds={}, report_history_limit={}, timezone={}, timing=({}, {}, {})", + next.max_delta, + next.prune_delta_by_snapshot_size, + next.source_refresh_interval_seconds, + next.runtime_report_interval_seconds, + next.report_history_limit, + next.timezone, + next.timing.refresh, + next.timing.retry, + next.timing.expire + ); +} + +fn log_cache_memory_stats(phase: &str, shared_cache: &SharedRtrCache, notifier: &RtrNotifier) { + let cache = shared_cache.load_full(); + let stats = cache.memory_stats(); + let rss_mib = current_rss_mib(); + + info!( + "RTR memory observe: phase={}, listener_count={}, serials={:?}, snapshot_payload_counts={:?}, delta_lengths={:?}, delta_payload_counts={:?}, rss_mib={:?}", + phase, + notifier.listener_count(), + stats.serials, + stats.snapshot_payload_counts, + stats.delta_lengths, + stats.delta_payload_counts, + rss_mib + ); +} diff --git a/src/source/pipeline.rs b/src/source/pipeline.rs index c2cd94e..e8b6b76 100644 --- a/src/source/pipeline.rs +++ b/src/source/pipeline.rs @@ -15,7 +15,6 @@ use crate::source::ccr::{ pub struct PayloadLoadConfig { pub ccr_dir: String, pub slurm_dir: Option, - pub strict_ccr_validation: bool, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -107,7 +106,7 @@ pub fn load_payloads_from_latest_sources_with_report( aspa: snapshot.vaps.len(), }; let produced_at = snapshot.produced_at.clone(); - let conversion = snapshot_to_payloads_with_options(&snapshot, config.strict_ccr_validation)?; + let conversion = snapshot_to_payloads_with_options(&snapshot, true)?; let invalid = PayloadTypeCounts { total: conversion.invalid_vrps.len() + conversion.invalid_vaps.len(), vrp: conversion.invalid_vrps.len(), @@ -147,13 +146,12 @@ pub fn load_payloads_from_latest_sources_with_report( let after_slurm = PayloadTypeCounts::from_payloads(&payloads); info!( - "loaded latest CCR snapshot: file={}, produced_at={:?}, vrp_count={}, vap_count={}, payload_count={}, strict_ccr_validation={}", + "loaded latest CCR snapshot: file={}, produced_at={:?}, vrp_count={}, vap_count={}, payload_count={}, strict_ccr_validation=true", latest.display(), produced_at, snapshot.vrps.len(), snapshot.vaps.len(), - before_slurm.total, - config.strict_ccr_validation + before_slurm.total ); Ok(PayloadLoadResult { diff --git a/tests/test_pipeline.rs b/tests/test_pipeline.rs index ea939b5..dfe8743 100644 --- a/tests/test_pipeline.rs +++ b/tests/test_pipeline.rs @@ -36,7 +36,6 @@ fn load_payloads_rejects_entire_slurm_set_when_any_file_is_invalid() { let config = PayloadLoadConfig { ccr_dir: data_dir(), slurm_dir: Some(slurm_dir.path().to_string_lossy().to_string()), - strict_ccr_validation: false, }; let err = load_payloads_from_latest_sources(&config).unwrap_err(); @@ -50,7 +49,6 @@ fn load_report_contains_source_and_quality_details() { let config = PayloadLoadConfig { ccr_dir: data_dir(), slurm_dir: None, - strict_ccr_validation: false, }; let result = load_payloads_from_latest_sources_with_report(&config).unwrap(); @@ -85,7 +83,6 @@ fn load_report_contains_slurm_metadata_and_rule_counts() { let config = PayloadLoadConfig { ccr_dir: data_dir(), slurm_dir: Some(slurm_dir.path().to_string_lossy().to_string()), - strict_ccr_validation: false, }; let result = load_payloads_from_latest_sources_with_report(&config).unwrap(); diff --git a/tests/test_rtr_config.rs b/tests/test_rtr_config.rs index a515a10..4ee3a0d 100644 --- a/tests/test_rtr_config.rs +++ b/tests/test_rtr_config.rs @@ -39,7 +39,6 @@ fn runtime_config_patch_validates_and_preserves_unspecified_values() { source_refresh_interval_seconds: Some(60), runtime_report_interval_seconds: Some(120), report_history_limit: Some(20), - strict_ccr_validation: Some(true), timezone: Some("UTC".to_string()), timing: Some(RuntimeTimingConfigPatch { refresh: Some(1800), @@ -54,7 +53,6 @@ fn runtime_config_patch_validates_and_preserves_unspecified_values() { assert_eq!(next.source_refresh_interval_seconds, 60); assert_eq!(next.runtime_report_interval_seconds, 120); assert_eq!(next.report_history_limit, 20); - assert!(next.strict_ccr_validation); assert_eq!(next.timezone, "UTC"); assert_eq!(next.timing.refresh, 1800); assert_eq!(next.timing.retry, current.timing.retry); diff --git a/tests/test_rtr_report.rs b/tests/test_rtr_report.rs index 591c92d..cfbbaf9 100644 --- a/tests/test_rtr_report.rs +++ b/tests/test_rtr_report.rs @@ -20,7 +20,6 @@ fn test_configuration(report_history_limit: usize) -> ReportConfiguration { report_history_limit, 100, false, - false, chrono_tz::Asia::Shanghai, (3600, 600, 7200), )