补充rtr-client,完善tls连接等

This commit is contained in:
xiuting.xu
2026-03-25 10:08:40 +08:00
parent 9cbea4e2d0
commit 03c0ab0ec7
45 changed files with 7521 additions and 1835 deletions

View File

@@ -1,929 +0,0 @@
use std::cmp::Ordering;
use std::collections::{BTreeSet, VecDeque};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::data_model::resources::ip_resources::IPAddress;
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Timing};
use crate::rtr::store_db::RtrStore;
const DEFAULT_RETRY_INTERVAL: Duration = Duration::from_secs(600);
const DEFAULT_EXPIRE_INTERVAL: Duration = Duration::from_secs(7200);
pub type SharedRtrCache = Arc<RwLock<RtrCache>>;
#[derive(Debug, Clone)]
pub struct DualTime {
instant: Instant,
utc: DateTime<Utc>,
}
impl DualTime {
/// Create current time.
pub fn now() -> Self {
Self {
instant: Instant::now(),
utc: Utc::now(),
}
}
/// Get UTC time for logs.
pub fn utc(&self) -> DateTime<Utc> {
self.utc
}
/// Elapsed duration since creation/reset.
pub fn elapsed(&self) -> Duration {
self.instant.elapsed()
}
/// Whether duration is expired.
pub fn is_expired(&self, duration: Duration) -> bool {
self.elapsed() >= duration
}
/// Reset to now.
pub fn reset(&mut self) {
self.instant = Instant::now();
self.utc = Utc::now();
}
}
impl Serialize for DualTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.utc.timestamp_millis().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for DualTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let millis = i64::deserialize(deserializer)?;
let naive = NaiveDateTime::from_timestamp_millis(millis)
.ok_or_else(|| serde::de::Error::custom("invalid timestamp"))?;
let utc = DateTime::<Utc>::from_utc(naive, Utc);
Ok(Self {
instant: Instant::now(),
utc,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snapshot {
origins: BTreeSet<RouteOrigin>,
router_keys: BTreeSet<RouterKey>,
aspas: BTreeSet<Aspa>,
created_at: DualTime,
origins_hash: [u8; 32],
router_keys_hash: [u8; 32],
aspas_hash: [u8; 32],
snapshot_hash: [u8; 32],
}
impl Snapshot {
pub fn new(
origins: BTreeSet<RouteOrigin>,
router_keys: BTreeSet<RouterKey>,
aspas: BTreeSet<Aspa>,
) -> Self {
let mut snapshot = Snapshot {
origins,
router_keys,
aspas,
created_at: DualTime::now(),
origins_hash: [0u8; 32],
router_keys_hash: [0u8; 32],
aspas_hash: [0u8; 32],
snapshot_hash: [0u8; 32],
};
snapshot.recompute_hashes();
snapshot
}
pub fn empty() -> Self {
Self::new(BTreeSet::new(), BTreeSet::new(), BTreeSet::new())
}
pub fn from_payloads(payloads: Vec<Payload>) -> Self {
let mut origins = BTreeSet::new();
let mut router_keys = BTreeSet::new();
let mut aspas = BTreeSet::new();
for p in payloads {
match p {
Payload::RouteOrigin(o) => {
origins.insert(o);
}
Payload::RouterKey(k) => {
router_keys.insert(k);
}
Payload::Aspa(a) => {
aspas.insert(a);
}
}
}
Snapshot::new(origins, router_keys, aspas)
}
pub fn recompute_hashes(&mut self) {
self.origins_hash = self.compute_origins_hash();
self.router_keys_hash = self.compute_router_keys_hash();
self.aspas_hash = self.compute_aspas_hash();
self.snapshot_hash = self.compute_snapshot_hash();
}
fn compute_origins_hash(&self) -> [u8; 32] {
Self::hash_ordered_iter(self.origins.iter())
}
fn compute_router_keys_hash(&self) -> [u8; 32] {
Self::hash_ordered_iter(self.router_keys.iter())
}
fn compute_aspas_hash(&self) -> [u8; 32] {
Self::hash_ordered_iter(self.aspas.iter())
}
fn compute_snapshot_hash(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(b"snapshot:v1");
hasher.update(self.origins_hash);
hasher.update(self.router_keys_hash);
hasher.update(self.aspas_hash);
hasher.finalize().into()
}
fn hash_ordered_iter<'a, T, I>(iter: I) -> [u8; 32]
where
T: Serialize + 'a,
I: IntoIterator<Item = &'a T>,
{
let mut hasher = Sha256::new();
hasher.update(b"set:v1");
for item in iter {
let encoded =
serde_json::to_vec(item).expect("serialize snapshot item for hashing failed");
let len = (encoded.len() as u32).to_be_bytes();
hasher.update(len);
hasher.update(encoded);
}
hasher.finalize().into()
}
pub fn diff(&self, new_snapshot: &Snapshot) -> (Vec<Payload>, Vec<Payload>) {
let mut announced = Vec::new();
let mut withdrawn = Vec::new();
if !self.same_origins(new_snapshot) {
for origin in new_snapshot.origins.difference(&self.origins) {
announced.push(Payload::RouteOrigin(origin.clone()));
}
for origin in self.origins.difference(&new_snapshot.origins) {
withdrawn.push(Payload::RouteOrigin(origin.clone()));
}
}
if !self.same_router_keys(new_snapshot) {
for key in new_snapshot.router_keys.difference(&self.router_keys) {
announced.push(Payload::RouterKey(key.clone()));
}
for key in self.router_keys.difference(&new_snapshot.router_keys) {
withdrawn.push(Payload::RouterKey(key.clone()));
}
}
if !self.same_aspas(new_snapshot) {
for aspa in new_snapshot.aspas.difference(&self.aspas) {
announced.push(Payload::Aspa(aspa.clone()));
}
for aspa in self.aspas.difference(&new_snapshot.aspas) {
withdrawn.push(Payload::Aspa(aspa.clone()));
}
}
(announced, withdrawn)
}
pub fn created_at(&self) -> DualTime {
self.created_at.clone()
}
pub fn payloads(&self) -> Vec<Payload> {
let mut v = Vec::with_capacity(
self.origins.len() + self.router_keys.len() + self.aspas.len(),
);
v.extend(self.origins.iter().cloned().map(Payload::RouteOrigin));
v.extend(self.router_keys.iter().cloned().map(Payload::RouterKey));
v.extend(self.aspas.iter().cloned().map(Payload::Aspa));
v
}
/// Payloads sorted for RTR full snapshot sending.
/// Snapshot represents current valid state, so all payloads are treated as announcements.
pub fn payloads_for_rtr(&self) -> Vec<Payload> {
let mut payloads = self.payloads();
sort_payloads_for_rtr(&mut payloads, true);
payloads
}
pub fn origins_hash(&self) -> [u8; 32] {
self.origins_hash
}
pub fn router_keys_hash(&self) -> [u8; 32] {
self.router_keys_hash
}
pub fn aspas_hash(&self) -> [u8; 32] {
self.aspas_hash
}
pub fn snapshot_hash(&self) -> [u8; 32] {
self.snapshot_hash
}
pub fn same_origins(&self, other: &Self) -> bool {
self.origins_hash == other.origins_hash
}
pub fn same_router_keys(&self, other: &Self) -> bool {
self.router_keys_hash == other.router_keys_hash
}
pub fn same_aspas(&self, other: &Self) -> bool {
self.aspas_hash == other.aspas_hash
}
pub fn same_content(&self, other: &Self) -> bool {
self.snapshot_hash == other.snapshot_hash
}
pub fn origins(&self) -> &BTreeSet<RouteOrigin> {
&self.origins
}
pub fn router_keys(&self) -> &BTreeSet<RouterKey> {
&self.router_keys
}
pub fn aspas(&self) -> &BTreeSet<Aspa> {
&self.aspas
}
}
impl Snapshot {
pub fn is_empty(&self) -> bool {
self.origins.is_empty()
&& self.router_keys.is_empty()
&& self.aspas.is_empty()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Delta {
serial: u32,
announced: Vec<Payload>,
withdrawn: Vec<Payload>,
created_at: DualTime,
}
impl Delta {
pub fn new(serial: u32, mut announced: Vec<Payload>, mut withdrawn: Vec<Payload>) -> Self {
sort_payloads_for_rtr(&mut announced, true);
sort_payloads_for_rtr(&mut withdrawn, false);
Delta {
serial,
announced,
withdrawn,
created_at: DualTime::now(),
}
}
pub fn serial(&self) -> u32 {
self.serial
}
pub fn announced(&self) -> &[Payload] {
&self.announced
}
pub fn withdrawn(&self) -> &[Payload] {
&self.withdrawn
}
pub fn created_at(&self) -> DualTime {
self.created_at.clone()
}
}
#[derive(Debug)]
pub struct RtrCache {
// Session ID created at cache startup.
session_id: u16,
// Current serial.
pub serial: u32,
// Full snapshot.
pub snapshot: Snapshot,
// Delta window.
deltas: VecDeque<Arc<Delta>>,
// Max number of deltas to keep.
max_delta: u8,
// Refresh interval.
timing: Timing,
// Last update begin time.
last_update_begin: DualTime,
// Last update end time.
last_update_end: DualTime,
// Cache created time.
created_at: DualTime,
}
impl Default for RtrCache {
fn default() -> Self {
let now = DualTime::now();
Self {
session_id: rand::random(),
serial: 0,
snapshot: Snapshot::empty(),
deltas: VecDeque::with_capacity(100),
max_delta: 100,
timing: Timing::default(),
last_update_begin: now.clone(),
last_update_end: now.clone(),
created_at: now,
}
}
}
pub struct RtrCacheBuilder {
session_id: Option<u16>,
max_delta: Option<u8>,
timing: Option<Timing>,
serial: Option<u32>,
snapshot: Option<Snapshot>,
deltas: Option<VecDeque<Arc<Delta>>>,
created_at: Option<DualTime>,
}
impl RtrCacheBuilder {
pub fn new() -> Self {
Self {
session_id: None,
max_delta: None,
timing: None,
serial: None,
snapshot: None,
deltas: None,
created_at: None,
}
}
pub fn session_id(mut self, v: u16) -> Self {
self.session_id = Some(v);
self
}
pub fn max_delta(mut self, v: u8) -> Self {
self.max_delta = Some(v);
self
}
pub fn timing(mut self, v: Timing) -> Self {
self.timing = Some(v);
self
}
pub fn serial(mut self, v: u32) -> Self {
self.serial = Some(v);
self
}
pub fn snapshot(mut self, v: Snapshot) -> Self {
self.snapshot = Some(v);
self
}
pub fn deltas(mut self, v: VecDeque<Arc<Delta>>) -> Self {
self.deltas = Some(v);
self
}
pub fn created_at(mut self, v: DualTime) -> Self {
self.created_at = Some(v);
self
}
pub fn build(self) -> RtrCache {
let now = DualTime::now();
let max_delta = self.max_delta.unwrap_or(100);
let timing = self.timing.unwrap_or_default();
let snapshot = self.snapshot.unwrap_or_else(Snapshot::empty);
let deltas = self
.deltas
.unwrap_or_else(|| VecDeque::with_capacity(max_delta.into()));
let serial = self.serial.unwrap_or(0);
let created_at = self.created_at.unwrap_or_else(|| now.clone());
let session_id = self.session_id.unwrap_or_else(rand::random);
RtrCache {
session_id,
serial,
snapshot,
deltas,
max_delta,
timing,
last_update_begin: now.clone(),
last_update_end: now,
created_at,
}
}
}
impl RtrCache {
/// Initialize cache from DB if possible; otherwise from file loader.
pub fn init(
self,
store: &RtrStore,
max_delta: u8,
timing: Timing,
file_loader: impl Fn() -> anyhow::Result<Vec<Payload>>,
) -> anyhow::Result<Self> {
if let Some(cache) = Self::try_restore_from_store(store, max_delta, timing)? {
tracing::info!(
"RTR cache restored from store: session_id={}, serial={}",
self.session_id,
self.serial
);
return Ok(cache);
}
tracing::warn!("RTR cache store unavailable or invalid, fallback to file loader");
let payloads = file_loader()?;
let snapshot = Snapshot::from_payloads(payloads);
if snapshot.is_empty() {
anyhow::bail!("file loader returned an empty snapshot");
}
tracing::info!(
"RTR cache initialized from file loader: session_id={}, serial={}",
self.session_id,
self.serial
);
let serial = 1;
let session_id: u16 = rand::random();
let snapshot_for_store = snapshot.clone();
let snapshot_for_cache = snapshot.clone();
let store = store.clone();
tokio::spawn(async move {
if let Err(e) =
store.save_snapshot_and_meta(&snapshot_for_store, session_id, serial)
{
tracing::error!("persist failed: {:?}", e);
}
});
Ok(RtrCacheBuilder::new()
.session_id(session_id)
.max_delta(max_delta)
.timing(timing)
.serial(serial)
.snapshot(snapshot_for_cache)
.build())
}
fn try_restore_from_store(
store: &RtrStore,
max_delta: u8,
timing: Timing,
) -> anyhow::Result<Option<Self>> {
let snapshot = store.get_snapshot()?;
let session_id = store.get_session_id()?;
let serial = store.get_serial()?;
let (snapshot, session_id, serial) = match (snapshot, session_id, serial) {
(Some(snapshot), Some(session_id), Some(serial)) => (snapshot, session_id, serial),
_ => {
tracing::warn!("RTR cache store incomplete: snapshot/session_id/serial missing");
return Ok(None);
}
};
if snapshot.is_empty() {
tracing::warn!("RTR cache store snapshot is empty, treat as unusable");
return Ok(None);
}
let mut cache = RtrCacheBuilder::new()
.session_id(session_id)
.max_delta(max_delta)
.timing(timing)
.serial(serial)
.snapshot(snapshot)
.build();
match store.get_delta_window()? {
Some((min_serial, _max_serial)) => {
let deltas = match store.load_deltas_since(min_serial.wrapping_sub(1)) {
Ok(deltas) => deltas,
Err(err) => {
tracing::warn!(
"RTR cache store delta recovery failed, treat store as unusable: {:?}",
err
);
return Ok(None);
}
};
for delta in deltas {
cache.push_delta(Arc::new(delta));
}
}
None => {
tracing::info!("RTR cache store has no delta window, restore snapshot only");
}
}
Ok(Some(cache))
}
fn next_serial(&mut self) -> u32 {
self.serial = self.serial.wrapping_add(1);
self.serial
}
fn push_delta(&mut self, delta: Arc<Delta>) {
if self.deltas.len() >= self.max_delta as usize {
self.deltas.pop_front();
}
self.deltas.push_back(delta);
}
fn replace_snapshot(&mut self, snapshot: Snapshot) {
self.snapshot = snapshot;
}
fn delta_window(&self) -> Option<(u32, u32)> {
let min = self.deltas.front().map(|d| d.serial());
let max = self.deltas.back().map(|d| d.serial());
match (min, max) {
(Some(min), Some(max)) => Some((min, max)),
_ => None,
}
}
fn store_sync(
&mut self,
store: &RtrStore,
snapshot: Snapshot,
serial: u32,
session_id: u16,
delta: Arc<Delta>,
) {
let window = self.delta_window();
let store = store.clone();
tokio::spawn(async move {
if let Err(e) = store.save_delta(&delta) {
tracing::error!("persist delta failed: {:?}", e);
}
if let Err(e) = store.save_snapshot_and_meta(&snapshot, session_id, serial) {
tracing::error!("persist snapshot/meta failed: {:?}", e);
}
if let Some((min_serial, max_serial)) = window {
if let Err(e) = store.set_delta_window(min_serial, max_serial) {
tracing::error!("persist delta window failed: {:?}", e);
}
}
});
}
// Update cache.
pub fn update(
&mut self,
new_payloads: Vec<Payload>,
store: &RtrStore,
) -> anyhow::Result<()> {
self.last_update_begin = DualTime::now();
let new_snapshot = Snapshot::from_payloads(new_payloads);
if self.snapshot.same_content(&new_snapshot) {
self.last_update_end = DualTime::now();
return Ok(());
}
let (announced, withdrawn) = self.snapshot.diff(&new_snapshot);
if announced.is_empty() && withdrawn.is_empty() {
self.last_update_end = DualTime::now();
return Ok(());
}
let new_serial = self.next_serial();
let delta = Arc::new(Delta::new(new_serial, announced, withdrawn));
self.push_delta(delta.clone());
self.replace_snapshot(new_snapshot.clone());
self.last_update_end = DualTime::now();
self.store_sync(store, new_snapshot, new_serial, self.session_id, delta);
Ok(())
}
pub fn session_id(&self) -> u16 {
self.session_id
}
pub fn snapshot(&self) -> Snapshot {
self.snapshot.clone()
}
pub fn serial(&self) -> u32 {
self.serial
}
pub fn timing(&self) -> Timing {
self.timing
}
pub fn retry_interval(&self) -> Duration {
DEFAULT_RETRY_INTERVAL
}
pub fn expire_interval(&self) -> Duration {
DEFAULT_EXPIRE_INTERVAL
}
pub fn current_snapshot(&self) -> (&Snapshot, u32, u16) {
(&self.snapshot, self.serial, self.session_id)
}
pub fn last_update_begin(&self) -> DualTime {
self.last_update_begin.clone()
}
pub fn last_update_end(&self) -> DualTime {
self.last_update_end.clone()
}
pub fn created_at(&self) -> DualTime {
self.created_at.clone()
}
}
impl RtrCache {
pub fn get_deltas_since(
&self,
client_session: u16,
client_serial: u32,
) -> SerialResult {
if client_session != self.session_id {
return SerialResult::ResetRequired;
}
if client_serial == self.serial {
return SerialResult::UpToDate;
}
if self.deltas.is_empty() {
return SerialResult::ResetRequired;
}
let oldest_serial = self.deltas.front().unwrap().serial;
let newest_serial = self.deltas.back().unwrap().serial;
let min_supported = oldest_serial.wrapping_sub(1);
if client_serial < min_supported {
return SerialResult::ResetRequired;
}
if client_serial > self.serial {
return SerialResult::ResetRequired;
}
let mut result = Vec::new();
for delta in &self.deltas {
if delta.serial > client_serial {
result.push(delta.clone());
}
}
if let Some(first) = result.first() {
if first.serial != client_serial.wrapping_add(1) {
return SerialResult::ResetRequired;
}
} else {
return SerialResult::UpToDate;
}
let _ = newest_serial;
SerialResult::Deltas(result)
}
}
pub enum SerialResult {
/// Client is up to date.
UpToDate,
/// Return applicable deltas.
Deltas(Vec<Arc<Delta>>),
/// Delta window cannot cover; reset required.
ResetRequired,
}
//------------ RTR ordering -------------------------------------------------
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
enum PayloadPduType {
Ipv4Prefix = 4,
Ipv6Prefix = 6,
RouterKey = 9,
Aspa = 11,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum RouteOriginKey {
V4 {
addr: u32,
plen: u8,
mlen: u8,
asn: u32,
},
V6 {
addr: u128,
plen: u8,
mlen: u8,
asn: u32,
},
}
fn sort_payloads_for_rtr(payloads: &mut [Payload], announce: bool) {
payloads.sort_by(|a, b| compare_payload_for_rtr(a, b, announce));
}
fn compare_payload_for_rtr(a: &Payload, b: &Payload, announce: bool) -> Ordering {
let type_a = payload_pdu_type(a);
let type_b = payload_pdu_type(b);
match type_a.cmp(&type_b) {
Ordering::Equal => {}
other => return other,
}
match (a, b) {
(Payload::RouteOrigin(a), Payload::RouteOrigin(b)) => {
compare_route_origin_for_rtr(a, b, announce)
}
(Payload::RouterKey(a), Payload::RouterKey(b)) => {
compare_router_key_for_rtr(a, b)
}
(Payload::Aspa(a), Payload::Aspa(b)) => compare_aspa_for_rtr(a, b),
_ => Ordering::Equal,
}
}
fn payload_pdu_type(payload: &Payload) -> PayloadPduType {
match payload {
Payload::RouteOrigin(ro) => {
if route_origin_is_ipv4(ro) {
PayloadPduType::Ipv4Prefix
} else {
PayloadPduType::Ipv6Prefix
}
}
Payload::RouterKey(_) => PayloadPduType::RouterKey,
Payload::Aspa(_) => PayloadPduType::Aspa,
}
}
fn route_origin_is_ipv4(ro: &RouteOrigin) -> bool {
ro.prefix().address.is_ipv4()
}
fn route_origin_key(ro: &RouteOrigin) -> RouteOriginKey {
let prefix = ro.prefix();
let plen = prefix.prefix_length;
let mlen = ro.max_length();
let asn = ro.asn().into_u32();
match prefix.address {
IPAddress::V4(addr) => {
RouteOriginKey::V4 {
addr: u32::from(addr),
plen,
mlen,
asn,
}
}
IPAddress::V6(addr) => {
RouteOriginKey::V6 {
addr: u128::from(addr),
plen,
mlen,
asn,
}
}
}
}
fn compare_route_origin_for_rtr(
a: &RouteOrigin,
b: &RouteOrigin,
announce: bool,
) -> Ordering {
match (route_origin_key(a), route_origin_key(b)) {
(
RouteOriginKey::V4 {
addr: addr_a,
plen: plen_a,
mlen: mlen_a,
asn: asn_a,
},
RouteOriginKey::V4 {
addr: addr_b,
plen: plen_b,
mlen: mlen_b,
asn: asn_b,
},
) => {
if announce {
addr_b.cmp(&addr_a)
.then_with(|| mlen_b.cmp(&mlen_a))
.then_with(|| plen_b.cmp(&plen_a))
.then_with(|| asn_b.cmp(&asn_a))
} else {
addr_a.cmp(&addr_b)
.then_with(|| mlen_a.cmp(&mlen_b))
.then_with(|| plen_a.cmp(&plen_b))
.then_with(|| asn_a.cmp(&asn_b))
}
}
(
RouteOriginKey::V6 {
addr: addr_a,
plen: plen_a,
mlen: mlen_a,
asn: asn_a,
},
RouteOriginKey::V6 {
addr: addr_b,
plen: plen_b,
mlen: mlen_b,
asn: asn_b,
},
) => {
if announce {
addr_b.cmp(&addr_a)
.then_with(|| mlen_b.cmp(&mlen_a))
.then_with(|| plen_b.cmp(&plen_a))
.then_with(|| asn_b.cmp(&asn_a))
} else {
addr_a.cmp(&addr_b)
.then_with(|| mlen_a.cmp(&mlen_b))
.then_with(|| plen_a.cmp(&plen_b))
.then_with(|| asn_a.cmp(&asn_b))
}
}
_ => Ordering::Equal,
}
}
fn compare_router_key_for_rtr(a: &RouterKey, b: &RouterKey) -> Ordering {
a.ski()
.cmp(&b.ski())
.then_with(|| a.spki().len().cmp(&b.spki().len()))
.then_with(|| a.spki().cmp(b.spki()))
.then_with(|| a.asn().into_u32().cmp(&b.asn().into_u32()))
}
fn compare_aspa_for_rtr(a: &Aspa, b: &Aspa) -> Ordering {
a.customer_asn()
.into_u32()
.cmp(&b.customer_asn().into_u32())
}

632
src/rtr/cache/core.rs vendored Normal file
View File

@@ -0,0 +1,632 @@
use std::collections::{BTreeMap, VecDeque};
use std::cmp::Ordering;
use std::sync::Arc;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};
use crate::rtr::payload::{Payload, Timing};
use super::model::{Delta, DualTime, Snapshot};
use super::ordering::{change_key, ChangeKey};
const SERIAL_HALF_RANGE: u32 = 1 << 31;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
pub enum CacheAvailability {
Ready,
NoDataAvailable,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct SessionIds {
ids: [u16; 3],
}
impl SessionIds {
pub fn from_array(ids: [u16; 3]) -> Self {
Self { ids }
}
pub fn random_distinct() -> Self {
let mut ids = [0u16; 3];
for idx in 0..ids.len() {
loop {
let candidate: u16 = rand::random();
if ids[..idx].iter().all(|existing| *existing != candidate) {
ids[idx] = candidate;
break;
}
}
}
Self { ids }
}
pub fn get(&self, version: u8) -> u16 {
self.ids[version_index(version)]
}
}
#[derive(Debug)]
pub struct RtrCache {
availability: CacheAvailability,
session_ids: SessionIds,
serial: u32,
snapshot: Snapshot,
deltas: VecDeque<Arc<Delta>>,
max_delta: u8,
timing: Timing,
last_update_begin: DualTime,
last_update_end: DualTime,
created_at: DualTime,
}
impl Default for RtrCache {
fn default() -> Self {
let now = DualTime::now();
Self {
availability: CacheAvailability::Ready,
session_ids: SessionIds::random_distinct(),
serial: 0,
snapshot: Snapshot::empty(),
deltas: VecDeque::with_capacity(100),
max_delta: 100,
timing: Timing::default(),
last_update_begin: now.clone(),
last_update_end: now.clone(),
created_at: now,
}
}
}
pub struct RtrCacheBuilder {
availability: Option<CacheAvailability>,
session_ids: Option<SessionIds>,
max_delta: Option<u8>,
timing: Option<Timing>,
serial: Option<u32>,
snapshot: Option<Snapshot>,
deltas: Option<VecDeque<Arc<Delta>>>,
created_at: Option<DualTime>,
}
impl RtrCacheBuilder {
pub fn new() -> Self {
Self {
availability: None,
session_ids: None,
max_delta: None,
timing: None,
serial: None,
snapshot: None,
deltas: None,
created_at: None,
}
}
pub fn session_ids(mut self, v: SessionIds) -> Self {
self.session_ids = Some(v);
self
}
pub fn availability(mut self, v: CacheAvailability) -> Self {
self.availability = Some(v);
self
}
pub fn max_delta(mut self, v: u8) -> Self {
self.max_delta = Some(v);
self
}
pub fn timing(mut self, v: Timing) -> Self {
self.timing = Some(v);
self
}
pub fn serial(mut self, v: u32) -> Self {
self.serial = Some(v);
self
}
pub fn snapshot(mut self, v: Snapshot) -> Self {
self.snapshot = Some(v);
self
}
pub fn deltas(mut self, v: VecDeque<Arc<Delta>>) -> Self {
self.deltas = Some(v);
self
}
pub fn created_at(mut self, v: DualTime) -> Self {
self.created_at = Some(v);
self
}
pub fn build(self) -> RtrCache {
let now = DualTime::now();
let max_delta = self.max_delta.unwrap_or(100);
let timing = self.timing.unwrap_or_default();
let snapshot = self.snapshot.unwrap_or_else(Snapshot::empty);
let deltas = self
.deltas
.unwrap_or_else(|| VecDeque::with_capacity(max_delta.into()));
let serial = self.serial.unwrap_or(0);
let created_at = self.created_at.unwrap_or_else(|| now.clone());
let availability = self.availability.unwrap_or(CacheAvailability::Ready);
let session_ids = self
.session_ids
.unwrap_or_else(SessionIds::random_distinct);
RtrCache {
availability,
session_ids,
serial,
snapshot,
deltas,
max_delta,
timing,
last_update_begin: now.clone(),
last_update_end: now,
created_at,
}
}
}
impl RtrCache {
fn set_unavailable(&mut self) {
warn!(
"RTR cache entering NoDataAvailable: old_serial={}, snapshot_empty={}, delta_count={}",
self.serial,
self.snapshot.is_empty(),
self.deltas.len()
);
self.availability = CacheAvailability::NoDataAvailable;
self.snapshot = Snapshot::empty();
self.deltas.clear();
}
fn reinitialize_from_snapshot(&mut self, snapshot: Snapshot) -> AppliedUpdate {
let old_serial = self.serial;
let old_session_ids = self.session_ids.clone();
self.availability = CacheAvailability::Ready;
self.session_ids = SessionIds::random_distinct();
self.serial = 1;
self.snapshot = snapshot.clone();
self.deltas.clear();
self.last_update_end = DualTime::now();
info!(
"RTR cache reinitialized from usable snapshot: old_serial={}, new_serial={}, old_session_ids={:?}, new_session_ids={:?}, payloads(route_origins={}, router_keys={}, aspas={})",
old_serial,
self.serial,
old_session_ids,
self.session_ids,
snapshot.origins().len(),
snapshot.router_keys().len(),
snapshot.aspas().len()
);
AppliedUpdate {
availability: self.availability,
snapshot,
serial: self.serial,
session_ids: self.session_ids.clone(),
delta: None,
delta_window: None,
clear_delta_window: true,
}
}
fn next_serial(&mut self) -> u32 {
let old = self.serial;
self.serial = self.serial.wrapping_add(1);
debug!(
"RTR cache advanced serial: old_serial={}, new_serial={}",
old,
self.serial
);
self.serial
}
fn push_delta(&mut self, delta: Arc<Delta>) {
let dropped_serial = if self.deltas.len() >= self.max_delta as usize {
self.deltas.front().map(|oldest| oldest.serial())
} else {
None
};
if self.deltas.len() >= self.max_delta as usize {
self.deltas.pop_front();
}
debug!(
"RTR cache pushing delta into window: delta_serial={}, announced={}, withdrawn={}, dropped_oldest_serial={:?}, window_size_before={}, max_delta={}",
delta.serial(),
delta.announced().len(),
delta.withdrawn().len(),
dropped_serial,
self.deltas.len(),
self.max_delta
);
self.deltas.push_back(delta);
}
fn replace_snapshot(&mut self, snapshot: Snapshot) {
self.snapshot = snapshot;
}
fn delta_window(&self) -> Option<(u32, u32)> {
let min = self.deltas.front().map(|d| d.serial());
let max = self.deltas.back().map(|d| d.serial());
match (min, max) {
(Some(min), Some(max)) => Some((min, max)),
_ => None,
}
}
pub(super) fn apply_update(&mut self, new_payloads: Vec<Payload>) -> Result<Option<AppliedUpdate>> {
self.last_update_begin = DualTime::now();
info!(
"RTR cache applying update: availability={:?}, current_serial={}, incoming_payloads={}",
self.availability,
self.serial,
new_payloads.len()
);
let new_snapshot = Snapshot::from_payloads(new_payloads);
debug!(
"RTR cache built new snapshot from update: route_origins={}, router_keys={}, aspas={}, snapshot_empty={}",
new_snapshot.origins().len(),
new_snapshot.router_keys().len(),
new_snapshot.aspas().len(),
new_snapshot.is_empty()
);
if new_snapshot.is_empty() {
let changed = self.availability != CacheAvailability::NoDataAvailable
|| !self.snapshot.is_empty()
|| !self.deltas.is_empty();
self.set_unavailable();
self.last_update_end = DualTime::now();
if !changed {
debug!("RTR cache update produced empty snapshot but cache was already unavailable; no state change");
return Ok(None);
}
info!(
"RTR cache update cleared usable data and marked cache unavailable: serial={}, session_ids={:?}",
self.serial,
self.session_ids
);
return Ok(Some(AppliedUpdate {
availability: self.availability,
snapshot: Snapshot::empty(),
serial: self.serial,
session_ids: self.session_ids.clone(),
delta: None,
delta_window: None,
clear_delta_window: true,
}));
}
if self.availability == CacheAvailability::NoDataAvailable {
info!("RTR cache recovered from NoDataAvailable with non-empty snapshot");
return Ok(Some(self.reinitialize_from_snapshot(new_snapshot)));
}
if self.snapshot.same_content(&new_snapshot) {
self.last_update_end = DualTime::now();
debug!(
"RTR cache update detected identical snapshot content: serial={}, session_ids={:?}",
self.serial,
self.session_ids
);
return Ok(None);
}
let (announced, withdrawn) = self.snapshot.diff(&new_snapshot);
debug!(
"RTR cache diff computed: announced={}, withdrawn={}, current_serial={}",
announced.len(),
withdrawn.len(),
self.serial
);
if announced.is_empty() && withdrawn.is_empty() {
self.last_update_end = DualTime::now();
debug!("RTR cache diff was empty after normalization; no update applied");
return Ok(None);
}
let new_serial = self.next_serial();
let delta = Arc::new(Delta::new(new_serial, announced, withdrawn));
if delta.is_empty() {
self.last_update_end = DualTime::now();
debug!(
"RTR cache delta collapsed to empty after dedup/order normalization: serial={}",
new_serial
);
return Ok(None);
}
self.push_delta(delta.clone());
self.replace_snapshot(new_snapshot.clone());
self.last_update_end = DualTime::now();
let delta_window = self.delta_window();
info!(
"RTR cache applied update: serial={}, announced={}, withdrawn={}, delta_window={:?}, snapshot(route_origins={}, router_keys={}, aspas={})",
new_serial,
delta.announced().len(),
delta.withdrawn().len(),
delta_window,
new_snapshot.origins().len(),
new_snapshot.router_keys().len(),
new_snapshot.aspas().len()
);
Ok(Some(AppliedUpdate {
availability: self.availability,
snapshot: new_snapshot,
serial: new_serial,
session_ids: self.session_ids.clone(),
delta: Some(delta),
delta_window,
clear_delta_window: false,
}))
}
pub fn is_data_available(&self) -> bool {
self.availability == CacheAvailability::Ready
}
pub fn availability(&self) -> CacheAvailability {
self.availability
}
pub fn session_id_for_version(&self, version: u8) -> u16 {
self.session_ids.get(version)
}
pub fn session_ids(&self) -> SessionIds {
self.session_ids.clone()
}
pub fn snapshot(&self) -> Snapshot {
self.snapshot.clone()
}
pub fn serial(&self) -> u32 {
self.serial
}
pub fn timing(&self) -> Timing {
self.timing
}
pub fn current_snapshot_with_session_ids(&self) -> (&Snapshot, u32, SessionIds) {
(&self.snapshot, self.serial, self.session_ids.clone())
}
pub fn last_update_begin(&self) -> DualTime {
self.last_update_begin.clone()
}
pub fn last_update_end(&self) -> DualTime {
self.last_update_end.clone()
}
pub fn created_at(&self) -> DualTime {
self.created_at.clone()
}
pub fn get_deltas_since(&self, client_serial: u32) -> SerialResult {
if client_serial == self.serial {
debug!(
"RTR cache delta query is already up to date: client_serial={}, cache_serial={}",
client_serial,
self.serial
);
return SerialResult::UpToDate;
}
if matches!(
serial_cmp(client_serial, self.serial),
Some(Ordering::Greater) | None
) {
warn!(
"RTR cache delta query requires reset due to invalid/newer client serial: client_serial={}, cache_serial={}",
client_serial,
self.serial
);
return SerialResult::ResetRequired;
}
let deltas = match self.collect_deltas_since(client_serial) {
Some(deltas) => deltas,
None => {
warn!(
"RTR cache delta query requires reset because requested serial is outside delta window: client_serial={}, cache_serial={}, delta_window={:?}",
client_serial,
self.serial,
self.delta_window()
);
return SerialResult::ResetRequired;
}
};
if deltas.is_empty() {
debug!(
"RTR cache delta query resolved to no deltas: client_serial={}, cache_serial={}",
client_serial,
self.serial
);
return SerialResult::UpToDate;
}
let merged = self.merge_deltas_minimally(&deltas);
if merged.is_empty() {
debug!(
"RTR cache merged delta query to empty result: client_serial={}, cache_serial={}, source_deltas={}",
client_serial,
self.serial,
deltas.len()
);
SerialResult::UpToDate
} else {
info!(
"RTR cache serving delta query: client_serial={}, cache_serial={}, source_deltas={}, merged_announced={}, merged_withdrawn={}",
client_serial,
self.serial,
deltas.len(),
merged.announced().len(),
merged.withdrawn().len()
);
SerialResult::Delta(merged)
}
}
fn collect_deltas_since(&self, client_serial: u32) -> Option<Vec<Arc<Delta>>> {
if self.deltas.is_empty() {
return None;
}
let oldest_serial = self.deltas.front().unwrap().serial();
let min_supported = oldest_serial.wrapping_sub(1);
if matches!(
serial_cmp(client_serial, min_supported),
Some(Ordering::Less) | None
) {
return None;
}
let mut result = Vec::new();
for delta in &self.deltas {
if serial_gt(delta.serial(), client_serial) {
result.push(delta.clone());
}
}
if let Some(first) = result.first() {
if first.serial() != client_serial.wrapping_add(1) {
return None;
}
}
Some(result)
}
fn merge_deltas_minimally(&self, deltas: &[Arc<Delta>]) -> Delta {
let mut states = BTreeMap::<ChangeKey, LogicalState>::new();
for delta in deltas {
for payload in delta.withdrawn() {
let key = change_key(payload);
let state = states.entry(key).or_insert_with(LogicalState::new);
if state.before.is_none() && state.after.is_none() {
state.before = Some(payload.clone());
}
state.after = None;
}
for payload in delta.announced() {
let key = change_key(payload);
let state = states.entry(key).or_insert_with(LogicalState::new);
state.after = Some(payload.clone());
}
}
let mut announced = Vec::new();
let mut withdrawn = Vec::new();
for (_key, state) in states {
match (state.before, state.after) {
(None, None) => {}
(None, Some(new_payload)) => {
announced.push(new_payload);
}
(Some(old_payload), None) => {
withdrawn.push(old_payload);
}
(Some(old_payload), Some(new_payload)) => {
if old_payload != new_payload {
if matches!(old_payload, Payload::Aspa(_))
&& matches!(new_payload, Payload::Aspa(_))
{
announced.push(new_payload);
} else {
withdrawn.push(old_payload);
announced.push(new_payload);
}
}
}
}
}
Delta::new(self.serial, announced, withdrawn)
}
}
#[derive(Debug, Clone, Default)]
struct LogicalState {
before: Option<Payload>,
after: Option<Payload>,
}
impl LogicalState {
fn new() -> Self {
Self {
before: None,
after: None,
}
}
}
pub enum SerialResult {
UpToDate,
Delta(Delta),
ResetRequired,
}
pub(super) struct AppliedUpdate {
pub(super) availability: CacheAvailability,
pub(super) snapshot: Snapshot,
pub(super) serial: u32,
pub(super) session_ids: SessionIds,
pub(super) delta: Option<Arc<Delta>>,
pub(super) delta_window: Option<(u32, u32)>,
pub(super) clear_delta_window: bool,
}
fn serial_cmp(a: u32, b: u32) -> Option<Ordering> {
if a == b {
return Some(Ordering::Equal);
}
let diff = a.wrapping_sub(b);
if diff == SERIAL_HALF_RANGE {
None
} else if diff < SERIAL_HALF_RANGE {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
}
}
fn serial_gt(a: u32, b: u32) -> bool {
matches!(serial_cmp(a, b), Some(Ordering::Greater))
}
fn version_index(version: u8) -> usize {
match version {
0..=2 => version as usize,
_ => panic!("unsupported RTR protocol version: {}", version),
}
}

14
src/rtr/cache/mod.rs vendored Normal file
View File

@@ -0,0 +1,14 @@
mod core;
mod model;
mod ordering;
mod store;
pub use core::{CacheAvailability, RtrCache, RtrCacheBuilder, SerialResult, SessionIds};
pub use model::{Delta, DualTime, Snapshot};
pub use ordering::{
OrderingViolation, validate_payload_updates_for_rtr, validate_payloads_for_rtr,
};
use std::sync::{Arc, RwLock};
pub type SharedRtrCache = Arc<RwLock<RtrCache>>;

392
src/rtr/cache/model.rs vendored Normal file
View File

@@ -0,0 +1,392 @@
use std::collections::{BTreeMap, BTreeSet};
use std::time::{Duration, Instant};
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey};
use super::ordering::{compare_payload_update_for_rtr, sort_payloads_for_rtr};
#[derive(Debug, Clone)]
pub struct DualTime {
instant: Instant,
utc: DateTime<Utc>,
}
impl DualTime {
pub fn now() -> Self {
Self {
instant: Instant::now(),
utc: Utc::now(),
}
}
pub fn utc(&self) -> DateTime<Utc> {
self.utc
}
pub fn elapsed(&self) -> Duration {
self.instant.elapsed()
}
pub fn is_expired(&self, duration: Duration) -> bool {
self.elapsed() >= duration
}
pub fn reset(&mut self) {
self.instant = Instant::now();
self.utc = Utc::now();
}
}
impl Serialize for DualTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.utc.timestamp_millis().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for DualTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let millis = i64::deserialize(deserializer)?;
let naive = NaiveDateTime::from_timestamp_millis(millis)
.ok_or_else(|| serde::de::Error::custom("invalid timestamp"))?;
let utc = DateTime::<Utc>::from_utc(naive, Utc);
Ok(Self {
instant: Instant::now(),
utc,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snapshot {
origins: BTreeSet<RouteOrigin>,
router_keys: BTreeSet<RouterKey>,
aspas: BTreeSet<Aspa>,
created_at: DualTime,
origins_hash: [u8; 32],
router_keys_hash: [u8; 32],
aspas_hash: [u8; 32],
snapshot_hash: [u8; 32],
}
impl Snapshot {
pub fn new(
origins: BTreeSet<RouteOrigin>,
router_keys: BTreeSet<RouterKey>,
aspas: BTreeSet<Aspa>,
) -> Self {
let mut snapshot = Snapshot {
origins,
router_keys,
aspas: normalize_aspas(aspas),
created_at: DualTime::now(),
origins_hash: [0u8; 32],
router_keys_hash: [0u8; 32],
aspas_hash: [0u8; 32],
snapshot_hash: [0u8; 32],
};
snapshot.recompute_hashes();
snapshot
}
pub fn empty() -> Self {
Self::new(BTreeSet::new(), BTreeSet::new(), BTreeSet::new())
}
pub fn from_payloads(payloads: Vec<Payload>) -> Self {
let mut origins = BTreeSet::new();
let mut router_keys = BTreeSet::new();
let mut aspas = Vec::new();
for p in payloads {
match p {
Payload::RouteOrigin(o) => {
origins.insert(o);
}
Payload::RouterKey(k) => {
router_keys.insert(k);
}
Payload::Aspa(a) => {
aspas.push(a);
}
}
}
Snapshot::new(origins, router_keys, normalize_aspas(aspas))
}
pub fn recompute_hashes(&mut self) {
self.origins_hash = self.compute_origins_hash();
self.router_keys_hash = self.compute_router_keys_hash();
self.aspas_hash = self.compute_aspas_hash();
self.snapshot_hash = self.compute_snapshot_hash();
}
fn compute_origins_hash(&self) -> [u8; 32] {
Self::hash_ordered_iter(self.origins.iter())
}
fn compute_router_keys_hash(&self) -> [u8; 32] {
Self::hash_ordered_iter(self.router_keys.iter())
}
fn compute_aspas_hash(&self) -> [u8; 32] {
Self::hash_ordered_iter(self.aspas.iter())
}
fn compute_snapshot_hash(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(b"snapshot:v1");
hasher.update(self.origins_hash);
hasher.update(self.router_keys_hash);
hasher.update(self.aspas_hash);
hasher.finalize().into()
}
fn hash_ordered_iter<'a, T, I>(iter: I) -> [u8; 32]
where
T: Serialize + 'a,
I: IntoIterator<Item = &'a T>,
{
let mut hasher = Sha256::new();
hasher.update(b"set:v1");
for item in iter {
let encoded =
serde_json::to_vec(item).expect("serialize snapshot item for hashing failed");
let len = (encoded.len() as u32).to_be_bytes();
hasher.update(len);
hasher.update(encoded);
}
hasher.finalize().into()
}
pub fn diff(&self, new_snapshot: &Snapshot) -> (Vec<Payload>, Vec<Payload>) {
let mut announced = Vec::new();
let mut withdrawn = Vec::new();
if !self.same_origins(new_snapshot) {
for origin in new_snapshot.origins.difference(&self.origins) {
announced.push(Payload::RouteOrigin(origin.clone()));
}
for origin in self.origins.difference(&new_snapshot.origins) {
withdrawn.push(Payload::RouteOrigin(origin.clone()));
}
}
if !self.same_router_keys(new_snapshot) {
for key in new_snapshot.router_keys.difference(&self.router_keys) {
announced.push(Payload::RouterKey(key.clone()));
}
for key in self.router_keys.difference(&new_snapshot.router_keys) {
withdrawn.push(Payload::RouterKey(key.clone()));
}
}
if !self.same_aspas(new_snapshot) {
diff_aspas(&self.aspas, &new_snapshot.aspas, &mut announced, &mut withdrawn);
}
(announced, withdrawn)
}
pub fn created_at(&self) -> DualTime {
self.created_at.clone()
}
pub fn payloads(&self) -> Vec<Payload> {
let mut v = Vec::with_capacity(
self.origins.len() + self.router_keys.len() + self.aspas.len(),
);
v.extend(self.origins.iter().cloned().map(Payload::RouteOrigin));
v.extend(self.router_keys.iter().cloned().map(Payload::RouterKey));
v.extend(self.aspas.iter().cloned().map(Payload::Aspa));
v
}
pub fn payloads_for_rtr(&self) -> Vec<Payload> {
let mut payloads = self.payloads();
sort_payloads_for_rtr(&mut payloads, true);
payloads
}
pub fn origins_hash(&self) -> [u8; 32] {
self.origins_hash
}
pub fn router_keys_hash(&self) -> [u8; 32] {
self.router_keys_hash
}
pub fn aspas_hash(&self) -> [u8; 32] {
self.aspas_hash
}
pub fn snapshot_hash(&self) -> [u8; 32] {
self.snapshot_hash
}
pub fn same_origins(&self, other: &Self) -> bool {
self.origins_hash == other.origins_hash
}
pub fn same_router_keys(&self, other: &Self) -> bool {
self.router_keys_hash == other.router_keys_hash
}
pub fn same_aspas(&self, other: &Self) -> bool {
self.aspas_hash == other.aspas_hash
}
pub fn same_content(&self, other: &Self) -> bool {
self.snapshot_hash == other.snapshot_hash
}
pub fn origins(&self) -> &BTreeSet<RouteOrigin> {
&self.origins
}
pub fn router_keys(&self) -> &BTreeSet<RouterKey> {
&self.router_keys
}
pub fn aspas(&self) -> &BTreeSet<Aspa> {
&self.aspas
}
pub fn is_empty(&self) -> bool {
self.origins.is_empty()
&& self.router_keys.is_empty()
&& self.aspas.is_empty()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Delta {
serial: u32,
announced: Vec<Payload>,
withdrawn: Vec<Payload>,
created_at: DualTime,
}
impl Delta {
pub fn new(serial: u32, mut announced: Vec<Payload>, mut withdrawn: Vec<Payload>) -> Self {
dedup_payloads(&mut announced);
dedup_payloads(&mut withdrawn);
sort_payloads_for_rtr(&mut announced, true);
sort_payloads_for_rtr(&mut withdrawn, false);
Delta {
serial,
announced,
withdrawn,
created_at: DualTime::now(),
}
}
pub fn serial(&self) -> u32 {
self.serial
}
pub fn announced(&self) -> &[Payload] {
&self.announced
}
pub fn withdrawn(&self) -> &[Payload] {
&self.withdrawn
}
pub fn created_at(&self) -> DualTime {
self.created_at.clone()
}
pub fn is_empty(&self) -> bool {
self.announced.is_empty() && self.withdrawn.is_empty()
}
pub fn payloads_for_rtr(&self) -> Vec<(bool, Payload)> {
let mut updates = Vec::with_capacity(self.announced.len() + self.withdrawn.len());
updates.extend(self.announced.iter().cloned().map(|p| (true, p)));
updates.extend(self.withdrawn.iter().cloned().map(|p| (false, p)));
updates.sort_by(|(a_upd, a_payload), (b_upd, b_payload)| {
compare_payload_update_for_rtr(a_payload, *a_upd, b_payload, *b_upd)
});
updates
}
}
fn dedup_payloads(payloads: &mut Vec<Payload>) {
let mut seen = BTreeSet::new();
payloads.retain(|p| seen.insert(p.clone()));
}
fn normalize_aspas<I>(aspas: I) -> BTreeSet<Aspa>
where
I: IntoIterator<Item = Aspa>,
{
let mut by_customer = BTreeMap::<u32, BTreeSet<_>>::new();
for aspa in aspas {
let providers = by_customer
.entry(aspa.customer_asn().into_u32())
.or_default();
providers.extend(aspa.provider_asns().iter().copied());
}
by_customer
.into_iter()
.map(|(customer_asn, providers)| {
Aspa::new(customer_asn.into(), providers.into_iter().collect())
})
.collect()
}
fn diff_aspas(
current: &BTreeSet<Aspa>,
next: &BTreeSet<Aspa>,
announced: &mut Vec<Payload>,
withdrawn: &mut Vec<Payload>,
) {
let current = current
.iter()
.map(|aspa| (aspa.customer_asn().into_u32(), aspa))
.collect::<BTreeMap<_, _>>();
let next = next
.iter()
.map(|aspa| (aspa.customer_asn().into_u32(), aspa))
.collect::<BTreeMap<_, _>>();
let customers = current
.keys()
.chain(next.keys())
.copied()
.collect::<BTreeSet<_>>();
for customer in customers {
match (current.get(&customer), next.get(&customer)) {
(None, Some(new_aspa)) => announced.push(Payload::Aspa((*new_aspa).clone())),
(Some(old_aspa), None) => withdrawn.push(Payload::Aspa((*old_aspa).clone())),
(Some(old_aspa), Some(new_aspa)) if old_aspa != new_aspa => {
announced.push(Payload::Aspa((*new_aspa).clone()));
}
_ => {}
}
}
}

311
src/rtr/cache/ordering.rs vendored Normal file
View File

@@ -0,0 +1,311 @@
use std::cmp::Ordering;
use std::fmt;
use crate::data_model::resources::ip_resources::IPAddress;
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct OrderingViolation {
index: usize,
left: String,
right: String,
}
impl OrderingViolation {
fn new(index: usize, left: &Payload, right: &Payload) -> Self {
Self {
index,
left: payload_brief(left),
right: payload_brief(right),
}
}
fn new_update(index: usize, left: (bool, &Payload), right: (bool, &Payload)) -> Self {
Self {
index,
left: payload_update_brief(left.0, left.1),
right: payload_update_brief(right.0, right.1),
}
}
}
impl fmt::Display for OrderingViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"RTR payload ordering violation at positions {} and {}: {} should not appear before {}",
self.index,
self.index + 1,
self.left,
self.right
)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum RouterKeyKey {
Key { ski: Ski, spki: Vec<u8>, asn: u32 },
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum ChangeKey {
RouteOrigin(RouteOriginKey),
RouterKey(RouterKeyKey),
AspaCustomer(u32),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
enum PayloadPduType {
Ipv4Prefix = 4,
Ipv6Prefix = 6,
RouterKey = 9,
Aspa = 11,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum RouteOriginKey {
V4 { addr: u32, plen: u8, mlen: u8, asn: u32 },
V6 { addr: u128, plen: u8, mlen: u8, asn: u32 },
}
pub(crate) fn change_key(payload: &Payload) -> ChangeKey {
match payload {
Payload::RouteOrigin(ro) => ChangeKey::RouteOrigin(route_origin_key(ro)),
Payload::RouterKey(rk) => ChangeKey::RouterKey(router_key_key(rk)),
Payload::Aspa(aspa) => ChangeKey::AspaCustomer(aspa.customer_asn().into_u32()),
}
}
pub(crate) fn compare_payload_update_for_rtr(
a_payload: &Payload,
a_announce: bool,
b_payload: &Payload,
b_announce: bool,
) -> Ordering {
let type_a = payload_pdu_type(a_payload);
let type_b = payload_pdu_type(b_payload);
match type_a.cmp(&type_b) {
Ordering::Equal => {}
other => return other,
}
match b_announce.cmp(&a_announce) {
Ordering::Equal => {}
other => return other,
}
match (a_payload, b_payload) {
(Payload::RouteOrigin(a), Payload::RouteOrigin(b)) => {
compare_route_origin_for_rtr(a, b, a_announce)
}
(Payload::RouterKey(a), Payload::RouterKey(b)) => compare_router_key_for_rtr(a, b),
(Payload::Aspa(a), Payload::Aspa(b)) => compare_aspa_for_rtr(a, b),
_ => Ordering::Equal,
}
}
pub(crate) fn sort_payloads_for_rtr(payloads: &mut [Payload], announce: bool) {
payloads.sort_by(|a, b| compare_payload_for_rtr(a, b, announce));
}
pub fn validate_payloads_for_rtr(
payloads: &[Payload],
announce: bool,
) -> Result<(), OrderingViolation> {
for (index, pair) in payloads.windows(2).enumerate() {
if compare_payload_for_rtr(&pair[0], &pair[1], announce) == Ordering::Greater {
return Err(OrderingViolation::new(index, &pair[0], &pair[1]));
}
}
Ok(())
}
pub fn validate_payload_updates_for_rtr(
updates: &[(bool, Payload)],
) -> Result<(), OrderingViolation> {
for (index, pair) in updates.windows(2).enumerate() {
if compare_payload_update_for_rtr(&pair[0].1, pair[0].0, &pair[1].1, pair[1].0)
== Ordering::Greater
{
return Err(OrderingViolation::new_update(
index,
(pair[0].0, &pair[0].1),
(pair[1].0, &pair[1].1),
));
}
}
Ok(())
}
fn router_key_key(rk: &RouterKey) -> RouterKeyKey {
RouterKeyKey::Key {
ski: rk.ski(),
spki: rk.spki().as_ref().to_vec(),
asn: rk.asn().into_u32(),
}
}
fn compare_payload_for_rtr(a: &Payload, b: &Payload, announce: bool) -> Ordering {
let type_a = payload_pdu_type(a);
let type_b = payload_pdu_type(b);
match type_a.cmp(&type_b) {
Ordering::Equal => {}
other => return other,
}
match (a, b) {
(Payload::RouteOrigin(a), Payload::RouteOrigin(b)) => {
compare_route_origin_for_rtr(a, b, announce)
}
(Payload::RouterKey(a), Payload::RouterKey(b)) => compare_router_key_for_rtr(a, b),
(Payload::Aspa(a), Payload::Aspa(b)) => compare_aspa_for_rtr(a, b),
_ => Ordering::Equal,
}
}
fn payload_pdu_type(payload: &Payload) -> PayloadPduType {
match payload {
Payload::RouteOrigin(ro) => {
if route_origin_is_ipv4(ro) {
PayloadPduType::Ipv4Prefix
} else {
PayloadPduType::Ipv6Prefix
}
}
Payload::RouterKey(_) => PayloadPduType::RouterKey,
Payload::Aspa(_) => PayloadPduType::Aspa,
}
}
fn route_origin_is_ipv4(ro: &RouteOrigin) -> bool {
ro.prefix().address.is_ipv4()
}
fn route_origin_key(ro: &RouteOrigin) -> RouteOriginKey {
let prefix = ro.prefix();
let plen = prefix.prefix_length;
let mlen = ro.max_length();
let asn = ro.asn().into_u32();
match prefix.address {
IPAddress::V4(addr) => RouteOriginKey::V4 {
addr: u32::from(addr),
plen,
mlen,
asn,
},
IPAddress::V6(addr) => RouteOriginKey::V6 {
addr: u128::from(addr),
plen,
mlen,
asn,
},
}
}
fn compare_route_origin_for_rtr(a: &RouteOrigin, b: &RouteOrigin, announce: bool) -> Ordering {
match (route_origin_key(a), route_origin_key(b)) {
(
RouteOriginKey::V4 {
addr: addr_a,
plen: plen_a,
mlen: mlen_a,
asn: asn_a,
},
RouteOriginKey::V4 {
addr: addr_b,
plen: plen_b,
mlen: mlen_b,
asn: asn_b,
},
) => {
if announce {
addr_b
.cmp(&addr_a)
.then_with(|| mlen_b.cmp(&mlen_a))
.then_with(|| plen_b.cmp(&plen_a))
.then_with(|| asn_b.cmp(&asn_a))
} else {
addr_a
.cmp(&addr_b)
.then_with(|| mlen_a.cmp(&mlen_b))
.then_with(|| plen_a.cmp(&plen_b))
.then_with(|| asn_a.cmp(&asn_b))
}
}
(
RouteOriginKey::V6 {
addr: addr_a,
plen: plen_a,
mlen: mlen_a,
asn: asn_a,
},
RouteOriginKey::V6 {
addr: addr_b,
plen: plen_b,
mlen: mlen_b,
asn: asn_b,
},
) => {
if announce {
addr_b
.cmp(&addr_a)
.then_with(|| mlen_b.cmp(&mlen_a))
.then_with(|| plen_b.cmp(&plen_a))
.then_with(|| asn_b.cmp(&asn_a))
} else {
addr_a
.cmp(&addr_b)
.then_with(|| mlen_a.cmp(&mlen_b))
.then_with(|| plen_a.cmp(&plen_b))
.then_with(|| asn_a.cmp(&asn_b))
}
}
_ => Ordering::Equal,
}
}
fn compare_router_key_for_rtr(a: &RouterKey, b: &RouterKey) -> Ordering {
a.ski()
.cmp(&b.ski())
.then_with(|| a.spki().len().cmp(&b.spki().len()))
.then_with(|| a.spki().cmp(b.spki()))
.then_with(|| a.asn().into_u32().cmp(&b.asn().into_u32()))
}
fn compare_aspa_for_rtr(a: &Aspa, b: &Aspa) -> Ordering {
a.customer_asn()
.into_u32()
.cmp(&b.customer_asn().into_u32())
}
fn payload_brief(payload: &Payload) -> String {
match payload {
Payload::RouteOrigin(origin) => format!(
"{} prefix {:?}/{} max={} asn={}",
if route_origin_is_ipv4(origin) { "IPv4" } else { "IPv6" },
origin.prefix().address,
origin.prefix().prefix_length,
origin.max_length(),
origin.asn().into_u32()
),
Payload::RouterKey(key) => format!(
"RouterKey ski={:02x?} asn={}",
key.ski().as_ref(),
key.asn().into_u32()
),
Payload::Aspa(aspa) => format!("ASPA customer_asn={}", aspa.customer_asn().into_u32()),
}
}
fn payload_update_brief(announce: bool, payload: &Payload) -> String {
format!(
"{} {}",
if announce { "announce" } else { "withdraw" },
payload_brief(payload)
)
}

195
src/rtr/cache/store.rs vendored Normal file
View File

@@ -0,0 +1,195 @@
use std::collections::VecDeque;
use std::sync::Arc;
use anyhow::Result;
use crate::rtr::payload::{Payload, Timing};
use crate::rtr::store::RtrStore;
use super::core::{AppliedUpdate, CacheAvailability, RtrCache, RtrCacheBuilder, SessionIds};
use super::model::Snapshot;
impl RtrCache {
pub fn init(
self,
store: &RtrStore,
max_delta: u8,
timing: Timing,
file_loader: impl Fn() -> Result<Vec<Payload>>,
) -> Result<Self> {
if let Some(cache) = try_restore_from_store(store, max_delta, timing)? {
tracing::info!(
"RTR cache restored from store: availability={:?}, session_ids={:?}, serial={}, snapshot(route_origins={}, router_keys={}, aspas={})",
cache.availability(),
cache.session_ids(),
cache.serial(),
cache.snapshot().origins().len(),
cache.snapshot().router_keys().len(),
cache.snapshot().aspas().len()
);
return Ok(cache);
}
tracing::warn!("RTR cache store unavailable or invalid, fallback to file loader");
let payloads = file_loader()?;
let session_ids = SessionIds::random_distinct();
let snapshot = Snapshot::from_payloads(payloads);
let availability = if snapshot.is_empty() {
CacheAvailability::NoDataAvailable
} else {
CacheAvailability::Ready
};
let serial = if snapshot.is_empty() { 0 } else { 1 };
if snapshot.is_empty() {
tracing::warn!(
"RTR cache initialized without usable data: session_ids={:?}, serial={}",
session_ids,
serial
);
} else {
tracing::info!(
"RTR cache initialized from file loader: session_ids={:?}, serial={}",
session_ids,
serial
);
}
let snapshot_for_store = snapshot.clone();
let session_ids_for_store = session_ids.clone();
tokio::spawn({
let store = store.clone();
async move {
if let Err(e) = store.save_cache_state(
availability,
&snapshot_for_store,
&session_ids_for_store,
serial,
None,
None,
true,
) {
tracing::error!("persist cache state failed: {:?}", e);
}
}
});
Ok(RtrCacheBuilder::new()
.availability(availability)
.session_ids(session_ids)
.max_delta(max_delta)
.timing(timing)
.serial(serial)
.snapshot(snapshot)
.build())
}
pub fn update(&mut self, new_payloads: Vec<Payload>, store: &RtrStore) -> Result<()> {
if let Some(update) = self.apply_update(new_payloads)? {
spawn_store_sync(store, update);
}
Ok(())
}
}
fn try_restore_from_store(store: &RtrStore, max_delta: u8, timing: Timing) -> Result<Option<RtrCache>> {
let snapshot = store.get_snapshot()?;
let session_ids = store.get_session_ids()?;
let serial = store.get_serial()?;
let availability = store.get_availability()?;
let (snapshot, session_ids, serial) = match (snapshot, session_ids, serial) {
(Some(snapshot), Some(session_ids), Some(serial)) => (snapshot, session_ids, serial),
_ => {
tracing::warn!("RTR cache store incomplete: snapshot/session_ids/serial missing");
return Ok(None);
}
};
let availability = availability.unwrap_or_else(|| {
tracing::warn!("RTR cache store missing availability metadata, defaulting to Ready");
CacheAvailability::Ready
});
let deltas = if availability == CacheAvailability::NoDataAvailable {
tracing::warn!("RTR cache store restored in no-data-available state");
VecDeque::with_capacity(max_delta.into())
} else {
match store.get_delta_window()? {
Some((min_serial, max_serial)) => {
match store.load_delta_window(min_serial, max_serial) {
Ok(deltas) => deltas.into_iter().map(Arc::new).collect(),
Err(err) => {
tracing::warn!(
"RTR cache store delta recovery failed, treat store as unusable: {:?}",
err
);
return Ok(None);
}
}
}
None => {
tracing::info!("RTR cache store has no delta window, restore snapshot only");
VecDeque::with_capacity(max_delta.into())
}
}
};
Ok(Some(
RtrCacheBuilder::new()
.availability(availability)
.session_ids(session_ids)
.max_delta(max_delta)
.timing(timing)
.serial(serial)
.snapshot(snapshot)
.deltas(deltas)
.build(),
))
}
fn spawn_store_sync(store: &RtrStore, update: AppliedUpdate) {
let AppliedUpdate {
availability,
snapshot,
serial,
session_ids,
delta,
delta_window,
clear_delta_window,
} = update;
tokio::spawn({
let store = store.clone();
async move {
tracing::debug!(
"persisting RTR cache state: availability={:?}, serial={}, session_ids={:?}, delta_present={}, delta_window={:?}, clear_delta_window={}, snapshot(route_origins={}, router_keys={}, aspas={})",
availability,
serial,
session_ids,
delta.is_some(),
delta_window,
clear_delta_window,
snapshot.origins().len(),
snapshot.router_keys().len(),
snapshot.aspas().len()
);
if let Err(e) = store.save_cache_state(
availability,
&snapshot,
&session_ids,
serial,
delta.as_deref(),
delta_window,
clear_delta_window,
) {
tracing::error!("persist cache state failed: {:?}", e);
} else {
tracing::debug!("persist RTR cache state completed: serial={}", serial);
}
}
});
}

View File

@@ -1,9 +1,9 @@
pub mod pdu;
pub mod cache;
pub mod payload;
pub mod store_db;
pub mod store;
pub mod session;
pub mod error_type;
pub mod state;
pub mod server;
pub mod loader;
pub mod loader;

View File

@@ -1,4 +1,5 @@
use std::fmt::Debug;
use std::io;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::data_model::resources::as_resources::Asn;
@@ -16,6 +17,11 @@ enum PayloadPduType {
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Ski([u8; 20]);
impl AsRef<[u8]> for Ski {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct RouteOrigin {
@@ -100,10 +106,35 @@ impl Aspa {
pub fn provider_asns(&self) -> &[Asn] {
&self.provider_asns
}
pub fn validate_announcement(&self) -> Result<(), io::Error> {
if self.customer_asn.into_u32() == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ASPA customer ASN must not be AS0",
));
}
if self.provider_asns.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ASPA announcement must contain at least one provider ASN",
));
}
if self.provider_asns.iter().any(|asn| asn.into_u32() == 0) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ASPA provider list must not contain AS0",
));
}
Ok(())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Payload {
/// A route origin.
@@ -131,10 +162,71 @@ pub struct Timing {
}
impl Timing {
pub const MIN_REFRESH: u32 = 1;
pub const MAX_REFRESH: u32 = 86_400;
pub const MIN_RETRY: u32 = 1;
pub const MAX_RETRY: u32 = 7_200;
pub const MIN_EXPIRE: u32 = 600;
pub const MAX_EXPIRE: u32 = 172_800;
pub const fn new(refresh: u32, retry: u32, expire: u32) -> Self {
Self { refresh, retry, expire }
}
pub fn validate(self) -> Result<(), io::Error> {
if !(Self::MIN_REFRESH..=Self::MAX_REFRESH).contains(&self.refresh) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"refresh interval {} out of range {}..={}",
self.refresh, Self::MIN_REFRESH, Self::MAX_REFRESH
),
));
}
if !(Self::MIN_RETRY..=Self::MAX_RETRY).contains(&self.retry) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"retry interval {} out of range {}..={}",
self.retry, Self::MIN_RETRY, Self::MAX_RETRY
),
));
}
if !(Self::MIN_EXPIRE..=Self::MAX_EXPIRE).contains(&self.expire) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"expire interval {} out of range {}..={}",
self.expire, Self::MIN_EXPIRE, Self::MAX_EXPIRE
),
));
}
if self.expire <= self.refresh {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"expire interval {} must be greater than refresh interval {}",
self.expire, self.refresh
),
));
}
if self.expire <= self.retry {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"expire interval {} must be greater than retry interval {}",
self.expire, self.retry
),
));
}
Ok(())
}
pub fn refresh(self) -> Duration {
Duration::from_secs(u64::from(self.refresh))
}
@@ -157,4 +249,4 @@ impl Default for Timing {
expire: 7200,
}
}
}
}

