From 99250f8aa9a5ea512f5e22fba6eb4493844acafa Mon Sep 17 00:00:00 2001 From: "xiuting.xu" Date: Wed, 15 Apr 2026 16:26:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0timing=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++- deploy/server/docker-compose.yml | 2 +- scripts/start-rtr-server-tcp.sh | 10 +++- scripts/start-rtr-server-tls.sh | 10 +++- scripts/start-rtr-server.sh | 10 +++- src/main.rs | 94 +++++++++++++++++++++++++------- src/rtr/payload.rs | 2 +- 7 files changed, 107 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 30667a9..566ab2a 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,11 @@ RTR Server 运行时从 `CCR` 目录中扫描最新的 `.ccr` 文件作为输入 | `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_REFRESH_INTERVAL_SECS` | 刷新 CCR 目录并重新加载最新 `.ccr` 的间隔,单位秒,必须 `>= 1`。 | `300` | `300` | +| `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` | @@ -91,6 +95,8 @@ RTR Server 运行时从 `CCR` 目录中扫描最新的 `.ccr` 文件作为输入 - 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。 diff --git a/deploy/server/docker-compose.yml b/deploy/server/docker-compose.yml index 0e21d84..53d72f0 100644 --- a/deploy/server/docker-compose.yml +++ b/deploy/server/docker-compose.yml @@ -19,7 +19,7 @@ services: RPKI_RTR_CCR_DIR: "/app/data" RPKI_RTR_SLURM_DIR: "/app/slurm" RPKI_RTR_STRICT_CCR_VALIDATION: "false" - RPKI_RTR_REFRESH_INTERVAL_SECS: "300" + RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "300" volumes: - ../../data:/app/data:ro - ../../rtr-db:/app/rtr-db diff --git a/scripts/start-rtr-server-tcp.sh b/scripts/start-rtr-server-tcp.sh index 1adf103..eb6aebd 100644 --- a/scripts/start-rtr-server-tcp.sh +++ b/scripts/start-rtr-server-tcp.sh @@ -13,12 +13,18 @@ export RPKI_RTR_CCR_DIR : "${RPKI_RTR_MAX_DELTA:=100}" : "${RPKI_RTR_STRICT_CCR_VALIDATION:=false}" -: "${RPKI_RTR_REFRESH_INTERVAL_SECS:=300}" +: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:=300}" +: "${RPKI_RTR_TIMING_REFRESH_SECS:=3600}" +: "${RPKI_RTR_TIMING_RETRY_SECS:=600}" +: "${RPKI_RTR_TIMING_EXPIRE_SECS:=7200}" : "${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_REFRESH_INTERVAL_SECS +export RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS +export RPKI_RTR_TIMING_REFRESH_SECS +export RPKI_RTR_TIMING_RETRY_SECS +export RPKI_RTR_TIMING_EXPIRE_SECS export RPKI_RTR_MAX_CONNECTIONS export RPKI_RTR_NOTIFY_QUEUE_SIZE diff --git a/scripts/start-rtr-server-tls.sh b/scripts/start-rtr-server-tls.sh index e64730d..5f47995 100644 --- a/scripts/start-rtr-server-tls.sh +++ b/scripts/start-rtr-server-tls.sh @@ -22,12 +22,18 @@ export RPKI_RTR_TLS_CLIENT_CA_PATH : "${RPKI_RTR_MAX_DELTA:=100}" : "${RPKI_RTR_STRICT_CCR_VALIDATION:=false}" -: "${RPKI_RTR_REFRESH_INTERVAL_SECS:=300}" +: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:=300}" +: "${RPKI_RTR_TIMING_REFRESH_SECS:=3600}" +: "${RPKI_RTR_TIMING_RETRY_SECS:=600}" +: "${RPKI_RTR_TIMING_EXPIRE_SECS:=7200}" : "${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_REFRESH_INTERVAL_SECS +export RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS +export RPKI_RTR_TIMING_REFRESH_SECS +export RPKI_RTR_TIMING_RETRY_SECS +export RPKI_RTR_TIMING_EXPIRE_SECS export RPKI_RTR_MAX_CONNECTIONS export RPKI_RTR_NOTIFY_QUEUE_SIZE diff --git a/scripts/start-rtr-server.sh b/scripts/start-rtr-server.sh index fb6f1e5..5940eba 100644 --- a/scripts/start-rtr-server.sh +++ b/scripts/start-rtr-server.sh @@ -23,11 +23,17 @@ export RPKI_RTR_TLS_KEY_PATH export RPKI_RTR_TLS_CLIENT_CA_PATH : "${RPKI_RTR_MAX_DELTA:=100}" -: "${RPKI_RTR_REFRESH_INTERVAL_SECS:=300}" +: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:=300}" +: "${RPKI_RTR_TIMING_REFRESH_SECS:=3600}" +: "${RPKI_RTR_TIMING_RETRY_SECS:=600}" +: "${RPKI_RTR_TIMING_EXPIRE_SECS:=7200}" : "${RPKI_RTR_MAX_CONNECTIONS:=512}" : "${RPKI_RTR_NOTIFY_QUEUE_SIZE:=1024}" export RPKI_RTR_MAX_DELTA -export RPKI_RTR_REFRESH_INTERVAL_SECS +export RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS +export RPKI_RTR_TIMING_REFRESH_SECS +export RPKI_RTR_TIMING_RETRY_SECS +export RPKI_RTR_TIMING_EXPIRE_SECS export RPKI_RTR_MAX_CONNECTIONS export RPKI_RTR_NOTIFY_QUEUE_SIZE diff --git a/src/main.rs b/src/main.rs index 35dd1e9..8df60db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,8 @@ struct AppConfig { max_delta: u8, prune_delta_by_snapshot_size: bool, strict_ccr_validation: bool, - refresh_interval: Duration, + source_refresh_interval: Duration, + timing: Timing, service_config: RtrServiceConfig, } @@ -51,7 +52,8 @@ impl Default for AppConfig { max_delta: 100, prune_delta_by_snapshot_size: false, strict_ccr_validation: false, - refresh_interval: Duration::from_secs(300), + source_refresh_interval: Duration::from_secs(300), + timing: Timing::default(), service_config: RtrServiceConfig { max_connections: 512, @@ -126,22 +128,51 @@ impl AppConfig { 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_REFRESH_INTERVAL_SECS")? { - let secs: u64 = value.parse().map_err(|err| { - anyhow!( - "invalid RPKI_RTR_REFRESH_INTERVAL_SECS '{}': {}", - value, - err - ) - })?; - if secs == 0 { - return Err(anyhow!( - "invalid RPKI_RTR_REFRESH_INTERVAL_SECS '{}': must be >= 1", - value - )); + let source_refresh_interval_new = env_var("RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS")?; + let source_refresh_interval_legacy = env_var("RPKI_RTR_REFRESH_INTERVAL_SECS")?; + match ( + source_refresh_interval_new.as_deref(), + source_refresh_interval_legacy.as_deref(), + ) { + (Some(new_value), Some(_)) => { + let secs = parse_positive_u64( + new_value, + "RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS", + )?; + config.source_refresh_interval = Duration::from_secs(secs); + warn!( + "both RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS and legacy RPKI_RTR_REFRESH_INTERVAL_SECS are set; using RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS" + ); } - config.refresh_interval = Duration::from_secs(secs); + (Some(new_value), None) => { + let secs = parse_positive_u64( + new_value, + "RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS", + )?; + config.source_refresh_interval = Duration::from_secs(secs); + } + (None, Some(legacy_value)) => { + let secs = parse_positive_u64(legacy_value, "RPKI_RTR_REFRESH_INTERVAL_SECS")?; + config.source_refresh_interval = Duration::from_secs(secs); + warn!( + "RPKI_RTR_REFRESH_INTERVAL_SECS is deprecated; use RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS" + ); + } + (None, None) => {} } + if let Some(value) = env_var("RPKI_RTR_TIMING_REFRESH_SECS")? { + config.timing.refresh = parse_positive_u32(&value, "RPKI_RTR_TIMING_REFRESH_SECS")?; + } + if let Some(value) = env_var("RPKI_RTR_TIMING_RETRY_SECS")? { + config.timing.retry = parse_positive_u32(&value, "RPKI_RTR_TIMING_RETRY_SECS")?; + } + if let Some(value) = env_var("RPKI_RTR_TIMING_EXPIRE_SECS")? { + config.timing.expire = parse_positive_u32(&value, "RPKI_RTR_TIMING_EXPIRE_SECS")?; + } + config + .timing + .validate() + .map_err(|err| anyhow!("invalid RTR timing configuration: {}", err))?; if let Some(value) = env_var("RPKI_RTR_MAX_CONNECTIONS")? { config.service_config.max_connections = value .parse() @@ -218,7 +249,7 @@ fn init_shared_cache(config: &AppConfig, store: &RtrStore) -> Result JoinHandle<()> { - let refresh_interval = config.refresh_interval; + let refresh_interval = config.source_refresh_interval; let payload_load_config = PayloadLoadConfig { ccr_dir: config.ccr_dir.clone(), slurm_dir: config.slurm_dir.clone(), @@ -358,9 +389,12 @@ fn log_startup_config(config: &AppConfig) { info!("max_delta={}", config.max_delta); info!("strict_ccr_validation={}", config.strict_ccr_validation); info!( - "refresh_interval_secs={}", - config.refresh_interval.as_secs() + "source_refresh_interval_secs={}", + config.source_refresh_interval.as_secs() ); + info!("rtr_timing_refresh_secs={}", config.timing.refresh); + info!("rtr_timing_retry_secs={}", config.timing.retry); + info!("rtr_timing_expire_secs={}", config.timing.expire); info!("max_connections={}", config.service_config.max_connections); info!( "notify_queue_size={}", @@ -407,3 +441,23 @@ fn parse_bool(value: &str, name: &str) -> Result { _ => Err(anyhow!("invalid {} '{}': expected boolean", name, value)), } } + +fn parse_positive_u64(value: &str, name: &str) -> Result { + let parsed = value + .parse::() + .map_err(|err| anyhow!("invalid {} '{}': {}", name, value, err))?; + if parsed == 0 { + return Err(anyhow!("invalid {} '{}': must be >= 1", name, value)); + } + Ok(parsed) +} + +fn parse_positive_u32(value: &str, name: &str) -> Result { + let parsed = value + .parse::() + .map_err(|err| anyhow!("invalid {} '{}': {}", name, value, err))?; + if parsed == 0 { + return Err(anyhow!("invalid {} '{}': must be >= 1", name, value)); + } + Ok(parsed) +} diff --git a/src/rtr/payload.rs b/src/rtr/payload.rs index a9b1e50..a5aebbb 100644 --- a/src/rtr/payload.rs +++ b/src/rtr/payload.rs @@ -292,7 +292,7 @@ impl Timing { impl Default for Timing { fn default() -> Self { Self { - refresh: 60, + refresh: 3600, retry: 600, expire: 7200, }