RTR server以及client的开发、test的补充
This commit is contained in:
521
src/rtr/cache.rs
521
src/rtr/cache.rs
@@ -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
185
src/rtr/loader.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
14
src/rtr/server/config.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/rtr/server/connection.rs
Normal file
79
src/rtr/server/connection.rs
Normal 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
219
src/rtr/server/listener.rs
Normal 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
12
src/rtr/server/mod.rs
Normal 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;
|
||||
16
src/rtr/server/notifier.rs
Normal file
16
src/rtr/server/notifier.rs
Normal 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
154
src/rtr/server/service.rs
Normal 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
52
src/rtr/server/tls.rs
Normal 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)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user