arm构建
This commit is contained in:
parent
cc3518897f
commit
b5cbc7fbee
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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"] }
|
||||
|
||||
645
README.md
645
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 <compose-file> up -d --build
|
||||
docker compose -f <compose-file> logs -f
|
||||
docker compose -f <compose-file> 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`。
|
||||
|
||||
131
deploy/README.md
131
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`
|
||||
|
||||
237
deploy/client/build-run.sh
Normal file
237
deploy/client/build-run.sh
Normal file
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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}",
|
||||
|
||||
@ -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}",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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=""
|
||||
|
||||
|
||||
215
deploy/server/build-run.sh
Normal file
215
deploy/server/build-run.sh
Normal file
@ -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
|
||||
@ -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}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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<SlurmFile> {
|
||||
}
|
||||
|
||||
fn parse_args(args: impl Iterator<Item = String>) -> Result<Cli> {
|
||||
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<Item = String>) -> Result<Cli> {
|
||||
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<Item = String>) -> Result<Cli> {
|
||||
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] <snapshot.ccr> <policy.slurm>"
|
||||
"Usage: cargo run --bin slurm_apply_client -- [--dump-payloads] <snapshot.ccr> <policy.slurm>"
|
||||
);
|
||||
eprintln!();
|
||||
eprintln!("Reads a CCR snapshot, converts it into payloads, applies SLURM, and prints JSON.");
|
||||
|
||||
508
src/main_rtr.rs
508
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<RtrStore> {
|
||||
info!("opening RTR store: {}", config.db_path);
|
||||
RtrStore::open(&config.db_path)
|
||||
}
|
||||
|
||||
fn init_shared_cache(
|
||||
config: &AppConfig,
|
||||
store: &RtrStore,
|
||||
report_context: &ReportContext,
|
||||
) -> Result<SharedRtrCache> {
|
||||
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<rpki::rtr::admin::SourceReloadCommand>,
|
||||
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<SourceFingerprint> = 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<SourceFingerprint>,
|
||||
) -> Result<SourceReloadResult> {
|
||||
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");
|
||||
|
||||
115
src/rtr/bootstrap.rs
Normal file
115
src/rtr/bootstrap.rs
Normal file
@ -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<RtrStore> {
|
||||
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<SharedRtrCache> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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<u64>,
|
||||
pub runtime_report_interval_seconds: Option<u64>,
|
||||
pub report_history_limit: Option<usize>,
|
||||
pub strict_ccr_validation: Option<bool>,
|
||||
pub timezone: Option<String>,
|
||||
pub timing: Option<RuntimeTimingConfigPatch>,
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
394
src/rtr/runtime.rs
Normal file
394
src/rtr/runtime.rs
Normal file
@ -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<SourceReloadCommand>,
|
||||
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<SourceFingerprint> = 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<SourceFingerprint>,
|
||||
) -> Result<SourceReloadResult> {
|
||||
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
|
||||
);
|
||||
}
|
||||
@ -15,7 +15,6 @@ use crate::source::ccr::{
|
||||
pub struct PayloadLoadConfig {
|
||||
pub ccr_dir: String,
|
||||
pub slurm_dir: Option<String>,
|
||||
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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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),
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user