View File

@@ -2,9 +2,9 @@ use std::{cmp, mem};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::Arc;
use crate::data_model::resources::as_resources::Asn;
use crate::rtr::error_type::ErrorCode;
use crate::rtr::payload::{Ski, Timing};
use std::io;
use std::io::Write;
use tokio::io::{AsyncWrite};
use anyhow::Result;
@@ -12,7 +12,6 @@ use std::slice;
use anyhow::bail;
use serde::Serialize;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
pub const HEADER_LEN: usize = 8;
pub const MAX_PDU_LEN: u32 = 65535;
@@ -208,10 +207,15 @@ impl Header {
}
}
pub async fn read<S: AsyncRead + Unpin>(sock: &mut S) -> Result<Self, io::Error> {
pub async fn read_raw<S: AsyncRead + Unpin>(
sock: &mut S
) -> Result<[u8; HEADER_LEN], io::Error> {
let mut buf = [0u8; HEADER_LEN];
sock.read_exact(&mut buf).await?;
Ok(buf)
}
pub fn from_raw(buf: [u8; HEADER_LEN]) -> Result<Self, io::Error> {
let version = buf[0];
let pdu = buf[1];
let session_id = u16::from_be_bytes([buf[2], buf[3]]);
@@ -239,6 +243,10 @@ impl Header {
})
}
pub async fn read<S: AsyncRead + Unpin>(sock: &mut S) -> Result<Self, io::Error> {
Self::from_raw(Self::read_raw(sock).await?)
}
pub fn version(self) -> u8{self.version}
pub fn pdu(self) -> u8{self.pdu}
@@ -256,6 +264,10 @@ impl Header {
})
}
pub fn error_code(self) -> u16 {
debug_assert_eq!(self.pdu(), ErrorReport::PDU);
self.session_id()
}
}
@@ -281,7 +293,7 @@ impl HeaderWithFlags {
length: length.to_be(),
}
}
pub async fn read(sock: &mut TcpStream) -> Result<Self> {
pub async fn read<S: AsyncRead + Unpin>(sock: &mut S) -> Result<Self> {
let mut buf = [0u8; HEADER_LEN];
// 1. 精确读取 8 字节
@@ -311,7 +323,7 @@ impl HeaderWithFlags {
pdu,
flags,
zero,
length,
length: length.to_be(),
})
}
@@ -344,7 +356,7 @@ impl SerialNotify {
}
pub fn serial_number(self) -> u32 {
self.serial_number
u32::from_be(self.serial_number)
}
}
@@ -370,7 +382,7 @@ impl SerialQuery {
}
pub fn serial_number(self) -> u32 {
self.serial_number
u32::from_be(self.serial_number)
}
}
@@ -538,12 +550,17 @@ pub enum EndOfData {
}
impl EndOfData {
pub fn new(version: u8, session_id: u16, serial_number: u32, timing: Timing) -> Self {
pub fn new(
version: u8,
session_id: u16,
serial_number: u32,
timing: Timing,
) -> Result<Self, io::Error> {
if version == 0 {
EndOfData::V0(EndOfDataV0::new(version, session_id, serial_number))
Ok(EndOfData::V0(EndOfDataV0::new(version, session_id, serial_number)))
}
else {
EndOfData::V1(EndOfDataV1::new(version, session_id, serial_number, timing))
Ok(EndOfData::V1(EndOfDataV1::new(version, session_id, serial_number, timing)?))
}
}
@@ -588,14 +605,37 @@ pub struct EndOfDataV1 {
impl EndOfDataV1 {
pub const PDU: u8 = 7;
pub fn new(version: u8, session_id: u16, serial_number: u32, timing: Timing) -> Self {
EndOfDataV1 {
pub fn version(&self) -> u8 {
self.header.version()
}
pub fn session_id(&self) -> u16 {
self.header.session_id()
}
pub fn pdu(&self) -> u8 {
self.header.pdu()
}
pub fn size() -> u32 {
mem::size_of::<Self>() as u32
}
pub fn new(
version: u8,
session_id: u16,
serial_number: u32,
timing: Timing,
) -> Result<Self, io::Error> {
timing.validate()?;
Ok(EndOfDataV1 {
header: Header::new(version, Self::PDU, session_id, END_OF_DATA_V1_LEN),
serial_number: serial_number.to_be(),
refresh_interval: timing.refresh.to_be(),
retry_interval: timing.retry.to_be(),
expire_interval: timing.expire.to_be(),
}
})
}
pub fn serial_number(self) -> u32{u32::from_be(self.serial_number)}
@@ -607,8 +647,50 @@ impl EndOfDataV1 {
expire: u32::from_be(self.expire_interval),
}
}
fn validate(&self) -> Result<(), io::Error> {
self.timing().validate()
}
pub async fn read<Sock: AsyncRead + Unpin>(
sock: &mut Sock
) -> Result<Self, io::Error> {
let mut res = Self::default();
sock.read_exact(res.header.as_mut()).await?;
if res.header.pdu() != Self::PDU {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PDU type mismatch when expecting EndOfDataV1",
))
}
if res.header.length() as usize != mem::size_of::<Self>() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid length for EndOfDataV1",
))
}
sock.read_exact(&mut res.as_mut()[Header::LEN..]).await?;
res.validate()?;
Ok(res)
}
pub async fn read_payload<Sock: AsyncRead + Unpin>(
header: Header, sock: &mut Sock
) -> Result<Self, io::Error> {
if header.length() as usize != mem::size_of::<Self>() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid length for EndOfDataV1 PDU",
))
}
let mut res = Self::default();
sock.read_exact(&mut res.as_mut()[Header::LEN..]).await?;
res.header = header;
res.validate()?;
Ok(res)
}
}
concrete!(EndOfDataV1);
common!(EndOfDataV1);
// Cache Reset
#[repr(C, packed)]
@@ -640,6 +722,7 @@ pub struct ErrorReport {
impl ErrorReport {
/// The PDU type of an error PDU.
pub const PDU: u8 = 10;
const FIXED_PART_LEN: usize = HEADER_LEN + 2 * mem::size_of::<u32>();
/// Creates a new error PDU from components.
pub fn new(
@@ -650,12 +733,12 @@ impl ErrorReport {
) -> Self {
let pdu = pdu.as_ref();
let text = text.as_ref();
let max_payload_len = MAX_PDU_LEN as usize - Self::FIXED_PART_LEN;
let pdu_len = cmp::min(pdu.len(), max_payload_len);
let text_room = max_payload_len - pdu_len;
let text_len = cmp::min(text.len(), text_room);
let size =
mem::size_of::<Header>()
+ 2 * mem::size_of::<u32>()
+ pdu.len() + text.len()
;
let size = Self::FIXED_PART_LEN + pdu_len + text_len;
let header = Header::new(
version, 10, error_code, u32::try_from(size).unwrap()
);
@@ -663,37 +746,92 @@ impl ErrorReport {
let mut octets = Vec::with_capacity(size);
octets.extend_from_slice(header.as_ref());
octets.extend_from_slice(
u32::try_from(pdu.len()).unwrap().to_be_bytes().as_ref()
u32::try_from(pdu_len).unwrap().to_be_bytes().as_ref()
);
octets.extend_from_slice(pdu);
octets.extend_from_slice(&pdu[..pdu_len]);
octets.extend_from_slice(
u32::try_from(text.len()).unwrap().to_be_bytes().as_ref()
u32::try_from(text_len).unwrap().to_be_bytes().as_ref()
);
octets.extend_from_slice(text);
octets.extend_from_slice(&text[..text_len]);
ErrorReport { octets }
}
pub async fn read<Sock: AsyncRead + Unpin>(
sock: &mut Sock
) -> Result<Self, io::Error> {
let header = Header::read(sock).await?;
if header.pdu() != Self::PDU {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PDU type mismatch when expecting ErrorReport",
));
}
Self::read_payload(header, sock).await
}
pub async fn read_payload<Sock: AsyncRead + Unpin>(
header: Header,
sock: &mut Sock,
) -> Result<Self, io::Error> {
let total_len = header.pdu_len()?;
let Some(payload_len) = total_len.checked_sub(mem::size_of::<Header>()) else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PDU size smaller than header size",
));
};
let mut octets = Vec::with_capacity(total_len);
octets.extend_from_slice(header.as_ref());
octets.resize(total_len, 0);
sock.read_exact(&mut octets[mem::size_of::<Header>()..]).await?;
let res = ErrorReport { octets };
res.validate()?;
debug_assert_eq!(payload_len + mem::size_of::<Header>(), res.octets.len());
Ok(res)
}
pub fn version(&self) -> u8 {
self.header().version()
}
pub fn error_code(&self) -> Result<ErrorCode, u16> {
ErrorCode::try_from(self.header().error_code()).map_err(|_| self.header().error_code())
}
pub fn erroneous_pdu(&self) -> &[u8] {
&self.octets[self.erroneous_pdu_range()]
}
pub fn text(&self) -> &[u8] {
&self.octets[self.text_range()]
}
/// Skips over the payload of the error PDU.
pub async fn skip_payload<Sock: AsyncRead + Unpin>(
header: Header, sock: &mut Sock
) -> Result<(), io::Error> {
let Some(mut remaining) = header.pdu_len()?.checked_sub(
mem::size_of::<Header>()
) else {
let Some(mut remaining) = header.pdu_len()?.checked_sub(mem::size_of::<Header>()) else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PDU size smaller than header size",
))
));
};
let mut buf = [0u8; 1024];
while remaining > 0 {
let read_len = cmp::min(remaining, mem::size_of_val(&buf));
let read = sock.read(
// Safety: We limited the length to the buffer size.
unsafe { buf.get_unchecked_mut(..read_len) }
).await?;
let read = sock.read(&mut buf[..read_len]).await?;
if read == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected EOF while skipping ErrorReport payload",
));
}
remaining -= read;
}
Ok(())
@@ -705,11 +843,115 @@ impl ErrorReport {
) -> Result<(), io::Error> {
a.write_all(self.as_ref()).await
}
fn header(&self) -> Header {
Header::from_raw(self.header_bytes()).expect("validated ErrorReport header")
}
fn header_bytes(&self) -> [u8; HEADER_LEN] {
self.octets[..HEADER_LEN]
.try_into()
.expect("ErrorReport shorter than header")
}
fn erroneous_pdu_len(&self) -> usize {
u32::from_be_bytes(
self.octets[Header::LEN..Header::LEN + 4]
.try_into()
.unwrap()
) as usize
}
fn erroneous_pdu_range(&self) -> std::ops::Range<usize> {
let start = Header::LEN + 4;
let end = start + self.erroneous_pdu_len();
start..end
}
fn text_len_offset(&self) -> usize {
self.erroneous_pdu_range().end
}
fn text_len(&self) -> usize {
let offset = self.text_len_offset();
u32::from_be_bytes(
self.octets[offset..offset + 4]
.try_into()
.unwrap()
) as usize
}
fn text_range(&self) -> std::ops::Range<usize> {
let start = self.text_len_offset() + 4;
let end = start + self.text_len();
start..end
}
fn validate(&self) -> Result<(), io::Error> {
let header = Header::from_raw(self.header_bytes())?;
if header.pdu() != Self::PDU {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"unexpected PDU type for ErrorReport",
));
}
let total_len = header.pdu_len()?;
if total_len != self.octets.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ErrorReport length mismatch",
));
}
if self.octets.len() < Header::LEN + 8 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ErrorReport too short",
));
}
let pdu_len = self.erroneous_pdu_len();
let text_len_offset = Header::LEN + 4 + pdu_len;
let Some(text_len_end) = text_len_offset.checked_add(4) else {
return Err(io::Error::new(io::ErrorKind::InvalidData, "ErrorReport length overflow"));
};
if text_len_end > self.octets.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ErrorReport truncated before error text length",
));
}
let text_len = u32::from_be_bytes(
self.octets[text_len_offset..text_len_end]
.try_into()
.unwrap()
) as usize;
let Some(text_end) = text_len_end.checked_add(text_len) else {
return Err(io::Error::new(io::ErrorKind::InvalidData, "ErrorReport text overflow"));
};
if text_end != self.octets.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ErrorReport payload length mismatch",
));
}
if std::str::from_utf8(self.text()).is_err() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ErrorReport text is not valid UTF-8",
));
}
Ok(())
}
}
// TODO: 补全
// Router Key
/// Router Key
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct RouterKey {
header: HeaderWithFlags,
@@ -724,17 +966,13 @@ pub struct RouterKey {
impl RouterKey {
pub const PDU: u8 = 9;
const BASE_LEN: usize = HEADER_LEN + 20 + 4;
pub async fn write<A: AsyncWrite + Unpin>(
&self,
w: &mut A,
) -> Result<(), io::Error> {
let length = HEADER_LEN
+ 1 // flags
// + self.ski.as_ref().len()
+ 4 // ASN
+ self.subject_public_key_info.len();
let length = Self::BASE_LEN + self.subject_public_key_info.len();
let header = HeaderWithFlags::new(
self.header.version(),
@@ -750,13 +988,42 @@ impl RouterKey {
ZERO_8,
]).await?;
w.write_all(&length.to_be_bytes()).await?;
// w.write_all(self.ski.as_ref()).await?;
w.write_all(&(length as u32).to_be_bytes()).await?;
w.write_all(self.ski.as_ref()).await?;
w.write_all(&self.asn.into_u32().to_be_bytes()).await?;
w.write_all(&self.subject_public_key_info).await?;
Ok(())
}
pub fn new(
version: u8,
flags: Flags,
ski: Ski,
asn: Asn,
subject_public_key_info: Arc<[u8]>,
) -> Self {
let length = Self::BASE_LEN + subject_public_key_info.len();
Self {
header: HeaderWithFlags::new(version, Self::PDU, flags, length as u32),
flags,
ski,
asn,
subject_public_key_info,
}
}
pub fn ski(&self) -> Ski {
self.ski
}
pub fn asn(&self) -> Asn {
self.asn
}
pub fn spki(&self) -> &[u8] {
&self.subject_public_key_info
}
}
@@ -772,21 +1039,19 @@ pub struct Aspa{
impl Aspa {
pub const PDU: u8 = 11;
const BASE_LEN: usize = HEADER_LEN + 4;
pub async fn write<A: AsyncWrite + Unpin>(
&self,
w: &mut A,
) -> Result<(), io::Error> {
let length = HEADER_LEN
+ 1
+ 4
+ (self.provider_asns.len() * 4);
let length = Self::BASE_LEN + (self.provider_asns.len() * 4);
let header = HeaderWithFlags::new(
self.header.version(),
Self::PDU,
Flags::new(self.header.flags),
self.header.flags(),
length as u32,
);
@@ -797,7 +1062,7 @@ impl Aspa {
ZERO_8,
]).await?;
w.write_all(&length.to_be_bytes()).await?;
w.write_all(&(length as u32).to_be_bytes()).await?;
w.write_all(&self.customer_asn.to_be_bytes()).await?;
for asn in &self.provider_asns {
@@ -806,6 +1071,20 @@ impl Aspa {
Ok(())
}
pub fn new(
version: u8,
flags: Flags,
customer_asn: u32,
provider_asns: Vec<u32>,
) -> Self {
let length = Self::BASE_LEN + (provider_asns.len() * 4);
Self {
header: HeaderWithFlags::new(version, Self::PDU, flags, length as u32),
customer_asn,
provider_asns,
}
}
}
@@ -823,54 +1102,3 @@ impl AsMut<[u8]> for ErrorReport {
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::duplex;
#[tokio::test]
async fn test_serial_notify_roundtrip() {
let (mut client, mut server) = duplex(1024);
let original = SerialNotify::new(1, 42, 100);
// 写入
tokio::spawn(async move {
original.write(&mut client).await.unwrap();
});
// 读取
let decoded = SerialNotify::read(&mut server).await.unwrap();
assert_eq!(decoded.version(), 1);
assert_eq!(decoded.session_id(), 42);
assert_eq!(decoded.serial_number(), 100u32.to_be());
}
#[tokio::test]
async fn test_ipv4_prefix_roundtrip() {
use std::net::Ipv4Addr;
let (mut client, mut server) = duplex(1024);
let prefix = IPv4Prefix::new(
1,
Flags::new(1),
24,
24,
Ipv4Addr::new(192,168,0,0),
65000u32.into(),
);
tokio::spawn(async move {
prefix.write(&mut client).await.unwrap();
});
let decoded = IPv4Prefix::read(&mut server).await.unwrap();
assert_eq!(decoded.prefix_len(), 24);
assert_eq!(decoded.max_len(), 24);
assert_eq!(decoded.prefix(), Ipv4Addr::new(192,168,0,0));
assert_eq!(decoded.flag().is_announce(), true);
}
}

View File

@@ -1,7 +1,12 @@
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct RtrServiceConfig {
pub max_connections: usize,
pub notify_queue_size: usize,
pub tcp_keepalive: Option<Duration>,
pub warn_insecure_tcp: bool,
pub require_tls_server_dns_name_san: bool,
}
impl Default for RtrServiceConfig {
@@ -9,6 +14,9 @@ impl Default for RtrServiceConfig {
Self {
max_connections: 1024,
notify_queue_size: 1024,
tcp_keepalive: Some(Duration::from_secs(60)),
warn_insecure_tcp: true,
require_tls_server_dns_name_san: false,
}
}
}
}

View File

@@ -1,13 +1,15 @@
use std::net::SocketAddr;
use std::net::{IpAddr, SocketAddr};
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use anyhow::{Context, Result};
use anyhow::{Context, Result, anyhow};
use tokio::net::TcpStream;
use tokio::sync::{broadcast, watch, OwnedSemaphorePermit};
use tracing::error;
use tracing::{error, info, warn};
use x509_parser::extensions::GeneralName;
use x509_parser::prelude::{FromDer, X509Certificate};
use tokio_rustls::TlsAcceptor;
@@ -30,6 +32,10 @@ impl ConnectionGuard {
_permit: permit,
}
}
pub fn active_count(&self) -> usize {
self.active_connections.load(Ordering::Relaxed)
}
}
impl Drop for ConnectionGuard {
@@ -52,6 +58,7 @@ pub async fn handle_tcp_connection(
return Err(err);
}
info!("RTR TCP session completed normally for {}", peer_addr);
Ok(())
}
@@ -63,10 +70,15 @@ pub async fn handle_tls_connection(
notify_rx: broadcast::Receiver<()>,
shutdown_rx: watch::Receiver<bool>,
) -> Result<()> {
info!("RTR TLS handshake started for {}", peer_addr);
let tls_stream = acceptor
.accept(stream)
.await
.with_context(|| format!("TLS handshake failed for {}", peer_addr))?;
info!("RTR TLS handshake completed for {}", peer_addr);
verify_peer_certificate_ip(&tls_stream, peer_addr.ip())
.with_context(|| format!("TLS client certificate SAN IP validation failed for {}", peer_addr))?;
info!("RTR TLS client certificate validated for {}", peer_addr);
let session = RtrSession::new(cache, tls_stream, notify_rx, shutdown_rx);
@@ -75,5 +87,57 @@ pub async fn handle_tls_connection(
return Err(err);
}
info!("RTR TLS session completed normally for {}", peer_addr);
Ok(())
}
}
fn verify_peer_certificate_ip(
tls_stream: &tokio_rustls::server::TlsStream<TcpStream>,
peer_ip: IpAddr,
) -> Result<()> {
let (_, server_connection) = tls_stream.get_ref();
let peer_certs = server_connection
.peer_certificates()
.ok_or_else(|| anyhow!("missing peer certificate after TLS client authentication"))?;
let end_entity = peer_certs
.first()
.ok_or_else(|| anyhow!("peer did not present an end-entity certificate"))?;
let (_, cert) = X509Certificate::from_der(end_entity.as_ref())
.map_err(|err| anyhow!("failed to parse peer certificate: {:?}", err))?;
let san = cert
.subject_alternative_name()
.map_err(|err| anyhow!("failed to parse peer certificate SAN: {:?}", err))?
.ok_or_else(|| anyhow!("peer certificate is missing subjectAltName"))?;
let matched = san.value.general_names.iter().any(|name| match name {
GeneralName::IPAddress(bytes) => {
let bytes = *bytes;
match (peer_ip, bytes.len()) {
(IpAddr::V4(ip), 4) => <[u8; 4]>::try_from(bytes)
.map(IpAddr::from)
.map(|cert_ip| cert_ip == IpAddr::V4(ip))
.unwrap_or(false),
(IpAddr::V6(ip), 16) => <[u8; 16]>::try_from(bytes)
.map(IpAddr::from)
.map(|cert_ip| cert_ip == IpAddr::V6(ip))
.unwrap_or(false),
_ => false,
}
}
_ => false,
});
if matched {
Ok(())
} else {
warn!(
"RTR TLS client certificate SAN IP mismatch for peer_ip={}",
peer_ip
);
Err(anyhow!(
"peer certificate subjectAltName iPAddress does not match {}",
peer_ip
))
}
}

