RTR server以及client的开发、test的补充

This commit is contained in:
xiuting.xu
2026-03-17 16:36:42 +08:00
parent 251dea8e5e
commit 9cbea4e2d0
31 changed files with 4970 additions and 260 deletions

View File

@@ -1,16 +1,22 @@
use std::cmp::Ordering;
use std::collections::{BTreeSet, VecDeque};
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey};
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,
@@ -79,6 +85,11 @@ pub struct Snapshot {
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 {
@@ -87,12 +98,18 @@ impl Snapshot {
router_keys: BTreeSet<RouterKey>,
aspas: BTreeSet<Aspa>,
) -> Self {
Snapshot {
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 {
@@ -118,37 +135,85 @@ impl Snapshot {
}
}
Snapshot {
origins,
router_keys,
aspas,
created_at: DualTime::now(),
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();
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_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()));
}
}
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_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()));
}
}
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()));
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)
@@ -169,6 +234,66 @@ impl Snapshot {
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)]
@@ -180,7 +305,10 @@ pub struct Delta {
}
impl Delta {
pub fn new(serial: u32, announced: Vec<Payload>, withdrawn: Vec<Payload>) -> Self {
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,
@@ -201,8 +329,8 @@ impl Delta {
&self.withdrawn
}
pub fn created_at(self) -> DualTime {
self.created_at
pub fn created_at(&self) -> DualTime {
self.created_at.clone()
}
}
@@ -219,7 +347,7 @@ pub struct RtrCache {
// Max number of deltas to keep.
max_delta: u8,
// Refresh interval.
refresh_interval: Duration,
timing: Timing,
// Last update begin time.
last_update_begin: DualTime,
// Last update end time.
@@ -237,7 +365,7 @@ impl Default for RtrCache {
snapshot: Snapshot::empty(),
deltas: VecDeque::with_capacity(100),
max_delta: 100,
refresh_interval: Duration::from_secs(600),
timing: Timing::default(),
last_update_begin: now.clone(),
last_update_end: now.clone(),
created_at: now,
@@ -248,9 +376,10 @@ impl Default for RtrCache {
pub struct RtrCacheBuilder {
session_id: Option<u16>,
max_delta: Option<u8>,
refresh_interval: Option<Duration>,
timing: Option<Timing>,
serial: Option<u32>,
snapshot: Option<Snapshot>,
deltas: Option<VecDeque<Arc<Delta>>>,
created_at: Option<DualTime>,
}
@@ -259,9 +388,10 @@ impl RtrCacheBuilder {
Self {
session_id: None,
max_delta: None,
refresh_interval: None,
timing: None,
serial: None,
snapshot: None,
deltas: None,
created_at: None,
}
}
@@ -276,8 +406,8 @@ impl RtrCacheBuilder {
self
}
pub fn refresh_interval(mut self, v: Duration) -> Self {
self.refresh_interval = Some(v);
pub fn timing(mut self, v: Timing) -> Self {
self.timing = Some(v);
self
}
@@ -291,6 +421,11 @@ impl RtrCacheBuilder {
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
@@ -299,8 +434,12 @@ impl RtrCacheBuilder {
pub fn build(self) -> RtrCache {
let now = DualTime::now();
let max_delta = self.max_delta.unwrap_or(100);
let refresh_interval = self.refresh_interval.unwrap_or(Duration::from_secs(600));
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);
@@ -309,9 +448,9 @@ impl RtrCacheBuilder {
session_id,
serial,
snapshot,
deltas: VecDeque::with_capacity(max_delta.into()),
deltas,
max_delta,
refresh_interval,
timing,
last_update_begin: now.clone(),
last_update_end: now,
created_at,
@@ -325,42 +464,44 @@ impl RtrCache {
self,
store: &RtrStore,
max_delta: u8,
refresh_interval: Duration,
timing: Timing,
file_loader: impl Fn() -> anyhow::Result<Vec<Payload>>,
) -> anyhow::Result<Self> {
let snapshot = store.get_snapshot()?;
let session_id = store.get_session_id()?;
let serial = store.get_serial()?;
if let (Some(snapshot), Some(session_id), Some(serial)) =
(snapshot, session_id, serial)
{
let mut cache = RtrCacheBuilder::new()
.session_id(session_id)
.max_delta(max_delta)
.refresh_interval(refresh_interval)
.serial(serial)
.snapshot(snapshot)
.build();
if let Some((min_serial, _max_serial)) = store.get_delta_window()? {
let deltas = store.load_deltas_since(min_serial.wrapping_sub(1))?;
for delta in deltas {
cache.push_delta(Arc::new(delta));
}
}
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, session_id, serial) {
if let Err(e) =
store.save_snapshot_and_meta(&snapshot_for_store, session_id, serial)
{
tracing::error!("persist failed: {:?}", e);
}
});
@@ -368,10 +509,65 @@ impl RtrCache {
Ok(RtrCacheBuilder::new()
.session_id(session_id)
.max_delta(max_delta)
.refresh_interval(refresh_interval)
.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())
.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 {
@@ -431,10 +627,19 @@ impl RtrCache {
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(());
}
@@ -462,8 +667,8 @@ impl RtrCache {
self.serial
}
pub fn refresh_interval(&self) -> Duration {
self.refresh_interval
pub fn timing(&self) -> Timing {
self.timing
}
pub fn retry_interval(&self) -> Duration {
@@ -477,6 +682,18 @@ impl RtrCache {
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 {
@@ -524,6 +741,7 @@ impl RtrCache {
return SerialResult::UpToDate;
}
let _ = newest_serial;
SerialResult::Deltas(result)
}
}
@@ -536,3 +754,176 @@ pub enum SerialResult {
/// 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())
}

185
src/rtr/loader.rs Normal file
View File

@@ -0,0 +1,185 @@
use std::fs;
use std::net::IpAddr;
use std::path::Path;
use std::str::FromStr;
use anyhow::{anyhow, Context, Result};
use crate::data_model::resources::as_resources::Asn;
use crate::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
use crate::rtr::payload::{Payload, RouteOrigin};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedVrp {
pub prefix_addr: IpAddr,
pub prefix_len: u8,
pub max_len: u8,
pub asn: u32,
}
/// 从文本文件中加载 VRP,并转换成 RTR Payload::RouteOrigin。
///
/// 文件格式:
///
/// ```text
/// # prefix,max_len,asn
/// 10.0.0.0/24,24,65001
/// 10.0.1.0/24,24,65002
/// 2001:db8::/32,48,65003
/// ```
pub fn load_vrps_from_file(path: impl AsRef<Path>) -> Result<Vec<Payload>> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.with_context(|| format!("failed to read VRP file: {}", path.display()))?;
let mut payloads = Vec::new();
for (idx, raw_line) in content.lines().enumerate() {
let line_no = idx + 1;
let line = raw_line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let vrp = parse_vrp_line(line)
.with_context(|| format!("invalid VRP line {}: {}", line_no, raw_line))?;
payloads.push(Payload::RouteOrigin(build_route_origin(vrp)?));
}
Ok(payloads)
}
/// 解析单行 VRP。
///
/// 格式:
/// `prefix/prefix_len,max_len,asn`
///
/// 例如:
/// `10.0.0.0/24,24,65001`
pub fn parse_vrp_line(line: &str) -> Result<ParsedVrp> {
let parts: Vec<_> = line.split(',').map(|s| s.trim()).collect();
if parts.len() != 3 {
return Err(anyhow!(
"expected format: <prefix>/<prefix_len>,<max_len>,<asn>"
));
}
let prefix_part = parts[0];
let max_len = u8::from_str(parts[1])
.with_context(|| format!("invalid max_len: {}", parts[1]))?;
let asn = u32::from_str(parts[2])
.with_context(|| format!("invalid asn: {}", parts[2]))?;
let (addr_str, prefix_len_str) = prefix_part
.split_once('/')
.ok_or_else(|| anyhow!("prefix must be in CIDR form, e.g. 10.0.0.0/24"))?;
let prefix_addr = IpAddr::from_str(addr_str.trim())
.with_context(|| format!("invalid IP address: {}", addr_str))?;
let prefix_len = u8::from_str(prefix_len_str.trim())
.with_context(|| format!("invalid prefix length: {}", prefix_len_str))?;
validate_vrp(prefix_addr, prefix_len, max_len)?;
Ok(ParsedVrp {
prefix_addr,
prefix_len,
max_len,
asn,
})
}
fn validate_vrp(prefix_addr: IpAddr, prefix_len: u8, max_len: u8) -> Result<()> {
match prefix_addr {
IpAddr::V4(_) => {
if prefix_len > 32 {
return Err(anyhow!("IPv4 prefix length must be <= 32"));
}
if max_len > 32 {
return Err(anyhow!("IPv4 max_len must be <= 32"));
}
if max_len < prefix_len {
return Err(anyhow!("IPv4 max_len must be >= prefix length"));
}
}
IpAddr::V6(_) => {
if prefix_len > 128 {
return Err(anyhow!("IPv6 prefix length must be <= 128"));
}
if max_len > 128 {
return Err(anyhow!("IPv6 max_len must be <= 128"));
}
if max_len < prefix_len {
return Err(anyhow!("IPv6 max_len must be >= prefix length"));
}
}
}
Ok(())
}
pub fn build_route_origin(vrp: ParsedVrp) -> Result<RouteOrigin> {
let address = match vrp.prefix_addr {
IpAddr::V4(addr) => IPAddress::from_ipv4(addr),
IpAddr::V6(addr) => IPAddress::from_ipv6(addr),
};
let prefix = IPAddressPrefix::new(address, vrp.prefix_len);
let asn = Asn::from(vrp.asn);
Ok(RouteOrigin::new(prefix, vrp.max_len, asn))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_ipv4_vrp_line() {
let got = parse_vrp_line("10.0.0.0/24,24,65001").unwrap();
assert_eq!(
got,
ParsedVrp {
prefix_addr: IpAddr::from_str("10.0.0.0").unwrap(),
prefix_len: 24,
max_len: 24,
asn: 65001,
}
);
}
#[test]
fn parse_ipv6_vrp_line() {
let got = parse_vrp_line("2001:db8::/32,48,65003").unwrap();
assert_eq!(
got,
ParsedVrp {
prefix_addr: IpAddr::from_str("2001:db8::").unwrap(),
prefix_len: 32,
max_len: 48,
asn: 65003,
}
);
}
#[test]
fn parse_rejects_invalid_max_len() {
let err = parse_vrp_line("10.0.0.0/24,16,65001").unwrap_err();
assert!(err.to_string().contains("max_len"));
}
#[test]
fn parse_rejects_invalid_ip() {
let err = parse_vrp_line("10.0.0.999/24,24,65001").unwrap_err();
assert!(err.to_string().contains("invalid IP"));
}
#[test]
fn parse_rejects_invalid_format() {
let err = parse_vrp_line("10.0.0.0/24,24").unwrap_err();
assert!(err.to_string().contains("expected format"));
}
}

View File

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

View File

@@ -1,13 +1,19 @@
use std::fmt::Debug;
use std::sync::Arc;
use std::time::Duration;
use asn1_rs::nom::character::streaming::u64;
use serde::{Deserialize, Serialize};
use crate::data_model::resources::as_resources::Asn;
use crate::data_model::resources::ip_resources::IPAddressPrefix;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
enum PayloadPduType {
Ipv4Prefix = 4,
Ipv6Prefix = 6,
RouterKey = 9,
Aspa = 11,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Ski([u8; 20]);
@@ -19,6 +25,14 @@ pub struct RouteOrigin {
}
impl RouteOrigin {
pub fn new(prefix: IPAddressPrefix, max_length: u8, asn: Asn) -> Self {
Self {
prefix,
max_length,
asn,
}
}
pub fn prefix(&self) -> &IPAddressPrefix {
&self.prefix
}
@@ -37,13 +51,29 @@ impl RouteOrigin {
pub struct RouterKey {
subject_key_identifier: Ski,
asn: Asn,
subject_public_key_info: Arc<[u8]>,
subject_public_key_info: Vec<u8>,
}
impl RouterKey {
pub fn new(subject_key_identifier: Ski, asn: Asn, subject_public_key_info: Vec<u8>) -> Self {
Self {
subject_key_identifier,
asn,
subject_public_key_info,
}
}
pub fn ski(&self) -> Ski {
self.subject_key_identifier
}
pub fn asn(&self) -> Asn {
self.asn
}
pub fn spki(&self) -> &[u8] {
&self.subject_public_key_info
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
@@ -53,6 +83,16 @@ pub struct Aspa {
}
impl Aspa {
pub fn new(customer_asn: Asn, mut provider_asns: Vec<Asn>) -> Self {
provider_asns.sort();
provider_asns.dedup();
Self {
customer_asn,
provider_asns,
}
}
pub fn customer_asn(&self) -> Asn {
self.customer_asn
}
@@ -66,7 +106,7 @@ impl Aspa {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Payload {
/// A route origin authorisation.
/// A route origin.
RouteOrigin(RouteOrigin),
/// A BGPsec router key.
@@ -91,6 +131,10 @@ pub struct Timing {
}
impl Timing {
pub const fn new(refresh: u32, retry: u32, expire: u32) -> Self {
Self { refresh, retry, expire }
}
pub fn refresh(self) -> Duration {
Duration::from_secs(u64::from(self.refresh))
}
@@ -104,3 +148,13 @@ impl Timing {
}
}
impl Default for Timing {
fn default() -> Self {
Self {
refresh: 3600,
retry: 600,
expire: 7200,
}
}
}

View File

@@ -10,10 +10,11 @@ use anyhow::Result;
use std::slice;
use anyhow::bail;
use serde::Serialize;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
pub const HEADER_LEN: u32 = 8;
pub const HEADER_LEN: usize = 8;
pub const MAX_PDU_LEN: u32 = 65535;
pub const IPV4_PREFIX_LEN: u32 = 20;
pub const IPV6_PREFIX_LEN: u32 = 32;
@@ -78,6 +79,10 @@ macro_rules! concrete {
self.header.session_id()
}
pub fn pdu(&self) -> u8 {
self.header.pdu()
}
/// Returns the PDU size.
///
/// The size is returned as a `u32` since that type is used in
@@ -126,7 +131,7 @@ macro_rules! concrete {
) -> Result<Result<Self, Header>, io::Error> {
let mut res = Self::default();
sock.read_exact(res.header.as_mut()).await?;
if res.header.pdu() == Error::PDU {
if res.header.pdu() == ErrorReport::PDU {
// Since we should drop the session after an error, we
// can safely ignore all the rest of the error for now.
return Ok(Err(res.header))
@@ -183,7 +188,7 @@ macro_rules! concrete {
// 所有PDU公共头部信息
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct Header {
version: u8,
pdu: u8,
@@ -203,36 +208,34 @@ impl Header {
}
}
pub async fn read(sock: &mut TcpStream) -> Result<Self> {
pub async fn read<S: AsyncRead + Unpin>(sock: &mut S) -> Result<Self, io::Error> {
let mut buf = [0u8; HEADER_LEN];
// 1. 精确读取 8 字节
sock.read_exact(&mut buf).await?;
// 2. 手动解析(大端)
let version = buf[0];
let pdu = buf[1];
let reserved = u16::from_be_bytes([buf[2], buf[3]]);
let length = u32::from_be_bytes([
buf[4], buf[5], buf[6], buf[7],
]);
let session_id = u16::from_be_bytes([buf[2], buf[3]]);
let length = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
// 3. 基础合法性校验
if length < HEADER_LEN{
bail!("Invalid PDU length");
if length < HEADER_LEN as u32 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid PDU length",
));
}
// 限制最大长度
if length > MAX_PDU_LEN {
bail!("PDU too large");
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PDU too large",
));
}
Ok(Self {
version,
pdu,
session_id: reserved,
length,
session_id: session_id.to_be(),
length: length.to_be(),
})
}
@@ -259,7 +262,7 @@ impl Header {
common!(Header);
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct HeaderWithFlags {
version: u8,
pdu: u8,
@@ -294,7 +297,7 @@ impl HeaderWithFlags {
]);
// 3. 基础合法性校验
if length < HEADER_LEN{
if length < HEADER_LEN as u32{
bail!("Invalid PDU length");
}
@@ -324,7 +327,7 @@ impl HeaderWithFlags {
// Serial Notify
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct SerialNotify {
header: Header,
serial_number: u32,
@@ -340,13 +343,17 @@ impl SerialNotify {
}
}
pub fn serial_number(self) -> u32 {
self.serial_number
}
}
concrete!(SerialNotify);
// Serial Query
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct SerialQuery {
header: Header,
serial_number: u32,
@@ -372,7 +379,7 @@ concrete!(SerialQuery);
// Reset Query
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct ResetQuery {
header: Header
}
@@ -382,7 +389,7 @@ impl ResetQuery {
pub fn new(version: u8) -> Self {
ResetQuery {
header: Header::new(version, Self::PDU, ZERO_16, HEADER_LEN),
header: Header::new(version, Self::PDU, ZERO_16, HEADER_LEN as u32),
}
}
}
@@ -392,7 +399,7 @@ concrete!(ResetQuery);
// Cache Response
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct CacheResponse {
header: Header,
}
@@ -402,7 +409,7 @@ impl CacheResponse {
pub fn new(version: u8, session_id: u16) -> Self {
CacheResponse {
header: Header::new(version, Self::PDU, session_id, HEADER_LEN),
header: Header::new(version, Self::PDU, session_id, HEADER_LEN as u32),
}
}
}
@@ -411,7 +418,7 @@ concrete!(CacheResponse);
// Flags
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct Flags(u8);
impl Flags {
@@ -434,7 +441,7 @@ impl Flags {
// IPv4 Prefix
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct IPv4Prefix {
header: Header,
@@ -479,7 +486,7 @@ concrete!(IPv4Prefix);
// IPv6 Prefix
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct IPv6Prefix {
header: Header,
@@ -524,7 +531,7 @@ concrete!(IPv6Prefix);
// End of Data
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize)]
pub enum EndOfData {
V0(EndOfDataV0),
V1(EndOfDataV1),
@@ -544,7 +551,7 @@ impl EndOfData {
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct EndOfDataV0 {
header: Header,
@@ -567,7 +574,7 @@ concrete!(EndOfDataV0);
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct EndOfDataV1 {
header: Header,
@@ -605,7 +612,7 @@ concrete!(EndOfDataV1);
// Cache Reset
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct CacheReset {
header: Header,
}
@@ -615,7 +622,7 @@ impl CacheReset {
pub fn new(version: u8) -> Self{
CacheReset {
header: Header::new(version, Self::PDU, ZERO_16, HEADER_LEN)
header: Header::new(version, Self::PDU, ZERO_16, HEADER_LEN as u32)
}
}
}
@@ -624,7 +631,7 @@ concrete!(CacheReset);
// Error Report
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct ErrorReport {
octets: Vec<u8>,
}
@@ -703,7 +710,7 @@ impl ErrorReport {
// TODO: 补全
// Router Key
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct RouterKey {
header: HeaderWithFlags,
@@ -727,13 +734,13 @@ impl RouterKey {
+ 1 // flags
// + self.ski.as_ref().len()
+ 4 // ASN
+ self.subject_public_key_info.len() as u32;
+ self.subject_public_key_info.len();
let header = HeaderWithFlags::new(
self.header.version(),
Self::PDU,
self.flags,
length,
length as u32,
);
w.write_all(&[
@@ -755,6 +762,7 @@ impl RouterKey {
// ASPA
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize)]
pub struct Aspa{
header: HeaderWithFlags,
@@ -773,13 +781,13 @@ impl Aspa {
let length = HEADER_LEN
+ 1
+ 4
+ (self.provider_asns.len() as u32 * 4);
+ (self.provider_asns.len() * 4);
let header = HeaderWithFlags::new(
self.header.version(),
Self::PDU,
Flags::new(self.header.flags),
length,
length as u32,
);
w.write_all(&[
@@ -836,7 +844,7 @@ mod tests {
assert_eq!(decoded.version(), 1);
assert_eq!(decoded.session_id(), 42);
assert_eq!(decoded.serial_number, 100u32.to_be());
assert_eq!(decoded.serial_number(), 100u32.to_be());
}
#[tokio::test]

14
src/rtr/server/config.rs Normal file
View File

@@ -0,0 +1,14 @@
#[derive(Debug, Clone)]
pub struct RtrServiceConfig {
pub max_connections: usize,
pub notify_queue_size: usize,
}
impl Default for RtrServiceConfig {
fn default() -> Self {
Self {
max_connections: 1024,
notify_queue_size: 1024,
}
}
}

View File

@@ -0,0 +1,79 @@
use std::net::SocketAddr;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use anyhow::{Context, Result};
use tokio::net::TcpStream;
use tokio::sync::{broadcast, watch, OwnedSemaphorePermit};
use tracing::error;
use tokio_rustls::TlsAcceptor;
use crate::rtr::cache::SharedRtrCache;
use crate::rtr::session::RtrSession;
pub struct ConnectionGuard {
active_connections: Arc<AtomicUsize>,
_permit: OwnedSemaphorePermit,
}
impl ConnectionGuard {
pub fn new(
active_connections: Arc<AtomicUsize>,
permit: OwnedSemaphorePermit,
) -> Self {
active_connections.fetch_add(1, Ordering::Relaxed);
Self {
active_connections,
_permit: permit,
}
}
}
impl Drop for ConnectionGuard {
fn drop(&mut self) {
self.active_connections.fetch_sub(1, Ordering::Relaxed);
}
}
pub async fn handle_tcp_connection(
cache: SharedRtrCache,
stream: TcpStream,
peer_addr: SocketAddr,
notify_rx: broadcast::Receiver<()>,
shutdown_rx: watch::Receiver<bool>,
) -> Result<()> {
let session = RtrSession::new(cache, stream, notify_rx, shutdown_rx);
if let Err(err) = session.run().await {
error!("RTR TCP session run failed for {}: {:?}", peer_addr, err);
return Err(err);
}
Ok(())
}
pub async fn handle_tls_connection(
cache: SharedRtrCache,
stream: TcpStream,
peer_addr: SocketAddr,
acceptor: TlsAcceptor,
notify_rx: broadcast::Receiver<()>,
shutdown_rx: watch::Receiver<bool>,
) -> Result<()> {
let tls_stream = acceptor
.accept(stream)
.await
.with_context(|| format!("TLS handshake failed for {}", peer_addr))?;
let session = RtrSession::new(cache, tls_stream, notify_rx, shutdown_rx);
if let Err(err) = session.run().await {
error!("RTR TLS session run failed for {}: {:?}", peer_addr, err);
return Err(err);
}
Ok(())
}

219
src/rtr/server/listener.rs Normal file
View File

@@ -0,0 +1,219 @@
use std::net::SocketAddr;
use std::path::Path;
use std::sync::{
Arc,
atomic::AtomicUsize,
};
use anyhow::{Context, Result};
use tokio::net::TcpListener;
use tokio::sync::{broadcast, watch, Semaphore};
use tracing::{info, warn};
use rustls::ServerConfig;
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;
pub struct RtrServer {
bind_addr: SocketAddr,
cache: SharedRtrCache,
notify_tx: broadcast::Sender<()>,
shutdown_tx: watch::Sender<bool>,
connection_limiter: Arc<Semaphore>,
active_connections: Arc<AtomicUsize>,
}
impl RtrServer {
pub fn new(
bind_addr: SocketAddr,
cache: SharedRtrCache,
notify_tx: broadcast::Sender<()>,
shutdown_tx: watch::Sender<bool>,
connection_limiter: Arc<Semaphore>,
active_connections: Arc<AtomicUsize>,
) -> Self {
Self {
bind_addr,
cache,
notify_tx,
shutdown_tx,
connection_limiter,
active_connections,
}
}
pub fn bind_addr(&self) -> SocketAddr {
self.bind_addr
}
pub fn cache(&self) -> SharedRtrCache {
self.cache.clone()
}
pub fn active_connections(&self) -> usize {
self.active_connections.load(std::sync::atomic::Ordering::Relaxed)
}
pub async fn run_tcp(self) -> Result<()> {
let listener = TcpListener::bind(self.bind_addr)
.await
.with_context(|| format!("failed to bind TCP RTR server on {}", self.bind_addr))?;
let mut shutdown_rx = self.shutdown_tx.subscribe();
info!("RTR TCP server listening on {}", self.bind_addr);
loop {
tokio::select! {
changed = shutdown_rx.changed() => {
match changed {
Ok(()) => {
if *shutdown_rx.borrow() {
info!("RTR TCP listener {} shutting down", self.bind_addr);
return Ok(());
}
}
Err(_) => {
info!("RTR TCP listener {} shutdown channel closed", self.bind_addr);
return Ok(());
}
}
}
accept_res = listener.accept() => {
let (stream, peer_addr) = match accept_res {
Ok(v) => v,
Err(err) => {
warn!("RTR TCP accept failed: {}", err);
continue;
}
};
if let Err(err) = stream.set_nodelay(true) {
warn!("failed to enable TCP_NODELAY for {}: {}", peer_addr, err);
}
let permit = match self.connection_limiter.clone().try_acquire_owned() {
Ok(permit) => permit,
Err(_) => {
warn!(
"RTR TCP connection rejected for {}: max connections reached ({})",
peer_addr,
self.connection_limiter.available_permits()
);
drop(stream);
continue;
}
};
let cache = self.cache.clone();
let notify_rx = self.notify_tx.subscribe();
let shutdown_rx = self.shutdown_tx.subscribe();
let active_connections = self.active_connections.clone();
info!("RTR TCP client connected: {}", peer_addr);
tokio::spawn(async move {
let _guard = ConnectionGuard::new(active_connections, permit);
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);
} else {
info!("RTR TCP session {} closed", peer_addr);
}
});
}
}
}
}
pub async fn run_tls_from_pem(
self,
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>,
) -> Result<()> {
let tls_config = Arc::new(load_rustls_server_config(cert_path, key_path)?);
self.run_tls(tls_config).await
}
pub async fn run_tls(self, tls_config: Arc<ServerConfig>) -> Result<()> {
let listener = TcpListener::bind(self.bind_addr)
.await
.with_context(|| format!("failed to bind TLS RTR server on {}", self.bind_addr))?;
let acceptor = TlsAcceptor::from(tls_config);
let mut shutdown_rx = self.shutdown_tx.subscribe();
info!("RTR TLS server listening on {}", self.bind_addr);
loop {
tokio::select! {
changed = shutdown_rx.changed() => {
match changed {
Ok(()) => {
if *shutdown_rx.borrow() {
info!("RTR TLS listener {} shutting down", self.bind_addr);
return Ok(());
}
}
Err(_) => {
info!("RTR TLS listener {} shutdown channel closed", self.bind_addr);
return Ok(());
}
}
}
accept_res = listener.accept() => {
let (stream, peer_addr) = match accept_res {
Ok(v) => v,
Err(err) => {
warn!("RTR TLS accept failed: {}", err);
continue;
}
};
if let Err(err) = stream.set_nodelay(true) {
warn!("failed to enable TCP_NODELAY 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);
drop(stream);
continue;
}
};
let cache = self.cache.clone();
let acceptor = acceptor.clone();
let notify_rx = self.notify_tx.subscribe();
let shutdown_rx = self.shutdown_tx.subscribe();
let active_connections = self.active_connections.clone();
info!("RTR TLS client connected: {}", peer_addr);
tokio::spawn(async move {
let _guard = ConnectionGuard::new(active_connections, permit);
if let Err(err) = handle_tls_connection(
cache,
stream,
peer_addr,
acceptor,
notify_rx,
shutdown_rx,
).await {
warn!("RTR TLS session {} ended with error: {:?}", peer_addr, err);
} else {
info!("RTR TLS session {} closed", peer_addr);
}
});
}
}
}
}
}

12
src/rtr/server/mod.rs Normal file
View File

@@ -0,0 +1,12 @@
pub mod config;
pub mod connection;
pub mod listener;
pub mod notifier;
pub mod service;
pub mod tls;
pub use config::RtrServiceConfig;
pub use listener::RtrServer;
pub use notifier::RtrNotifier;
pub use service::{RtrService, RunningRtrService};
pub use tls::load_rustls_server_config;

View File

@@ -0,0 +1,16 @@
use tokio::sync::broadcast;
#[derive(Clone)]
pub struct RtrNotifier {
tx: broadcast::Sender<()>,
}
impl RtrNotifier {
pub fn new(tx: broadcast::Sender<()>) -> Self {
Self { tx }
}
pub fn notify_cache_updated(&self) {
let _ = self.tx.send(());
}
}

154
src/rtr/server/service.rs Normal file
View File

@@ -0,0 +1,154 @@
use std::net::SocketAddr;
use std::path::Path;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use tokio::sync::{broadcast, watch, Semaphore};
use tokio::task::JoinHandle;
use tracing::error;
use crate::rtr::cache::SharedRtrCache;
use crate::rtr::server::config::RtrServiceConfig;
use crate::rtr::server::listener::RtrServer;
use crate::rtr::server::notifier::RtrNotifier;
pub struct RtrService {
cache: SharedRtrCache,
notify_tx: broadcast::Sender<()>,
shutdown_tx: watch::Sender<bool>,
connection_limiter: Arc<Semaphore>,
active_connections: Arc<AtomicUsize>,
config: RtrServiceConfig,
}
impl RtrService {
pub fn new(cache: SharedRtrCache) -> Self {
Self::with_config(cache, RtrServiceConfig::default())
}
pub fn with_config(cache: SharedRtrCache, config: RtrServiceConfig) -> Self {
let (notify_tx, _) = broadcast::channel(config.notify_queue_size);
let (shutdown_tx, _) = watch::channel(false);
Self {
cache,
notify_tx,
shutdown_tx,
connection_limiter: Arc::new(Semaphore::new(config.max_connections)),
active_connections: Arc::new(AtomicUsize::new(0)),
config,
}
}
pub fn cache(&self) -> SharedRtrCache {
self.cache.clone()
}
pub fn notifier(&self) -> RtrNotifier {
RtrNotifier::new(self.notify_tx.clone())
}
pub fn notify_cache_updated(&self) {
let _ = self.notify_tx.send(());
}
pub fn active_connections(&self) -> usize {
self.active_connections.load(Ordering::Relaxed)
}
pub fn max_connections(&self) -> usize {
self.config.max_connections
}
pub fn tcp_server(&self, bind_addr: SocketAddr) -> RtrServer {
RtrServer::new(
bind_addr,
self.cache.clone(),
self.notify_tx.clone(),
self.shutdown_tx.clone(),
self.connection_limiter.clone(),
self.active_connections.clone(),
)
}
pub fn tls_server(&self, bind_addr: SocketAddr) -> RtrServer {
RtrServer::new(
bind_addr,
self.cache.clone(),
self.notify_tx.clone(),
self.shutdown_tx.clone(),
self.connection_limiter.clone(),
self.active_connections.clone(),
)
}
pub fn spawn_tcp(&self, bind_addr: SocketAddr) -> JoinHandle<()> {
let server = self.tcp_server(bind_addr);
tokio::spawn(async move {
if let Err(err) = server.run_tcp().await {
error!("RTR TCP server {} exited with error: {:?}", bind_addr, err);
}
})
}
pub fn spawn_tls_from_pem(
&self,
bind_addr: SocketAddr,
cert_path: impl AsRef<Path>,
key_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 server = self.tls_server(bind_addr);
tokio::spawn(async move {
if let Err(err) = server.run_tls_from_pem(cert_path, key_path).await {
error!("RTR TLS server {} exited with error: {:?}", bind_addr, err);
}
})
}
pub fn spawn_tcp_and_tls_from_pem(
&self,
tcp_bind_addr: SocketAddr,
tls_bind_addr: SocketAddr,
cert_path: impl AsRef<Path>,
key_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);
RunningRtrService {
shutdown_tx: self.shutdown_tx.clone(),
handles: vec![tcp_handle, tls_handle],
}
}
pub fn spawn_tcp_only(&self, tcp_bind_addr: SocketAddr) -> RunningRtrService {
let tcp_handle = self.spawn_tcp(tcp_bind_addr);
RunningRtrService {
shutdown_tx: self.shutdown_tx.clone(),
handles: vec![tcp_handle],
}
}
}
pub struct RunningRtrService {
shutdown_tx: watch::Sender<bool>,
handles: Vec<JoinHandle<()>>,
}
impl RunningRtrService {
pub fn shutdown(&self) {
let _ = self.shutdown_tx.send(true);
}
pub async fn wait(self) {
for handle in self.handles {
let _ = handle.await;
}
}
}

52
src/rtr/server/tls.rs Normal file
View File

@@ -0,0 +1,52 @@
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use rustls::ServerConfig;
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
pub fn load_rustls_server_config(
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>,
) -> 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 certs = load_certs(&cert_path)
.with_context(|| format!("failed to load certs from {}", cert_path.display()))?;
let key = load_private_key(&key_path)
.with_context(|| format!("failed to load private key from {}", key_path.display()))?;
let config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|e| anyhow!("invalid certificate/key pair: {}", e))?;
Ok(config)
}
fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let certs = rustls_pemfile::certs(&mut reader)
.collect::<std::result::Result<Vec<_>, _>>()?;
if certs.is_empty() {
return Err(anyhow!("no certificates found in {}", path.display()));
}
Ok(certs)
}
fn load_private_key(path: &Path) -> Result<PrivateKeyDer<'static>> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let key = rustls_pemfile::private_key(&mut reader)?
.ok_or_else(|| anyhow!("no private key found in {}", path.display()))?;
Ok(key)
}

View File

@@ -1,16 +1,16 @@
use std::sync::Arc;
use anyhow::{bail, Result};
use tokio::io;
use tokio::net::TcpStream;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::{broadcast, watch};
use tracing::warn;
use crate::rtr::cache::{Delta, RtrCache, SerialResult};
use crate::data_model::resources::ip_resources::IPAddress;
use crate::rtr::cache::{Delta, SerialResult, SharedRtrCache};
use crate::rtr::error_type::ErrorCode;
use crate::rtr::payload::{Payload, RouteOrigin, Timing};
use crate::rtr::payload::{Payload, RouteOrigin};
use crate::rtr::pdu::{
CacheReset, CacheResponse, EndOfData, ErrorReport, Flags, Header, IPv4Prefix, IPv6Prefix,
ResetQuery, SerialQuery,
ResetQuery, SerialNotify, SerialQuery,
};
const SUPPORTED_MAX_VERSION: u8 = 2;
@@ -26,59 +26,116 @@ enum SessionState {
Closed,
}
pub struct RtrSession {
cache: Arc<RtrCache>,
pub struct RtrSession<S> {
cache: SharedRtrCache,
version: Option<u8>,
stream: TcpStream,
stream: S,
state: SessionState,
notify_rx: broadcast::Receiver<()>,
shutdown_rx: watch::Receiver<bool>,
}
impl RtrSession {
pub fn new(cache: Arc<RtrCache>, stream: TcpStream) -> Self {
impl<S> RtrSession<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
pub fn new(
cache: SharedRtrCache,
stream: S,
notify_rx: broadcast::Receiver<()>,
shutdown_rx: watch::Receiver<bool>,
) -> Self {
Self {
cache,
version: None,
stream,
state: SessionState::Connected,
notify_rx,
shutdown_rx,
}
}
pub async fn run(mut self) -> Result<()> {
loop {
let header = match Header::read(&mut self.stream).await {
Ok(h) => h,
Err(_) => return Ok(()),
};
if self.version.is_none() {
self.negotiate_version(header.version()).await?;
} else if header.version() != self.version.unwrap() {
self.send_unsupported_version(self.version.unwrap()).await?;
bail!("version changed within session");
tokio::select! {
changed = self.shutdown_rx.changed() => {
match changed {
Ok(()) => {
if *self.shutdown_rx.borrow() {
self.state = SessionState::Closed;
return Ok(());
}
}
Err(_) => {
// shutdown sender dropped,按关闭处理
self.state = SessionState::Closed;
return Ok(());
}
}
}
match header.pdu() {
ResetQuery::PDU => {
let _ = ResetQuery::read_payload(header, &mut self.stream).await?;
self.handle_reset_query().await?;
}
SerialQuery::PDU => {
let query = SerialQuery::read_payload(header, &mut self.stream).await?;
let session_id = query.session_id();
let serial = u32::from_be(query.serial_number());
self.handle_serial(session_id, serial).await?;
}
ErrorReport::PDU => {
let _ = ErrorReport::skip_payload(header, &mut self.stream).await;
header_res = Header::read(&mut self.stream) => {
let header = match header_res {
Ok(h) => h,
Err(_) => {
self.state = SessionState::Closed;
return Ok(());
}
};
if self.version.is_none() {
self.negotiate_version(header.version()).await?;
} else if header.version() != self.version.unwrap() {
self.send_unsupported_version(self.version.unwrap()).await?;
self.state = SessionState::Closed;
return Ok(());
bail!("version changed within session");
}
_ => {
self.send_error(header.version(), ErrorCode::UnsupportedPduType, Some(&header), &[])
match header.pdu() {
ResetQuery::PDU => {
let _ = ResetQuery::read_payload(header, &mut self.stream).await?;
self.handle_reset_query().await?;
}
SerialQuery::PDU => {
let query = SerialQuery::read_payload(header, &mut self.stream).await?;
let session_id = query.session_id();
let serial = u32::from_be(query.serial_number());
self.handle_serial(session_id, serial).await?;
}
ErrorReport::PDU => {
let _ = ErrorReport::skip_payload(header, &mut self.stream).await;
self.state = SessionState::Closed;
return Ok(());
}
_ => {
self.send_error(
header.version(),
ErrorCode::UnsupportedPduType,
Some(&header),
&[],
)
.await?;
return Ok(());
self.state = SessionState::Closed;
return Ok(());
}
}
}
notify_res = self.notify_rx.recv(),
if self.state == SessionState::Established && self.version.is_some() => {
match notify_res {
Ok(()) => {
self.handle_notify().await?;
}
Err(broadcast::error::RecvError::Lagged(_)) => {
self.handle_notify().await?;
}
Err(broadcast::error::RecvError::Closed) => {
// notify 通道关闭,不影响已有会话,继续跑,真正关闭由 shutdown_rx 控制
}
}
}
}
}
}
@@ -110,38 +167,70 @@ impl RtrSession {
&[],
ErrorCode::UnsupportedProtocolVersion.description(),
)
.write(&mut self.stream)
.await
.write(&mut self.stream)
.await
}
async fn handle_reset_query(&mut self) -> Result<()> {
let (payloads, session_id, serial) = {
let cache = self
.cache
.read()
.map_err(|_| anyhow::anyhow!("cache read lock poisoned"))?;
let snapshot = cache.snapshot();
let payloads = snapshot.payloads_for_rtr();
let session_id = cache.session_id();
let serial = cache.serial();
(payloads, session_id, serial)
};
self.write_cache_response(session_id).await?;
self.send_payloads(&payloads, true).await?;
self.write_end_of_data(session_id, serial).await?;
self.state = SessionState::Established;
let snapshot = self.cache.snapshot();
self.write_cache_response().await?;
self.send_payloads(snapshot.payloads(), true).await?;
self.write_end_of_data(self.cache.session_id(), self.cache.serial())
.await?;
Ok(())
}
async fn handle_serial(&mut self, client_session: u16, client_serial: u32) -> Result<()> {
let current_session = self.cache.session_id();
let current_serial = self.cache.serial();
let serial_result = {
let cache = self
.cache
.read()
.map_err(|_| anyhow::anyhow!("cache read lock poisoned"))?;
cache.get_deltas_since(client_session, client_serial)
};
match self.cache.get_deltas_since(client_session, client_serial) {
match serial_result {
SerialResult::ResetRequired => {
self.write_cache_reset().await?;
self.state = SessionState::Established;
return Ok(());
}
SerialResult::UpToDate => {
let (current_session, current_serial) = {
let cache = self
.cache
.read()
.map_err(|_| anyhow::anyhow!("cache read lock poisoned"))?;
(cache.session_id(), cache.serial())
};
self.write_end_of_data(current_session, current_serial)
.await?;
self.state = SessionState::Established;
return Ok(());
}
SerialResult::Deltas(deltas) => {
self.write_cache_response().await?;
let (current_session, current_serial) = {
let cache = self
.cache
.read()
.map_err(|_| anyhow::anyhow!("cache read lock poisoned"))?;
(cache.session_id(), cache.serial())
};
self.write_cache_response(current_session).await?;
for delta in deltas {
self.send_delta(&delta).await?;
}
@@ -154,32 +243,55 @@ impl RtrSession {
Ok(())
}
async fn write_cache_response(&mut self) -> Result<()> {
let version = self.version.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "version not negotiated")
})?;
async fn handle_notify(&mut self) -> Result<()> {
let (session_id, serial) = {
let cache = self
.cache
.read()
.map_err(|_| anyhow::anyhow!("cache read lock poisoned"))?;
(cache.session_id(), cache.serial())
};
CacheResponse::new(version, self.cache.session_id())
self.send_serial_notify(session_id, serial).await
}
fn version(&self) -> io::Result<u8> {
self.version
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "version not negotiated"))
}
async fn send_serial_notify(&mut self, session_id: u16, serial: u32) -> Result<()> {
let version = self.version()?;
SerialNotify::new(version, session_id, serial)
.write(&mut self.stream)
.await?;
Ok(())
}
async fn write_cache_response(&mut self, session_id: u16) -> Result<()> {
let version = self.version()?;
CacheResponse::new(version, session_id)
.write(&mut self.stream)
.await?;
Ok(())
}
async fn write_cache_reset(&mut self) -> Result<()> {
let version = self.version.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "version not negotiated")
})?;
let version = self.version()?;
CacheReset::new(version).write(&mut self.stream).await?;
Ok(())
}
async fn write_end_of_data(&mut self, session_id: u16, serial: u32) -> Result<()> {
let version = self.version.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "version not negotiated")
})?;
let version = self.version()?;
let timing = {
let cache = self
.cache
.read()
.map_err(|_| anyhow::anyhow!("cache read lock poisoned"))?;
cache.timing()
};
let timing = self.timing();
let end = EndOfData::new(version, session_id, serial, timing);
match end {
EndOfData::V0(pdu) => pdu.write(&mut self.stream).await?,
@@ -189,20 +301,20 @@ impl RtrSession {
Ok(())
}
async fn send_payloads(&mut self, payloads: Vec<Payload>, announce: bool) -> Result<()> {
async fn send_payloads(&mut self, payloads: &[Payload], announce: bool) -> Result<()> {
for payload in payloads {
self.send_payload(&payload, announce).await?;
self.send_payload(payload, announce).await?;
}
Ok(())
}
async fn send_delta(&mut self, delta: &Arc<Delta>) -> Result<()> {
for payload in delta.withdrawn() {
self.send_payload(payload, false).await?;
}
async fn send_delta(&mut self, delta: &Delta) -> Result<()> {
for payload in delta.announced() {
self.send_payload(payload, true).await?;
}
for payload in delta.withdrawn() {
self.send_payload(payload, false).await?;
}
Ok(())
}
@@ -222,9 +334,7 @@ impl RtrSession {
}
async fn send_route_origin(&mut self, origin: &RouteOrigin, announce: bool) -> Result<()> {
let version = self.version.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "version not negotiated")
})?;
let version = self.version()?;
let flags = Flags::new(if announce {
ANNOUNCE_FLAG
@@ -236,15 +346,17 @@ impl RtrSession {
let prefix_len = prefix.prefix_length;
let max_len = origin.max_length();
if let Some(v4) = prefix.address.to_ipv4() {
IPv4Prefix::new(version, flags, prefix_len, max_len, v4, origin.asn())
.write(&mut self.stream)
.await?;
} else {
let v6 = prefix.address.to_ipv6();
IPv6Prefix::new(version, flags, prefix_len, max_len, v6, origin.asn())
.write(&mut self.stream)
.await?;
match prefix.address {
IPAddress::V4(v4) => {
IPv4Prefix::new(version, flags, prefix_len, max_len, v4, origin.asn())
.write(&mut self.stream)
.await?;
}
IPAddress::V6(v6) => {
IPv6Prefix::new(version, flags, prefix_len, max_len, v6, origin.asn())
.write(&mut self.stream)
.await?;
}
}
Ok(())
@@ -257,24 +369,10 @@ impl RtrSession {
offending_header: Option<&Header>,
text: &[u8],
) -> io::Result<()> {
let offending = offending_header
.map(|h| h.as_ref())
.unwrap_or(&[]);
let offending = offending_header.map(|h| h.as_ref()).unwrap_or(&[]);
ErrorReport::new(version, code.as_u16(), offending, text)
.write(&mut self.stream)
.await
}
fn timing(&self) -> Timing {
let refresh = self.cache.refresh_interval().as_secs();
let retry = self.cache.retry_interval().as_secs();
let expire = self.cache.expire_interval().as_secs();
Timing {
refresh: refresh.min(u32::MAX as u64) as u32,
retry: retry.min(u32::MAX as u64) as u32,
expire: expire.min(u32::MAX as u64) as u32,
}
}
}
}

View File

@@ -274,20 +274,30 @@ impl RtrStore {
}
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 mut out = Vec::new();
let start_key = delta_key(serial.wrapping_add(1));
let iter = self
let cf_handle = self
.db
.iterator_cf(cf_handle, IteratorMode::From(&start_key, Direction::Forward));
.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"))?;
for (key, value) in iter {
let parsed = delta_key_serial(&key).ok_or_else(|| anyhow!("Invalid delta key"))?;
if parsed <= serial {
continue;
}
let delta: Delta = serde_json::from_slice(&value)?;
let delta: Delta = serde_json::from_slice(value.as_ref())?;
out.push(delta);
}