增加bird部署;
优化内存占用
This commit is contained in:
27
src/rtr/cache/core.rs
vendored
27
src/rtr/cache/core.rs
vendored
@@ -58,21 +58,25 @@ pub struct VersionState {
|
||||
serial: u32,
|
||||
snapshot: Snapshot,
|
||||
#[serde(skip)]
|
||||
rtr_payloads: Arc<Vec<Payload>>,
|
||||
#[serde(skip)]
|
||||
deltas: VecDeque<Arc<Delta>>,
|
||||
}
|
||||
|
||||
impl VersionState {
|
||||
fn new(session_id: u16, serial: u32, snapshot: Snapshot, max_delta: u8) -> Self {
|
||||
let rtr_payloads = Arc::new(snapshot.payloads_for_rtr());
|
||||
Self {
|
||||
session_id,
|
||||
serial,
|
||||
snapshot,
|
||||
rtr_payloads,
|
||||
deltas: VecDeque::with_capacity(max_delta as usize),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RtrCache {
|
||||
availability: CacheAvailability,
|
||||
versions: [VersionState; VERSION_COUNT],
|
||||
@@ -190,11 +194,15 @@ impl RtrCacheBuilder {
|
||||
std::array::from_fn(|_| VecDeque::with_capacity(max_delta as usize))
|
||||
});
|
||||
|
||||
let versions = std::array::from_fn(|idx| VersionState {
|
||||
session_id: session_ids.as_array()[idx],
|
||||
serial: serials[idx],
|
||||
snapshot: snapshots[idx].clone(),
|
||||
deltas: deltas[idx].clone(),
|
||||
let versions = std::array::from_fn(|idx| {
|
||||
let snapshot = snapshots[idx].clone();
|
||||
VersionState {
|
||||
session_id: session_ids.as_array()[idx],
|
||||
serial: serials[idx],
|
||||
rtr_payloads: Arc::new(snapshot.payloads_for_rtr()),
|
||||
snapshot,
|
||||
deltas: deltas[idx].clone(),
|
||||
}
|
||||
});
|
||||
|
||||
let created_at = self.created_at.unwrap_or_else(|| now.clone());
|
||||
@@ -222,6 +230,7 @@ impl RtrCache {
|
||||
self.availability = CacheAvailability::NoDataAvailable;
|
||||
for version_state in &mut self.versions {
|
||||
version_state.snapshot = Snapshot::empty();
|
||||
version_state.rtr_payloads = Arc::new(Vec::new());
|
||||
version_state.deltas.clear();
|
||||
}
|
||||
}
|
||||
@@ -238,6 +247,7 @@ impl RtrCache {
|
||||
state.session_id = new_session_ids.get(v);
|
||||
state.serial = 1;
|
||||
state.snapshot = project_snapshot_for_version(source_snapshot, v);
|
||||
state.rtr_payloads = Arc::new(state.snapshot.payloads_for_rtr());
|
||||
state.deltas.clear();
|
||||
}
|
||||
self.last_update_end = DualTime::now();
|
||||
@@ -352,6 +362,7 @@ impl RtrCache {
|
||||
}
|
||||
|
||||
state.snapshot = projected;
|
||||
state.rtr_payloads = Arc::new(state.snapshot.payloads_for_rtr());
|
||||
Self::push_delta(
|
||||
state,
|
||||
self.max_delta,
|
||||
@@ -427,6 +438,10 @@ impl RtrCache {
|
||||
self.versions[version_index(version)].snapshot.clone()
|
||||
}
|
||||
|
||||
pub fn rtr_payloads_for_version(&self, version: u8) -> Arc<Vec<Payload>> {
|
||||
self.versions[version_index(version)].rtr_payloads.clone()
|
||||
}
|
||||
|
||||
pub fn serial_for_version(&self, version: u8) -> u32 {
|
||||
self.versions[version_index(version)].serial
|
||||
}
|
||||
|
||||
6
src/rtr/cache/mod.rs
vendored
6
src/rtr/cache/mod.rs
vendored
@@ -9,6 +9,8 @@ pub use ordering::{
|
||||
OrderingViolation, validate_payload_updates_for_rtr, validate_payloads_for_rtr,
|
||||
};
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type SharedRtrCache = Arc<RwLock<RtrCache>>;
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
pub type SharedRtrCache = Arc<ArcSwap<RtrCache>>;
|
||||
|
||||
42
src/rtr/cache/model.rs
vendored
42
src/rtr/cache/model.rs
vendored
@@ -1,4 +1,5 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::OnceLock;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
@@ -282,6 +283,8 @@ pub struct Delta {
|
||||
announced: Vec<Payload>,
|
||||
withdrawn: Vec<Payload>,
|
||||
created_at: DualTime,
|
||||
#[serde(skip)]
|
||||
updates_for_rtr: OnceLock<Vec<(bool, Payload)>>,
|
||||
}
|
||||
|
||||
impl Delta {
|
||||
@@ -291,12 +294,16 @@ impl Delta {
|
||||
|
||||
sort_payloads_for_rtr(&mut announced, true);
|
||||
sort_payloads_for_rtr(&mut withdrawn, false);
|
||||
let cached_updates = build_payload_updates_for_rtr(&announced, &withdrawn);
|
||||
let updates_for_rtr = OnceLock::new();
|
||||
let _ = updates_for_rtr.set(cached_updates);
|
||||
|
||||
Delta {
|
||||
serial,
|
||||
announced,
|
||||
withdrawn,
|
||||
created_at: DualTime::now(),
|
||||
updates_for_rtr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,18 +327,31 @@ impl Delta {
|
||||
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
|
||||
pub fn payload_updates_for_rtr(&self) -> &[(bool, Payload)] {
|
||||
self.updates_for_rtr
|
||||
.get_or_init(|| build_payload_updates_for_rtr(&self.announced, &self.withdrawn))
|
||||
.as_slice()
|
||||
}
|
||||
|
||||
pub fn payloads_for_rtr(&self) -> Vec<(bool, Payload)> {
|
||||
self.payload_updates_for_rtr().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
fn build_payload_updates_for_rtr(
|
||||
announced: &[Payload],
|
||||
withdrawn: &[Payload],
|
||||
) -> Vec<(bool, Payload)> {
|
||||
let mut updates = Vec::with_capacity(announced.len() + withdrawn.len());
|
||||
|
||||
updates.extend(announced.iter().cloned().map(|p| (true, p)));
|
||||
updates.extend(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>) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
@@ -14,7 +16,7 @@ use crate::rtr::cache::{
|
||||
validate_payloads_for_rtr,
|
||||
};
|
||||
use crate::rtr::error_type::ErrorCode;
|
||||
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey};
|
||||
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Timing};
|
||||
use crate::rtr::pdu::{
|
||||
Aspa as AspaPdu, CacheReset, CacheResponse, EndOfData, ErrorReport, Flags, HEADER_LEN, Header,
|
||||
IPv4Prefix, IPv6Prefix, ResetQuery, RouterKey as RouterKeyPdu, SerialNotify, SerialQuery,
|
||||
@@ -28,6 +30,83 @@ const WITHDRAW_FLAG: u8 = 0;
|
||||
|
||||
/// Per-session notify rate limit: no more than once per minute.
|
||||
const NOTIFY_MIN_INTERVAL: Duration = Duration::from_secs(60);
|
||||
/// Limit captured offending PDU bytes in memory for error reporting.
|
||||
const MAX_OFFENDING_PDU_CAPTURE: usize = 4096;
|
||||
/// Log rate limiting window and burst for repeated high-frequency events.
|
||||
const LOG_RATE_LIMIT_WINDOW: Duration = Duration::from_secs(30);
|
||||
const LOG_RATE_LIMIT_BURST: u32 = 10;
|
||||
|
||||
static SESSION_LOG_LIMITERS: OnceLock<Mutex<HashMap<&'static str, LogBucket>>> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct LogBucket {
|
||||
window_start: Instant,
|
||||
emitted: u32,
|
||||
suppressed: u32,
|
||||
}
|
||||
|
||||
fn allow_rate_limited_log(event: &'static str) -> Option<u32> {
|
||||
let now = Instant::now();
|
||||
let lock = SESSION_LOG_LIMITERS.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
let mut map = match lock.lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(poisoned) => poisoned.into_inner(),
|
||||
};
|
||||
|
||||
let bucket = map.entry(event).or_insert_with(|| LogBucket {
|
||||
window_start: now,
|
||||
emitted: 0,
|
||||
suppressed: 0,
|
||||
});
|
||||
|
||||
if now.duration_since(bucket.window_start) >= LOG_RATE_LIMIT_WINDOW {
|
||||
let suppressed = bucket.suppressed;
|
||||
bucket.window_start = now;
|
||||
bucket.emitted = 1;
|
||||
bucket.suppressed = 0;
|
||||
return Some(suppressed);
|
||||
}
|
||||
|
||||
if bucket.emitted < LOG_RATE_LIMIT_BURST {
|
||||
bucket.emitted += 1;
|
||||
let suppressed = bucket.suppressed;
|
||||
bucket.suppressed = 0;
|
||||
Some(suppressed)
|
||||
} else {
|
||||
bucket.suppressed += 1;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn warn_rate_limited<F>(event: &'static str, build: F)
|
||||
where
|
||||
F: FnOnce() -> String,
|
||||
{
|
||||
if let Some(suppressed) = allow_rate_limited_log(event) {
|
||||
if suppressed > 0 {
|
||||
warn!(
|
||||
"RTR session log rate-limited: event={}, suppressed_count={}",
|
||||
event, suppressed
|
||||
);
|
||||
}
|
||||
warn!("{}", build());
|
||||
}
|
||||
}
|
||||
|
||||
fn error_rate_limited<F>(event: &'static str, build: F)
|
||||
where
|
||||
F: FnOnce() -> String,
|
||||
{
|
||||
if let Some(suppressed) = allow_rate_limited_log(event) {
|
||||
if suppressed > 0 {
|
||||
warn!(
|
||||
"RTR session log rate-limited: event={}, suppressed_count={}",
|
||||
event, suppressed
|
||||
);
|
||||
}
|
||||
error!("{}", build());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum SessionState {
|
||||
@@ -126,16 +205,25 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) if err.kind() == io::ErrorKind::TimedOut => {
|
||||
warn!("RTR session transport timeout while waiting for header: {}", self.session_summary());
|
||||
warn_rate_limited("session_header_timeout_waiting", || {
|
||||
format!(
|
||||
"RTR session transport timeout while waiting for header: state={}, negotiated_version={:?}",
|
||||
self.state_name(),
|
||||
self.version
|
||||
)
|
||||
});
|
||||
self.handle_transport_timeout(&[]).await?;
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"RTR session failed to read header: err={}, {}",
|
||||
err,
|
||||
self.session_summary()
|
||||
);
|
||||
warn_rate_limited("session_header_read_failed", || {
|
||||
format!(
|
||||
"RTR session failed to read header: err={}, state={}, negotiated_version={:?}",
|
||||
err,
|
||||
self.state_name(),
|
||||
self.version
|
||||
)
|
||||
});
|
||||
self.state = SessionState::Closed;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -143,11 +231,14 @@ where
|
||||
let header = match Header::from_raw(raw_header) {
|
||||
Ok(h) => h,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"RTR session received invalid header: err={}, {}",
|
||||
err,
|
||||
self.session_summary()
|
||||
);
|
||||
warn_rate_limited("session_header_invalid", || {
|
||||
format!(
|
||||
"RTR session received invalid header: err={}, state={}, negotiated_version={:?}",
|
||||
err,
|
||||
self.state_name(),
|
||||
self.version
|
||||
)
|
||||
});
|
||||
self.handle_header_read_error(raw_header, err).await?;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -244,11 +335,14 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
warn!(
|
||||
"RTR session received unsupported PDU type {}, closing session: {}",
|
||||
header.pdu(),
|
||||
self.session_summary()
|
||||
);
|
||||
warn_rate_limited("session_unsupported_pdu", || {
|
||||
format!(
|
||||
"RTR session received unsupported PDU type {}, closing session: state={}, negotiated_version={:?}",
|
||||
header.pdu(),
|
||||
self.state_name(),
|
||||
self.version
|
||||
)
|
||||
});
|
||||
self.state = SessionState::Closed;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -259,15 +353,29 @@ where
|
||||
if self.state == SessionState::Established && self.version.is_some() => {
|
||||
match notify_res {
|
||||
Ok(()) => {
|
||||
debug!("RTR session handling cache update notify: {}", self.session_summary());
|
||||
debug!(
|
||||
"RTR session handling cache update notify: state={}, negotiated_version={:?}",
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
self.handle_notify().await?;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(_)) => {
|
||||
warn!("RTR session lagged on notify channel, forcing notify handling: {}", self.session_summary());
|
||||
warn_rate_limited("session_notify_lagged", || {
|
||||
format!(
|
||||
"RTR session lagged on notify channel, forcing notify handling: state={}, negotiated_version={:?}",
|
||||
self.state_name(),
|
||||
self.version
|
||||
)
|
||||
});
|
||||
self.handle_notify().await?;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!("RTR session notify channel closed, keeping session alive: {}", self.session_summary());
|
||||
debug!(
|
||||
"RTR session notify channel closed, keeping session alive: state={}, negotiated_version={:?}",
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
// keep session alive
|
||||
}
|
||||
}
|
||||
@@ -334,12 +442,14 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
warn!(
|
||||
"RTR session received unexpected protocol version in established session: established_version={}, received_version={}, pdu={}",
|
||||
established,
|
||||
header.version(),
|
||||
header.pdu()
|
||||
);
|
||||
warn_rate_limited("session_unexpected_protocol_version", || {
|
||||
format!(
|
||||
"RTR session received unexpected protocol version in established session: established_version={}, received_version={}, pdu={}",
|
||||
established,
|
||||
header.version(),
|
||||
header.pdu()
|
||||
)
|
||||
});
|
||||
|
||||
if header.pdu() != ErrorReport::PDU {
|
||||
let offending = self.read_full_pdu_bytes(header).await?;
|
||||
@@ -575,39 +685,37 @@ where
|
||||
self.version,
|
||||
offending_pdu.len()
|
||||
);
|
||||
let (data_available, payloads, session_id, serial) = {
|
||||
let (data_available, payloads, session_id, serial, timing) = {
|
||||
let version = self.version()?;
|
||||
let cache = self
|
||||
.cache
|
||||
.read()
|
||||
.map_err(|_| anyhow!("cache read lock poisoned"))?;
|
||||
let cache = self.cache.load_full();
|
||||
let data_available = cache.is_data_available();
|
||||
let snapshot = cache.snapshot_for_version(version);
|
||||
let payloads = snapshot.payloads_for_rtr();
|
||||
let payloads = cache.rtr_payloads_for_version(version);
|
||||
let session_id = cache.session_id_for_version(version);
|
||||
let serial = cache.serial_for_version(version);
|
||||
(data_available, payloads, session_id, serial)
|
||||
let timing = cache.timing();
|
||||
(data_available, payloads, session_id, serial, timing)
|
||||
};
|
||||
|
||||
if !data_available {
|
||||
self.send_no_data_available(offending_pdu, "cache data is not currently available")
|
||||
.await?;
|
||||
debug!(
|
||||
"RTR session replied No Data Available to Reset Query: {}",
|
||||
self.session_summary()
|
||||
"RTR session replied No Data Available to Reset Query: negotiated_version={:?}",
|
||||
self.version
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.write_cache_response(session_id).await?;
|
||||
self.send_payloads(&payloads, true).await?;
|
||||
self.write_end_of_data(session_id, serial).await?;
|
||||
self.send_payloads(payloads.as_slice(), true).await?;
|
||||
self.write_end_of_data(session_id, serial, timing).await?;
|
||||
debug!(
|
||||
"RTR session completed Reset Query: response_session_id={}, response_serial={}, payload_count={}, {}",
|
||||
"RTR session completed Reset Query: response_session_id={}, response_serial={}, payload_count={}, state={}, negotiated_version={:?}",
|
||||
session_id,
|
||||
serial,
|
||||
payloads.len(),
|
||||
self.session_summary()
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -627,25 +735,19 @@ where
|
||||
client_serial,
|
||||
offending_pdu.len()
|
||||
);
|
||||
let (data_available, current_session) = {
|
||||
let cache = self
|
||||
.cache
|
||||
.read()
|
||||
.map_err(|_| anyhow!("cache read lock poisoned"))?;
|
||||
(
|
||||
cache.is_data_available(),
|
||||
cache.session_id_for_version(version),
|
||||
)
|
||||
};
|
||||
let cache = self.cache.load_full();
|
||||
let data_available = cache.is_data_available();
|
||||
let current_session = cache.session_id_for_version(version);
|
||||
|
||||
if !data_available {
|
||||
self.send_no_data_available(offending_pdu, "cache data is not currently available")
|
||||
.await?;
|
||||
debug!(
|
||||
"RTR session replied No Data Available to Serial Query: client_session_id={}, client_serial={}, {}",
|
||||
"RTR session replied No Data Available to Serial Query: client_session_id={}, client_serial={}, state={}, negotiated_version={:?}",
|
||||
client_session,
|
||||
client_serial,
|
||||
self.session_summary()
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -663,75 +765,53 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
let serial_result = {
|
||||
let cache = self
|
||||
.cache
|
||||
.read()
|
||||
.map_err(|_| anyhow!("cache read lock poisoned"))?;
|
||||
cache.get_deltas_since_for_version(version, client_serial)
|
||||
};
|
||||
let serial_result = cache.get_deltas_since_for_version(version, client_serial);
|
||||
let current_session = cache.session_id_for_version(version);
|
||||
let current_serial = cache.serial_for_version(version);
|
||||
let timing = cache.timing();
|
||||
|
||||
match serial_result {
|
||||
SerialResult::ResetRequired => {
|
||||
self.write_cache_reset().await?;
|
||||
debug!(
|
||||
"RTR session replied Cache Reset to Serial Query: client_session_id={}, client_serial={}, {}",
|
||||
"RTR session replied Cache Reset to Serial Query: client_session_id={}, client_serial={}, state={}, negotiated_version={:?}",
|
||||
client_session,
|
||||
client_serial,
|
||||
self.session_summary()
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
SerialResult::UpToDate => {
|
||||
let (current_session, current_serial) = {
|
||||
let cache = self
|
||||
.cache
|
||||
.read()
|
||||
.map_err(|_| anyhow!("cache read lock poisoned"))?;
|
||||
(
|
||||
cache.session_id_for_version(version),
|
||||
cache.serial_for_version(version),
|
||||
)
|
||||
};
|
||||
|
||||
self.write_cache_response(current_session).await?;
|
||||
self.write_end_of_data(current_session, current_serial)
|
||||
self.write_end_of_data(current_session, current_serial, timing)
|
||||
.await?;
|
||||
debug!(
|
||||
"RTR session replied CacheResponse+EndOfData (up-to-date) to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, {}",
|
||||
"RTR session replied CacheResponse+EndOfData (up-to-date) to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, state={}, negotiated_version={:?}",
|
||||
client_session,
|
||||
client_serial,
|
||||
current_session,
|
||||
current_serial,
|
||||
self.session_summary()
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
SerialResult::Delta(delta) => {
|
||||
let (current_session, current_serial) = {
|
||||
let cache = self
|
||||
.cache
|
||||
.read()
|
||||
.map_err(|_| anyhow!("cache read lock poisoned"))?;
|
||||
(
|
||||
cache.session_id_for_version(version),
|
||||
cache.serial_for_version(version),
|
||||
)
|
||||
};
|
||||
|
||||
self.write_cache_response(current_session).await?;
|
||||
self.send_delta(&delta).await?;
|
||||
self.write_end_of_data(current_session, current_serial)
|
||||
self.write_end_of_data(current_session, current_serial, timing)
|
||||
.await?;
|
||||
debug!(
|
||||
"RTR session replied delta to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, {}",
|
||||
"RTR session replied delta to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, state={}, negotiated_version={:?}",
|
||||
client_session,
|
||||
client_serial,
|
||||
current_session,
|
||||
current_serial,
|
||||
self.session_summary()
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -753,18 +833,16 @@ where
|
||||
if let Some(last) = self.last_notify_at {
|
||||
if now.duration_since(last) < NOTIFY_MIN_INTERVAL {
|
||||
debug!(
|
||||
"RTR session notify skipped due to rate limit: {}",
|
||||
self.session_summary()
|
||||
"RTR session notify skipped due to rate limit: state={}, negotiated_version={:?}",
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let (session_id, serial) = {
|
||||
let cache = self
|
||||
.cache
|
||||
.read()
|
||||
.map_err(|_| anyhow!("cache read lock poisoned"))?;
|
||||
let cache = self.cache.load_full();
|
||||
(
|
||||
cache.session_id_for_version(version),
|
||||
cache.serial_for_version(version),
|
||||
@@ -772,56 +850,50 @@ where
|
||||
};
|
||||
|
||||
debug!(
|
||||
"RTR session sending SerialNotify: notify_session_id={}, notify_serial={}, {}",
|
||||
"RTR session sending SerialNotify: notify_session_id={}, notify_serial={}, state={}, negotiated_version={:?}",
|
||||
session_id,
|
||||
serial,
|
||||
self.session_summary()
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
if let Err(err) = SerialNotify::new(version, session_id, serial)
|
||||
.write(&mut self.stream)
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"RTR session failed to send SerialNotify: err={}, notify_session_id={}, notify_serial={}, {}",
|
||||
err,
|
||||
session_id,
|
||||
serial,
|
||||
self.session_summary()
|
||||
);
|
||||
error_rate_limited("session_serial_notify_send_failed", || {
|
||||
format!(
|
||||
"RTR session failed to send SerialNotify: err={}, notify_session_id={}, notify_serial={}, state={}, negotiated_version={:?}",
|
||||
err,
|
||||
session_id,
|
||||
serial,
|
||||
self.state_name(),
|
||||
self.version
|
||||
)
|
||||
});
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
self.last_notify_at = Some(now);
|
||||
info!(
|
||||
"RTR session sent SerialNotify: notify_session_id={}, notify_serial={}, {}",
|
||||
"RTR session sent SerialNotify: notify_session_id={}, notify_serial={}, state={}, negotiated_version={:?}",
|
||||
session_id,
|
||||
serial,
|
||||
self.session_summary()
|
||||
self.state_name(),
|
||||
self.version
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn session_summary(&self) -> String {
|
||||
let serial = self
|
||||
.cache
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|cache| {
|
||||
self.version
|
||||
.map(|version| cache.serial_for_version(version))
|
||||
})
|
||||
.map(|serial| serial.to_string())
|
||||
.unwrap_or_else(|| "<unavailable>".to_string());
|
||||
let session_id = self
|
||||
.version
|
||||
.and_then(|version| {
|
||||
self.cache
|
||||
.read()
|
||||
.ok()
|
||||
.map(|cache| cache.session_id_for_version(version))
|
||||
})
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let (session_id, serial) = if let Some(version) = self.version {
|
||||
let cache = self.cache.load_full();
|
||||
(
|
||||
cache.session_id_for_version(version).to_string(),
|
||||
cache.serial_for_version(version).to_string(),
|
||||
)
|
||||
} else {
|
||||
("<unknown>".to_string(), "<unavailable>".to_string())
|
||||
};
|
||||
format!(
|
||||
"state={}, negotiated_version={:?}, cache_session_id={}, cache_serial={}",
|
||||
self.state_name(),
|
||||
@@ -849,12 +921,7 @@ where
|
||||
return timeout;
|
||||
}
|
||||
|
||||
let retry = self
|
||||
.cache
|
||||
.read()
|
||||
.ok()
|
||||
.map(|cache| cache.timing().retry())
|
||||
.unwrap_or_else(|| Duration::from_secs(600));
|
||||
let retry = self.cache.load_full().timing().retry();
|
||||
|
||||
retry.checked_mul(3).unwrap_or(retry)
|
||||
}
|
||||
@@ -878,15 +945,8 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_end_of_data(&mut self, session_id: u16, serial: u32) -> Result<()> {
|
||||
async fn write_end_of_data(&mut self, session_id: u16, serial: u32, timing: Timing) -> Result<()> {
|
||||
let version = self.version()?;
|
||||
let timing = {
|
||||
let cache = self
|
||||
.cache
|
||||
.read()
|
||||
.map_err(|_| anyhow!("cache read lock poisoned"))?;
|
||||
cache.timing()
|
||||
};
|
||||
|
||||
let end = EndOfData::new(version, session_id, serial, timing)?;
|
||||
debug!(
|
||||
@@ -932,7 +992,7 @@ where
|
||||
}
|
||||
|
||||
async fn send_delta(&mut self, delta: &Delta) -> Result<()> {
|
||||
let updates = delta.payloads_for_rtr();
|
||||
let updates = delta.payload_updates_for_rtr();
|
||||
// draft-ietf-sidrops-8210bis-25 Section 11.4 / 12 define Ordering Error
|
||||
// for the party receiving out-of-order PDUs. A validator failure here
|
||||
// means we are about to send an invalid sequence, so abort locally
|
||||
@@ -953,7 +1013,7 @@ where
|
||||
aspas
|
||||
);
|
||||
for (announce, payload) in updates {
|
||||
self.send_payload(&payload, announce).await?;
|
||||
self.send_payload(payload, *announce).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1065,26 +1125,30 @@ where
|
||||
text: &[u8],
|
||||
) -> io::Result<()> {
|
||||
let text_preview = String::from_utf8_lossy(text);
|
||||
warn!(
|
||||
"RTR session sending ErrorReport: version={}, error_code={}({}), offending_pdu_len={}, text={}",
|
||||
version,
|
||||
code.as_u16(),
|
||||
code.description(),
|
||||
offending_pdu.len(),
|
||||
text_preview
|
||||
);
|
||||
warn_rate_limited("session_error_report_sent", || {
|
||||
format!(
|
||||
"RTR session sending ErrorReport: version={}, error_code={}({}), offending_pdu_len={}, text={}",
|
||||
version,
|
||||
code.as_u16(),
|
||||
code.description(),
|
||||
offending_pdu.len(),
|
||||
text_preview
|
||||
)
|
||||
});
|
||||
ErrorReport::new(version, code.as_u16(), offending_pdu, text)
|
||||
.write(&mut self.stream)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn handle_pdu_read_error(&mut self, header: Header, err: io::Error) -> Result<()> {
|
||||
warn!(
|
||||
"RTR session failed to read established-session PDU payload: pdu={}, version={}, err={}",
|
||||
header.pdu(),
|
||||
header.version(),
|
||||
err
|
||||
);
|
||||
warn_rate_limited("session_established_pdu_read_failed", || {
|
||||
format!(
|
||||
"RTR session failed to read established-session PDU payload: pdu={}, version={}, err={}",
|
||||
header.pdu(),
|
||||
header.version(),
|
||||
err
|
||||
)
|
||||
});
|
||||
if err.kind() == io::ErrorKind::InvalidData {
|
||||
let offending = self.read_full_pdu_bytes(header).await?;
|
||||
let version = self.version()?;
|
||||
@@ -1099,12 +1163,14 @@ where
|
||||
}
|
||||
|
||||
async fn handle_first_pdu_read_error(&mut self, header: Header, err: io::Error) -> Result<()> {
|
||||
warn!(
|
||||
"RTR session failed to read first PDU payload: pdu={}, version={}, err={}",
|
||||
header.pdu(),
|
||||
header.version(),
|
||||
err
|
||||
);
|
||||
warn_rate_limited("session_first_pdu_read_failed", || {
|
||||
format!(
|
||||
"RTR session failed to read first PDU payload: pdu={}, version={}, err={}",
|
||||
header.pdu(),
|
||||
header.version(),
|
||||
err
|
||||
)
|
||||
});
|
||||
if err.kind() == io::ErrorKind::InvalidData {
|
||||
let offending = self.read_full_pdu_bytes(header).await?;
|
||||
let err_version =
|
||||
@@ -1129,10 +1195,12 @@ where
|
||||
raw_header: [u8; HEADER_LEN],
|
||||
err: io::Error,
|
||||
) -> Result<()> {
|
||||
warn!(
|
||||
"RTR session handling invalid header bytes: raw_header={:02X?}, err={}",
|
||||
raw_header, err
|
||||
);
|
||||
warn_rate_limited("session_invalid_header_bytes", || {
|
||||
format!(
|
||||
"RTR session handling invalid header bytes: raw_header={:02X?}, err={}",
|
||||
raw_header, err
|
||||
)
|
||||
});
|
||||
if err.kind() == io::ErrorKind::InvalidData {
|
||||
let version = match self.version {
|
||||
Some(version) => version,
|
||||
@@ -1158,12 +1226,14 @@ where
|
||||
let version = self.version.unwrap_or(SUPPORTED_MAX_VERSION);
|
||||
let timeout = self.transport_timeout();
|
||||
let detail = format!("transport stalled for longer than {:?}", timeout);
|
||||
warn!(
|
||||
"RTR session transport timeout: version={}, offending_pdu_len={}, timeout={:?}",
|
||||
version,
|
||||
offending_pdu.len(),
|
||||
timeout
|
||||
);
|
||||
warn_rate_limited("session_transport_timeout", || {
|
||||
format!(
|
||||
"RTR session transport timeout: version={}, offending_pdu_len={}, timeout={:?}",
|
||||
version,
|
||||
offending_pdu.len(),
|
||||
timeout
|
||||
)
|
||||
});
|
||||
|
||||
let _ = self
|
||||
.send_error(
|
||||
@@ -1187,16 +1257,39 @@ where
|
||||
));
|
||||
};
|
||||
|
||||
let mut bytes = Vec::with_capacity(total_len);
|
||||
let capture_len = total_len.min(MAX_OFFENDING_PDU_CAPTURE);
|
||||
let capture_payload_len = capture_len.saturating_sub(HEADER_LEN);
|
||||
let mut bytes = Vec::with_capacity(capture_len);
|
||||
bytes.extend_from_slice(header.as_ref());
|
||||
bytes.resize(total_len, 0);
|
||||
timeout(
|
||||
self.transport_timeout(),
|
||||
self.stream
|
||||
.read_exact(&mut bytes[HEADER_LEN..HEADER_LEN + payload_len]),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "transport read timed out"))??;
|
||||
bytes.resize(capture_len, 0);
|
||||
if capture_payload_len > 0 {
|
||||
timeout(
|
||||
self.transport_timeout(),
|
||||
self.stream
|
||||
.read_exact(&mut bytes[HEADER_LEN..HEADER_LEN + capture_payload_len]),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "transport read timed out"))??;
|
||||
}
|
||||
|
||||
let mut remaining = payload_len.saturating_sub(capture_payload_len);
|
||||
let mut discard_buf = [0u8; 1024];
|
||||
while remaining > 0 {
|
||||
let read_len = remaining.min(discard_buf.len());
|
||||
let read = timeout(
|
||||
self.transport_timeout(),
|
||||
self.stream.read(&mut discard_buf[..read_len]),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "transport read timed out"))??;
|
||||
if read == 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"unexpected EOF while draining oversized PDU payload",
|
||||
));
|
||||
}
|
||||
remaining -= read;
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user