View File

@@ -4,8 +4,10 @@ use std::sync::{
Arc,
atomic::AtomicUsize,
};
use std::time::Duration;
use anyhow::{Context, Result};
use socket2::{SockRef, TcpKeepalive};
use tokio::net::TcpListener;
use tokio::sync::{broadcast, watch, Semaphore};
use tracing::{info, warn};
@@ -15,7 +17,8 @@ use tokio_rustls::TlsAcceptor;
use crate::rtr::cache::SharedRtrCache;
use crate::rtr::server::connection::{ConnectionGuard, handle_tcp_connection, handle_tls_connection};
use crate::rtr::server::tls::load_rustls_server_config;
use crate::rtr::server::config::RtrServiceConfig;
use crate::rtr::server::tls::load_rustls_server_config_with_options;
pub struct RtrServer {
bind_addr: SocketAddr,
@@ -24,6 +27,7 @@ pub struct RtrServer {
shutdown_tx: watch::Sender<bool>,
connection_limiter: Arc<Semaphore>,
active_connections: Arc<AtomicUsize>,
config: RtrServiceConfig,
}
impl RtrServer {
@@ -34,6 +38,7 @@ impl RtrServer {
shutdown_tx: watch::Sender<bool>,
connection_limiter: Arc<Semaphore>,
active_connections: Arc<AtomicUsize>,
config: RtrServiceConfig,
) -> Self {
Self {
bind_addr,
@@ -42,6 +47,7 @@ impl RtrServer {
shutdown_tx,
connection_limiter,
active_connections,
config,
}
}
@@ -95,6 +101,9 @@ impl RtrServer {
if let Err(err) = stream.set_nodelay(true) {
warn!("failed to enable TCP_NODELAY for {}: {}", peer_addr, err);
}
if let Err(err) = apply_keepalive(&stream, self.config.tcp_keepalive) {
warn!("failed to configure TCP keepalive for {}: {}", peer_addr, err);
}
let permit = match self.connection_limiter.clone().try_acquire_owned() {
Ok(permit) => permit,
@@ -102,7 +111,7 @@ impl RtrServer {
warn!(
"RTR TCP connection rejected for {}: max connections reached ({})",
peer_addr,
self.connection_limiter.available_permits()
self.config.max_connections
);
drop(stream);
continue;
@@ -114,16 +123,34 @@ impl RtrServer {
let shutdown_rx = self.shutdown_tx.subscribe();
let active_connections = self.active_connections.clone();
info!("RTR TCP client connected: {}", peer_addr);
info!(
"RTR TCP client connected: peer_addr={}, active_connections(before_spawn)={}",
peer_addr,
self.active_connections()
);
tokio::spawn(async move {
let _guard = ConnectionGuard::new(active_connections, permit);
let guard = ConnectionGuard::new(active_connections, permit);
info!(
"RTR TCP connection established: peer_addr={}, active_connections={}",
peer_addr,
guard.active_count()
);
if let Err(err) =
handle_tcp_connection(cache, stream, peer_addr, notify_rx, shutdown_rx).await
{
warn!("RTR TCP session {} ended with error: {:?}", peer_addr, err);
warn!(
"RTR TCP session closed with error: peer_addr={}, active_connections={}, err={:?}",
peer_addr,
guard.active_count(),
err
);
} else {
info!("RTR TCP session {} closed", peer_addr);
info!(
"RTR TCP session closed cleanly: peer_addr={}, active_connections={}",
peer_addr,
guard.active_count()
);
}
});
}
@@ -135,8 +162,14 @@ impl RtrServer {
self,
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>,
client_ca_path: impl AsRef<Path>,
) -> Result<()> {
let tls_config = Arc::new(load_rustls_server_config(cert_path, key_path)?);
let tls_config = Arc::new(load_rustls_server_config_with_options(
cert_path,
key_path,
client_ca_path,
self.config.require_tls_server_dns_name_san,
)?);
self.run_tls(tls_config).await
}
@@ -179,11 +212,18 @@ impl RtrServer {
if let Err(err) = stream.set_nodelay(true) {
warn!("failed to enable TCP_NODELAY for {}: {}", peer_addr, err);
}
if let Err(err) = apply_keepalive(&stream, self.config.tcp_keepalive) {
warn!("failed to configure TCP keepalive for {}: {}", peer_addr, err);
}
let permit = match self.connection_limiter.clone().try_acquire_owned() {
Ok(permit) => permit,
Err(_) => {
warn!("RTR TLS connection rejected for {}: max connections reached", peer_addr);
warn!(
"RTR TLS connection rejected for {}: max connections reached ({})",
peer_addr,
self.config.max_connections
);
drop(stream);
continue;
}
@@ -195,10 +235,19 @@ impl RtrServer {
let shutdown_rx = self.shutdown_tx.subscribe();
let active_connections = self.active_connections.clone();
info!("RTR TLS client connected: {}", peer_addr);
info!(
"RTR TLS client connected: peer_addr={}, active_connections(before_spawn)={}",
peer_addr,
self.active_connections()
);
tokio::spawn(async move {
let _guard = ConnectionGuard::new(active_connections, permit);
let guard = ConnectionGuard::new(active_connections, permit);
info!(
"RTR TLS connection established: peer_addr={}, active_connections={}",
peer_addr,
guard.active_count()
);
if let Err(err) = handle_tls_connection(
cache,
stream,
@@ -207,13 +256,38 @@ impl RtrServer {
notify_rx,
shutdown_rx,
).await {
warn!("RTR TLS session {} ended with error: {:?}", peer_addr, err);
warn!(
"RTR TLS session closed with error: peer_addr={}, active_connections={}, err={:?}",
peer_addr,
guard.active_count(),
err
);
} else {
info!("RTR TLS session {} closed", peer_addr);
info!(
"RTR TLS session closed cleanly: peer_addr={}, active_connections={}",
peer_addr,
guard.active_count()
);
}
});
}
}
}
}
}
}
fn apply_keepalive(
stream: &tokio::net::TcpStream,
keepalive: Option<Duration>,
) -> Result<()> {
let Some(keepalive) = keepalive else {
return Ok(());
};
let socket = SockRef::from(stream);
let keepalive = TcpKeepalive::new().with_time(keepalive);
socket
.set_tcp_keepalive(&keepalive)
.context("unable to apply TCP keepalive settings")?;
Ok(())
}

View File

@@ -7,7 +7,7 @@ use std::sync::{
use tokio::sync::{broadcast, watch, Semaphore};
use tokio::task::JoinHandle;
use tracing::error;
use tracing::{error, warn};
use crate::rtr::cache::SharedRtrCache;
use crate::rtr::server::config::RtrServiceConfig;
@@ -70,6 +70,7 @@ impl RtrService {
self.shutdown_tx.clone(),
self.connection_limiter.clone(),
self.active_connections.clone(),
self.config.clone(),
)
}
@@ -81,10 +82,17 @@ impl RtrService {
self.shutdown_tx.clone(),
self.connection_limiter.clone(),
self.active_connections.clone(),
self.config.clone(),
)
}
pub fn spawn_tcp(&self, bind_addr: SocketAddr) -> JoinHandle<()> {
if self.config.warn_insecure_tcp {
warn!(
"starting plain TCP RTR service on {}. Per draft-ietf-sidrops-8210bis-25 Section 9, unsecured TCP must only be used on a trusted and controlled network",
bind_addr
);
}
let server = self.tcp_server(bind_addr);
tokio::spawn(async move {
if let Err(err) = server.run_tcp().await {
@@ -98,13 +106,15 @@ impl RtrService {
bind_addr: SocketAddr,
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>,
client_ca_path: impl AsRef<Path>,
) -> JoinHandle<()> {
let cert_path = cert_path.as_ref().to_path_buf();
let key_path = key_path.as_ref().to_path_buf();
let client_ca_path = client_ca_path.as_ref().to_path_buf();
let server = self.tls_server(bind_addr);
tokio::spawn(async move {
if let Err(err) = server.run_tls_from_pem(cert_path, key_path).await {
if let Err(err) = server.run_tls_from_pem(cert_path, key_path, client_ca_path).await {
error!("RTR TLS server {} exited with error: {:?}", bind_addr, err);
}
})
@@ -116,9 +126,10 @@ impl RtrService {
tls_bind_addr: SocketAddr,
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>,
client_ca_path: impl AsRef<Path>,
) -> RunningRtrService {
let tcp_handle = self.spawn_tcp(tcp_bind_addr);
let tls_handle = self.spawn_tls_from_pem(tls_bind_addr, cert_path, key_path);
let tls_handle = self.spawn_tls_from_pem(tls_bind_addr, cert_path, key_path, client_ca_path);
RunningRtrService {
shutdown_tx: self.shutdown_tx.clone(),
@@ -151,4 +162,4 @@ impl RunningRtrService {
let _ = handle.await;
}
}
}
}

View File

@@ -1,32 +1,101 @@
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::{anyhow, Context, Result};
use rustls::ServerConfig;
use rustls::server::WebPkiClientVerifier;
use rustls::{RootCertStore, ServerConfig};
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use tracing::warn;
use x509_parser::extensions::GeneralName;
use x509_parser::prelude::{FromDer, X509Certificate};
pub fn load_rustls_server_config(
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>,
client_ca_path: impl AsRef<Path>,
) -> Result<ServerConfig> {
load_rustls_server_config_with_options(cert_path, key_path, client_ca_path, false)
}
pub fn load_rustls_server_config_with_options(
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>,
client_ca_path: impl AsRef<Path>,
require_dns_name_san: bool,
) -> Result<ServerConfig> {
let cert_path: PathBuf = cert_path.as_ref().to_path_buf();
let key_path: PathBuf = key_path.as_ref().to_path_buf();
let client_ca_path: PathBuf = client_ca_path.as_ref().to_path_buf();
let certs = load_certs(&cert_path)
.with_context(|| format!("failed to load certs from {}", cert_path.display()))?;
validate_server_certificate_dns_name_san(&certs, &cert_path, require_dns_name_san)?;
let key = load_private_key(&key_path)
.with_context(|| format!("failed to load private key from {}", key_path.display()))?;
let client_ca_certs = load_certs(&client_ca_path)
.with_context(|| format!("failed to load client CA certs from {}", client_ca_path.display()))?;
let mut client_roots = RootCertStore::empty();
let (added, _) = client_roots.add_parsable_certificates(client_ca_certs);
if added == 0 {
return Err(anyhow!(
"no valid client CA certificates found in {}",
client_ca_path.display()
));
}
let client_verifier = WebPkiClientVerifier::builder(Arc::new(client_roots))
.build()
.map_err(|e| anyhow!("invalid client certificate verifier configuration: {}", e))?;
let config = ServerConfig::builder()
.with_no_client_auth()
.with_client_cert_verifier(client_verifier)
.with_single_cert(certs, key)
.map_err(|e| anyhow!("invalid certificate/key pair: {}", e))?;
Ok(config)
}
fn validate_server_certificate_dns_name_san(
certs: &[CertificateDer<'static>],
cert_path: &Path,
require_dns_name_san: bool,
) -> Result<()> {
let leaf = certs
.first()
.ok_or_else(|| anyhow!("missing end-entity certificate in {}", cert_path.display()))?;
let (_, cert) = X509Certificate::from_der(leaf.as_ref())
.map_err(|err| anyhow!("failed to parse server certificate: {:?}", err))?;
let has_dns_name_san = cert
.subject_alternative_name()
.map_err(|err| anyhow!("failed to parse server certificate SAN: {:?}", err))?
.map(|san| {
san.value
.general_names
.iter()
.any(|name| matches!(name, GeneralName::DNSName(_)))
})
.unwrap_or(false);
if has_dns_name_san {
return Ok(());
}
let message = format!(
"server certificate {} does not contain a subjectAltName dNSName entry; draft-ietf-sidrops-8210bis-25 Section 9.2 requires routers to authenticate the cache using DNS-ID rather than CN-ID",
cert_path.display()
);
if require_dns_name_san {
Err(anyhow!(message))
} else {
warn!("{}", message);
Ok(())
}
}
fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
@@ -49,4 +118,4 @@ fn load_private_key(path: &Path) -> Result<PrivateKeyDer<'static>> {
.ok_or_else(|| anyhow!("no private key found in {}", path.display()))?;
Ok(key)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,16 @@
use crate::rtr::cache::SessionIds;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct State {
session_id: u16,
session_ids: SessionIds,
serial: u32,
}
impl State {
pub fn session_id(self) -> u16 {
self.session_id
pub fn session_ids(self) -> SessionIds {
self.session_ids
}
pub fn serial(self) -> u32 {

619
src/rtr/store.rs Normal file
View File

@@ -0,0 +1,619 @@
use rocksdb::{ColumnFamilyDescriptor, DB, Direction, IteratorMode, Options, WriteBatch};
use anyhow::{anyhow, Result};
use serde::{de::DeserializeOwned, Serialize};
use std::path::Path;
use std::sync::Arc;
use tokio::task;
use tracing::{debug, info, warn};
use crate::rtr::cache::{CacheAvailability, Delta, SessionIds, Snapshot};
use crate::rtr::state::State;
const CF_META: &str = "meta";
const CF_SNAPSHOT: &str = "snapshot";
const CF_DELTA: &str = "delta";
const META_STATE: &[u8] = b"state";
const META_SESSION_IDS: &[u8] = b"session_ids";
const META_SERIAL: &[u8] = b"serial";
const META_AVAILABILITY: &[u8] = b"availability";
const META_DELTA_MIN: &[u8] = b"delta_min";
const META_DELTA_MAX: &[u8] = b"delta_max";
const DELTA_KEY_PREFIX: u8 = b'd';
fn delta_key(serial: u32) -> [u8; 5] {
let mut key = [0u8; 5];
key[0] = DELTA_KEY_PREFIX;
key[1..].copy_from_slice(&serial.to_be_bytes());
key
}
fn delta_key_serial(key: &[u8]) -> Option<u32> {
if key.len() != 5 || key[0] != DELTA_KEY_PREFIX {
return None;
}
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&key[1..]);
Some(u32::from_be_bytes(bytes))
}
#[derive(Clone)]
pub struct RtrStore {
db: Arc<DB>,
}
impl RtrStore {
/// Open or create DB with required column families.
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
let mut opts = Options::default();
opts.create_if_missing(true);
opts.create_missing_column_families(true);
let cfs = vec![
ColumnFamilyDescriptor::new(CF_META, Options::default()),
ColumnFamilyDescriptor::new(CF_SNAPSHOT, Options::default()),
ColumnFamilyDescriptor::new(CF_DELTA, Options::default()),
];
info!("opening RTR RocksDB store at {}", path_ref.display());
let db = Arc::new(DB::open_cf_descriptors(&opts, path_ref, cfs)?);
info!("opened RTR RocksDB store at {}", path_ref.display());
Ok(Self { db })
}
/// Common serialize/put.
fn put_cf<T: Serialize>(&self, cf: &str, key: &[u8], value: &T) -> Result<()> {
let cf_handle = self.db.cf_handle(cf).ok_or_else(|| anyhow!("CF not found"))?;
let data = serde_json::to_vec(value)?;
self.db.put_cf(cf_handle, key, data)?;
Ok(())
}
/// Common get/deserialize.
fn get_cf<T: DeserializeOwned>(&self, cf: &str, key: &[u8]) -> Result<Option<T>> {
let cf_handle = self.db.cf_handle(cf).ok_or_else(|| anyhow!("CF not found"))?;
if let Some(value) = self.db.get_cf(cf_handle, key)? {
let obj = serde_json::from_slice(&value)?;
Ok(Some(obj))
} else {
Ok(None)
}
}
/// Common delete.
fn delete_cf(&self, cf: &str, key: &[u8]) -> Result<()> {
let cf_handle = self.db.cf_handle(cf).ok_or_else(|| anyhow!("CF not found"))?;
self.db.delete_cf(cf_handle, key)?;
Ok(())
}
// ===============================
// Meta/state
// ===============================
pub fn set_state(&self, state: &State) -> Result<()> {
self.put_cf(CF_META, META_STATE, &state)
}
pub fn get_state(&self) -> Result<Option<State>> {
self.get_cf(CF_META, META_STATE)
}
pub fn set_meta(&self, meta: &State) -> Result<()> {
self.set_state(meta)
}
pub fn get_meta(&self) -> Result<Option<State>> {
self.get_state()
}
pub fn set_session_ids(&self, session_ids: &SessionIds) -> Result<()> {
self.put_cf(CF_META, META_SESSION_IDS, session_ids)
}
pub fn get_session_ids(&self) -> Result<Option<SessionIds>> {
self.get_cf(CF_META, META_SESSION_IDS)
}
pub fn set_serial(&self, serial: u32) -> Result<()> {
self.put_cf(CF_META, META_SERIAL, &serial)
}
pub fn get_serial(&self) -> Result<Option<u32>> {
self.get_cf(CF_META, META_SERIAL)
}
pub fn set_availability(&self, availability: CacheAvailability) -> Result<()> {
self.put_cf(CF_META, META_AVAILABILITY, &availability)
}
pub fn get_availability(&self) -> Result<Option<CacheAvailability>> {
self.get_cf(CF_META, META_AVAILABILITY)
}
pub fn set_delta_window(&self, min_serial: u32, max_serial: u32) -> Result<()> {
debug!(
"RTR store persisting delta window metadata: min_serial={}, max_serial={}",
min_serial,
max_serial
);
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
let mut batch = WriteBatch::default();
batch.put_cf(meta_cf, META_DELTA_MIN, serde_json::to_vec(&min_serial)?);
batch.put_cf(meta_cf, META_DELTA_MAX, serde_json::to_vec(&max_serial)?);
self.db.write(batch)?;
Ok(())
}
pub fn clear_delta_window(&self) -> Result<()> {
debug!("RTR store clearing delta window metadata");
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
let mut batch = WriteBatch::default();
batch.delete_cf(meta_cf, META_DELTA_MIN);
batch.delete_cf(meta_cf, META_DELTA_MAX);
self.db.write(batch)?;
Ok(())
}
pub fn get_delta_window(&self) -> Result<Option<(u32, u32)>> {
let min: Option<u32> = self.get_cf(CF_META, META_DELTA_MIN)?;
let max: Option<u32> = self.get_cf(CF_META, META_DELTA_MAX)?;
match (min, max) {
(Some(min), Some(max)) => {
debug!(
"RTR store loaded delta window metadata: min_serial={}, max_serial={}",
min,
max
);
Ok(Some((min, max)))
}
(None, None) => Ok(None),
_ => Err(anyhow!("Inconsistent DB state: delta window mismatch")),
}
}
pub fn delete_state(&self) -> Result<()> {
self.delete_cf(CF_META, META_STATE)
}
pub fn delete_serial(&self) -> Result<()> {
self.delete_cf(CF_META, META_SERIAL)
}
// ===============================
// Snapshot
// ===============================
pub fn save_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
let cf_handle = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let mut batch = WriteBatch::default();
let data = serde_json::to_vec(snapshot)?;
batch.put_cf(cf_handle, b"current", data);
self.db.write(batch)?;
Ok(())
}
pub fn get_snapshot(&self) -> Result<Option<Snapshot>> {
self.get_cf(CF_SNAPSHOT, b"current")
}
pub fn delete_snapshot(&self) -> Result<()> {
self.delete_cf(CF_SNAPSHOT, b"current")
}
pub fn save_snapshot_and_state(&self, snapshot: &Snapshot, state: &State) -> Result<()> {
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
let mut batch = WriteBatch::default();
batch.put_cf(snapshot_cf, b"current", serde_json::to_vec(snapshot)?);
batch.put_cf(meta_cf, META_STATE, serde_json::to_vec(state)?);
batch.put_cf(
meta_cf,
META_SESSION_IDS,
serde_json::to_vec(&state.clone().session_ids())?,
);
batch.put_cf(
meta_cf,
META_SERIAL,
serde_json::to_vec(&state.clone().serial())?,
);
self.db.write(batch)?;
Ok(())
}
pub fn save_snapshot_and_meta(
&self,
snapshot: &Snapshot,
session_ids: &SessionIds,
serial: u32,
) -> Result<()> {
let mut batch = WriteBatch::default();
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
batch.put_cf(snapshot_cf, b"current", serde_json::to_vec(snapshot)?);
batch.put_cf(meta_cf, META_SESSION_IDS, serde_json::to_vec(session_ids)?);
batch.put_cf(meta_cf, META_SERIAL, serde_json::to_vec(&serial)?);
self.db.write(batch)?;
Ok(())
}
pub fn save_cache_state(
&self,
availability: CacheAvailability,
snapshot: &Snapshot,
session_ids: &SessionIds,
serial: u32,
delta: Option<&Delta>,
delta_window: Option<(u32, u32)>,
clear_delta_window: bool,
) -> Result<()> {
debug!(
"RTR store save_cache_state start: availability={:?}, serial={}, session_ids={:?}, delta_present={}, delta_window={:?}, clear_delta_window={}, snapshot(route_origins={}, router_keys={}, aspas={})",
availability,
serial,
session_ids,
delta.is_some(),
delta_window,
clear_delta_window,
snapshot.origins().len(),
snapshot.router_keys().len(),
snapshot.aspas().len()
);
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
let delta_cf = self.db.cf_handle(CF_DELTA).ok_or_else(|| anyhow!("CF_DELTA not found"))?;
let mut batch = WriteBatch::default();
batch.put_cf(snapshot_cf, b"current", serde_json::to_vec(snapshot)?);
batch.put_cf(meta_cf, META_SESSION_IDS, serde_json::to_vec(session_ids)?);
batch.put_cf(meta_cf, META_SERIAL, serde_json::to_vec(&serial)?);
batch.put_cf(meta_cf, META_AVAILABILITY, serde_json::to_vec(&availability)?);
if let Some(delta) = delta {
debug!(
"RTR store persisting delta: serial={}, announced={}, withdrawn={}",
delta.serial(),
delta.announced().len(),
delta.withdrawn().len()
);
batch.put_cf(delta_cf, delta_key(delta.serial()), serde_json::to_vec(delta)?);
}
if clear_delta_window {
let existing_keys = self.list_delta_keys()?;
let existing_serials = summarize_delta_keys(&existing_keys);
info!(
"RTR store clearing persisted delta window: deleting {} delta records, serials={}",
existing_keys.len(),
existing_serials
);
batch.delete_cf(meta_cf, META_DELTA_MIN);
batch.delete_cf(meta_cf, META_DELTA_MAX);
for key in existing_keys {
batch.delete_cf(delta_cf, key);
}
} else if let Some((min_serial, max_serial)) = delta_window {
batch.put_cf(meta_cf, META_DELTA_MIN, serde_json::to_vec(&min_serial)?);
batch.put_cf(meta_cf, META_DELTA_MAX, serde_json::to_vec(&max_serial)?);
// Serial numbers are compared in RFC 1982 ring order, while RocksDB stores
// keys in plain lexicographic order. After wraparound, a window such as
// [u32::MAX, 1] is contiguous in serial space but split in key space, so a
// simple delete_range() would leave stale high-serial keys behind.
let stale_keys = self.list_delta_keys_outside_window(min_serial, max_serial)?;
if !stale_keys.is_empty() {
info!(
"RTR store pruning stale delta records outside window [{}, {}]: count={}, serials={}",
min_serial,
max_serial,
stale_keys.len(),
summarize_delta_keys(&stale_keys)
);
} else {
debug!(
"RTR store found no stale delta records outside window [{}, {}]",
min_serial,
max_serial
);
}
for key in stale_keys {
batch.delete_cf(delta_cf, key);
}
}
self.db.write(batch)?;
debug!("RTR store save_cache_state completed: serial={}", serial);
Ok(())
}
pub fn save_snapshot_and_serial(&self, snapshot: &Snapshot, serial: u32) -> Result<()> {
let mut batch = WriteBatch::default();
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
batch.put_cf(snapshot_cf, b"current", serde_json::to_vec(snapshot)?);
batch.put_cf(meta_cf, META_SERIAL, serde_json::to_vec(&serial)?);
self.db.write(batch)?;
Ok(())
}
pub async fn save_snapshot_and_serial_async(
self: Arc<Self>,
snapshot: Snapshot,
serial: u32,
) -> Result<()> {
let snapshot_bytes = serde_json::to_vec(&snapshot)?;
let serial_bytes = serde_json::to_vec(&serial)?;
task::spawn_blocking(move || {
let mut batch = WriteBatch::default();
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
batch.put_cf(snapshot_cf, b"current", snapshot_bytes);
batch.put_cf(meta_cf, META_SERIAL, serial_bytes);
self.db.write(batch)?;
Ok::<_, anyhow::Error>(())
})
.await??;
Ok(())
}
pub fn load_snapshot_and_state(&self) -> Result<Option<(Snapshot, State)>> {
let snapshot: Option<Snapshot> = self.get_snapshot()?;
let state: Option<State> = self.get_state()?;
match (snapshot, state) {
(Some(snap), Some(state)) => Ok(Some((snap, state))),
(None, None) => Ok(None),
_ => Err(anyhow!("Inconsistent DB state: snapshot and state mismatch")),
}
}
pub fn load_snapshot_and_serial(&self) -> Result<Option<(Snapshot, u32)>> {
let snapshot: Option<Snapshot> = self.get_snapshot()?;
let serial: Option<u32> = self.get_serial()?;
match (snapshot, serial) {
(Some(snap), Some(serial)) => Ok(Some((snap, serial))),
(None, None) => Ok(None),
_ => Err(anyhow!("Inconsistent DB state: snapshot and serial mismatch")),
}
}
// ===============================
// Delta
// ===============================
pub fn save_delta(&self, delta: &Delta) -> Result<()> {
self.put_cf(CF_DELTA, &delta_key(delta.serial()), delta)
}
pub fn get_delta(&self, serial: u32) -> Result<Option<Delta>> {
self.get_cf(CF_DELTA, &delta_key(serial))
}
pub fn load_deltas_since(&self, serial: u32) -> Result<Vec<Delta>> {
let cf_handle = self
.db
.cf_handle(CF_DELTA)
.ok_or_else(|| anyhow!("CF_DELTA not found"))?;
let start_key = delta_key(serial.wrapping_add(1));
let iter = self.db.iterator_cf(
cf_handle,
IteratorMode::From(&start_key, Direction::Forward),
);
let mut out = Vec::new();
for item in iter {
let (key, value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
let parsed = delta_key_serial(key.as_ref())
.ok_or_else(|| anyhow!("Invalid delta key"))?;
if parsed <= serial {
continue;
}
let delta: Delta = serde_json::from_slice(value.as_ref())?;
out.push(delta);
}
Ok(out)
}
pub fn load_delta_window(&self, min_serial: u32, max_serial: u32) -> Result<Vec<Delta>> {
info!(
"RTR store loading persisted delta window: min_serial={}, max_serial={}",
min_serial,
max_serial
);
let cf_handle = self
.db
.cf_handle(CF_DELTA)
.ok_or_else(|| anyhow!("CF_DELTA not found"))?;
let iter = self.db.iterator_cf(cf_handle, IteratorMode::Start);
let mut out = Vec::new();
for item in iter {
let (key, value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
let parsed = delta_key_serial(key.as_ref())
.ok_or_else(|| anyhow!("Invalid delta key"))?;
// Restore by the persisted window bounds instead of load_deltas_since().
// The latter follows lexicographic key order and is not safe across serial
// wraparound, where older high-valued keys may otherwise be pulled back in.
if serial_in_window(parsed, min_serial, max_serial) {
let delta: Delta = serde_json::from_slice(value.as_ref())?;
out.push(delta);
}
}
out.sort_by_key(|delta| delta.serial().wrapping_sub(min_serial));
debug!(
"RTR store loaded delta candidates for window [{}, {}]: count={}, serials={}",
min_serial,
max_serial,
out.len(),
summarize_delta_serials(&out)
);
validate_delta_window(&out, min_serial, max_serial)?;
info!(
"RTR store restored valid delta window: min_serial={}, max_serial={}, count={}, serials={}",
min_serial,
max_serial,
out.len(),
summarize_delta_serials(&out)
);
Ok(out)
}
pub fn delete_delta(&self, serial: u32) -> Result<()> {
self.delete_cf(CF_DELTA, &delta_key(serial))
}
fn list_delta_keys(&self) -> Result<Vec<Vec<u8>>> {
let cf_handle = self
.db
.cf_handle(CF_DELTA)
.ok_or_else(|| anyhow!("CF_DELTA not found"))?;
let iter = self.db.iterator_cf(cf_handle, IteratorMode::Start);
let mut keys = Vec::new();
for item in iter {
let (key, _value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
keys.push(key.to_vec());
}
Ok(keys)
}
fn list_delta_keys_outside_window(&self, min_serial: u32, max_serial: u32) -> Result<Vec<Vec<u8>>> {
let cf_handle = self
.db
.cf_handle(CF_DELTA)
.ok_or_else(|| anyhow!("CF_DELTA not found"))?;
let iter = self.db.iterator_cf(cf_handle, IteratorMode::Start);
let mut keys = Vec::new();
for item in iter {
let (key, _value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
let serial = delta_key_serial(key.as_ref())
.ok_or_else(|| anyhow!("Invalid delta key"))?;
if !serial_in_window(serial, min_serial, max_serial) {
keys.push(key.to_vec());
}
}
Ok(keys)
}
}
fn serial_in_window(serial: u32, min_serial: u32, max_serial: u32) -> bool {
serial.wrapping_sub(min_serial) <= max_serial.wrapping_sub(min_serial)
}
fn validate_delta_window(deltas: &[Delta], min_serial: u32, max_serial: u32) -> Result<()> {
if deltas.is_empty() {
warn!(
"RTR store delta window validation failed: no persisted deltas for window [{}, {}]",
min_serial,
max_serial
);
return Err(anyhow!(
"delta window [{}, {}] has no persisted deltas",
min_serial,
max_serial
));
}
if deltas.first().map(Delta::serial) != Some(min_serial) {
warn!(
"RTR store delta window validation failed: first delta mismatch for window [{}, {}], got {:?}",
min_serial,
max_serial,
deltas.first().map(Delta::serial)
);
return Err(anyhow!(
"delta window starts at {}, but first persisted delta is {:?}",
min_serial,
deltas.first().map(Delta::serial)
));
}
if deltas.last().map(Delta::serial) != Some(max_serial) {
warn!(
"RTR store delta window validation failed: last delta mismatch for window [{}, {}], got {:?}",
min_serial,
max_serial,
deltas.last().map(Delta::serial)
);
return Err(anyhow!(
"delta window ends at {}, but last persisted delta is {:?}",
max_serial,
deltas.last().map(Delta::serial)
));
}
for pair in deltas.windows(2) {
if pair[1].serial() != pair[0].serial().wrapping_add(1) {
warn!(
"RTR store delta window validation failed: non-contiguous pair {} -> {} within window [{}, {}]",
pair[0].serial(),
pair[1].serial(),
min_serial,
max_serial
);
return Err(anyhow!(
"persisted deltas are not contiguous: {} -> {}",
pair[0].serial(),
pair[1].serial()
));
}
}
Ok(())
}
fn summarize_delta_keys(keys: &[Vec<u8>]) -> String {
let serials: Vec<u32> = keys
.iter()
.filter_map(|key| delta_key_serial(key))
.collect();
summarize_serials(&serials)
}
fn summarize_delta_serials(deltas: &[Delta]) -> String {
let serials: Vec<u32> = deltas.iter().map(Delta::serial).collect();
summarize_serials(&serials)
}
fn summarize_serials(serials: &[u32]) -> String {
const MAX_INLINE: usize = 12;
if serials.is_empty() {
return "[]".to_string();
}
if serials.len() <= MAX_INLINE {
return format!("{:?}", serials);
}
let head: Vec<u32> = serials.iter().take(6).copied().collect();
let tail: Vec<u32> = serials
.iter()
.rev()
.take(3)
.copied()
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect();
format!("{:?} ... {:?} (total={})", head, tail, serials.len())
}

View File

@@ -1,310 +0,0 @@
use rocksdb::{ColumnFamilyDescriptor, DB, Direction, IteratorMode, Options, WriteBatch};
use anyhow::{anyhow, Result};
use serde::{de::DeserializeOwned, Serialize};
use std::path::Path;
use std::sync::Arc;
use tokio::task;
use crate::rtr::cache::{Delta, Snapshot};
use crate::rtr::state::State;
const CF_META: &str = "meta";
const CF_SNAPSHOT: &str = "snapshot";
const CF_DELTA: &str = "delta";
const META_STATE: &[u8] = b"state";
const META_SESSION_ID: &[u8] = b"session_id";
const META_SERIAL: &[u8] = b"serial";
const META_DELTA_MIN: &[u8] = b"delta_min";
const META_DELTA_MAX: &[u8] = b"delta_max";
const DELTA_KEY_PREFIX: u8 = b'd';
fn delta_key(serial: u32) -> [u8; 5] {
let mut key = [0u8; 5];
key[0] = DELTA_KEY_PREFIX;
key[1..].copy_from_slice(&serial.to_be_bytes());
key
}
fn delta_key_serial(key: &[u8]) -> Option<u32> {
if key.len() != 5 || key[0] != DELTA_KEY_PREFIX {
return None;
}
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&key[1..]);
Some(u32::from_be_bytes(bytes))
}
#[derive(Clone)]
pub struct RtrStore {
db: Arc<DB>,
}
impl RtrStore {
/// Open or create DB with required column families.
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut opts = Options::default();
opts.create_if_missing(true);
opts.create_missing_column_families(true);
let cfs = vec![
ColumnFamilyDescriptor::new(CF_META, Options::default()),
ColumnFamilyDescriptor::new(CF_SNAPSHOT, Options::default()),
ColumnFamilyDescriptor::new(CF_DELTA, Options::default()),
];
let db = Arc::new(DB::open_cf_descriptors(&opts, path, cfs)?);
Ok(Self { db })
}
/// Common serialize/put.
fn put_cf<T: Serialize>(&self, cf: &str, key: &[u8], value: &T) -> Result<()> {
let cf_handle = self.db.cf_handle(cf).ok_or_else(|| anyhow!("CF not found"))?;
let data = serde_json::to_vec(value)?;
self.db.put_cf(cf_handle, key, data)?;
Ok(())
}
/// Common get/deserialize.
fn get_cf<T: DeserializeOwned>(&self, cf: &str, key: &[u8]) -> Result<Option<T>> {
let cf_handle = self.db.cf_handle(cf).ok_or_else(|| anyhow!("CF not found"))?;
if let Some(value) = self.db.get_cf(cf_handle, key)? {
let obj = serde_json::from_slice(&value)?;
Ok(Some(obj))
} else {
Ok(None)
}
}
/// Common delete.
fn delete_cf(&self, cf: &str, key: &[u8]) -> Result<()> {
let cf_handle = self.db.cf_handle(cf).ok_or_else(|| anyhow!("CF not found"))?;
self.db.delete_cf(cf_handle, key)?;
Ok(())
}
// ===============================
// Meta/state
// ===============================
pub fn set_state(&self, state: &State) -> Result<()> {
self.put_cf(CF_META, META_STATE, &state)
}
pub fn get_state(&self) -> Result<Option<State>> {
self.get_cf(CF_META, META_STATE)
}
pub fn set_meta(&self, meta: &State) -> Result<()> {
self.set_state(meta)
}
pub fn get_meta(&self) -> Result<Option<State>> {
self.get_state()
}
pub fn set_session_id(&self, session_id: u16) -> Result<()> {
self.put_cf(CF_META, META_SESSION_ID, &session_id)
}
pub fn get_session_id(&self) -> Result<Option<u16>> {
self.get_cf(CF_META, META_SESSION_ID)
}
pub fn set_serial(&self, serial: u32) -> Result<()> {
self.put_cf(CF_META, META_SERIAL, &serial)
}
pub fn get_serial(&self) -> Result<Option<u32>> {
self.get_cf(CF_META, META_SERIAL)
}
pub fn set_delta_window(&self, min_serial: u32, max_serial: u32) -> Result<()> {
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
let mut batch = WriteBatch::default();
batch.put_cf(meta_cf, META_DELTA_MIN, serde_json::to_vec(&min_serial)?);
batch.put_cf(meta_cf, META_DELTA_MAX, serde_json::to_vec(&max_serial)?);
self.db.write(batch)?;
Ok(())
}
pub fn get_delta_window(&self) -> Result<Option<(u32, u32)>> {
let min: Option<u32> = self.get_cf(CF_META, META_DELTA_MIN)?;
let max: Option<u32> = self.get_cf(CF_META, META_DELTA_MAX)?;
match (min, max) {
(Some(min), Some(max)) => Ok(Some((min, max))),
(None, None) => Ok(None),
_ => Err(anyhow!("Inconsistent DB state: delta window mismatch")),
}
}
pub fn delete_state(&self) -> Result<()> {
self.delete_cf(CF_META, META_STATE)
}
pub fn delete_serial(&self) -> Result<()> {
self.delete_cf(CF_META, META_SERIAL)
}
// ===============================
// Snapshot
// ===============================
pub fn save_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
let cf_handle = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let mut batch = WriteBatch::default();
let data = serde_json::to_vec(snapshot)?;
batch.put_cf(cf_handle, b"current", data);
self.db.write(batch)?;
Ok(())
}
pub fn get_snapshot(&self) -> Result<Option<Snapshot>> {
self.get_cf(CF_SNAPSHOT, b"current")
}
pub fn delete_snapshot(&self) -> Result<()> {
self.delete_cf(CF_SNAPSHOT, b"current")
}
pub fn save_snapshot_and_state(&self, snapshot: &Snapshot, state: &State) -> Result<()> {
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
let mut batch = WriteBatch::default();
batch.put_cf(snapshot_cf, b"current", serde_json::to_vec(snapshot)?);
batch.put_cf(meta_cf, META_STATE, serde_json::to_vec(state)?);
batch.put_cf(
meta_cf,
META_SESSION_ID,
serde_json::to_vec(&state.clone().session_id())?,
);
batch.put_cf(
meta_cf,
META_SERIAL,
serde_json::to_vec(&state.clone().serial())?,
);
self.db.write(batch)?;
Ok(())
}
pub fn save_snapshot_and_meta(
&self,
snapshot: &Snapshot,
session_id: u16,
serial: u32,
) -> Result<()> {
let mut batch = WriteBatch::default();
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
batch.put_cf(snapshot_cf, b"current", serde_json::to_vec(snapshot)?);
batch.put_cf(meta_cf, META_SESSION_ID, serde_json::to_vec(&session_id)?);
batch.put_cf(meta_cf, META_SERIAL, serde_json::to_vec(&serial)?);
self.db.write(batch)?;
Ok(())
}
pub fn save_snapshot_and_serial(&self, snapshot: &Snapshot, serial: u32) -> Result<()> {
let mut batch = WriteBatch::default();
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
batch.put_cf(snapshot_cf, b"current", serde_json::to_vec(snapshot)?);
batch.put_cf(meta_cf, META_SERIAL, serde_json::to_vec(&serial)?);
self.db.write(batch)?;
Ok(())
}
pub async fn save_snapshot_and_serial_async(
self: Arc<Self>,
snapshot: Snapshot,
serial: u32,
) -> Result<()> {
let snapshot_bytes = serde_json::to_vec(&snapshot)?;
let serial_bytes = serde_json::to_vec(&serial)?;
task::spawn_blocking(move || {
let mut batch = WriteBatch::default();
let snapshot_cf = self.db.cf_handle(CF_SNAPSHOT).ok_or_else(|| anyhow!("CF_SNAPSHOT not found"))?;
let meta_cf = self.db.cf_handle(CF_META).ok_or_else(|| anyhow!("CF_META not found"))?;
batch.put_cf(snapshot_cf, b"current", snapshot_bytes);
batch.put_cf(meta_cf, META_SERIAL, serial_bytes);
self.db.write(batch)?;
Ok::<_, anyhow::Error>(())
})
.await??;
Ok(())
}
pub fn load_snapshot_and_state(&self) -> Result<Option<(Snapshot, State)>> {
let snapshot: Option<Snapshot> = self.get_snapshot()?;
let state: Option<State> = self.get_state()?;
match (snapshot, state) {
(Some(snap), Some(state)) => Ok(Some((snap, state))),
(None, None) => Ok(None),
_ => Err(anyhow!("Inconsistent DB state: snapshot and state mismatch")),
}
}
pub fn load_snapshot_and_serial(&self) -> Result<Option<(Snapshot, u32)>> {
let snapshot: Option<Snapshot> = self.get_snapshot()?;
let serial: Option<u32> = self.get_serial()?;
match (snapshot, serial) {
(Some(snap), Some(serial)) => Ok(Some((snap, serial))),
(None, None) => Ok(None),
_ => Err(anyhow!("Inconsistent DB state: snapshot and serial mismatch")),
}
}
// ===============================
// Delta
// ===============================
pub fn save_delta(&self, delta: &Delta) -> Result<()> {
self.put_cf(CF_DELTA, &delta_key(delta.serial()), delta)
}
pub fn get_delta(&self, serial: u32) -> Result<Option<Delta>> {
self.get_cf(CF_DELTA, &delta_key(serial))
}
pub fn load_deltas_since(&self, serial: u32) -> Result<Vec<Delta>> {
let cf_handle = self
.db
.cf_handle(CF_DELTA)
.ok_or_else(|| anyhow!("CF_DELTA not found"))?;
let start_key = delta_key(serial.wrapping_add(1));
let iter = self.db.iterator_cf(
cf_handle,
IteratorMode::From(&start_key, Direction::Forward),
);
let mut out = Vec::new();
for item in iter {
let (key, value) = item.map_err(|e| anyhow!("rocksdb iterator error: {}", e))?;
let parsed = delta_key_serial(key.as_ref())
.ok_or_else(|| anyhow!("Invalid delta key"))?;
if parsed <= serial {
continue;
}
let delta: Delta = serde_json::from_slice(value.as_ref())?;
out.push(delta);
}
Ok(out)
}
pub fn delete_delta(&self, serial: u32) -> Result<()> {
self.delete_cf(CF_DELTA, &delta_key(serial))
}
}