rtr的输入换成ccr文件
This commit is contained in:
95
src/rtr/cache/core.rs
vendored
95
src/rtr/cache/core.rs
vendored
@@ -55,6 +55,7 @@ pub struct RtrCache {
|
||||
snapshot: Snapshot,
|
||||
deltas: VecDeque<Arc<Delta>>,
|
||||
max_delta: u8,
|
||||
prune_delta_by_snapshot_size: bool,
|
||||
timing: Timing,
|
||||
last_update_begin: DualTime,
|
||||
last_update_end: DualTime,
|
||||
@@ -71,6 +72,7 @@ impl Default for RtrCache {
|
||||
snapshot: Snapshot::empty(),
|
||||
deltas: VecDeque::with_capacity(100),
|
||||
max_delta: 100,
|
||||
prune_delta_by_snapshot_size: false,
|
||||
timing: Timing::default(),
|
||||
last_update_begin: now.clone(),
|
||||
last_update_end: now.clone(),
|
||||
@@ -83,6 +85,7 @@ pub struct RtrCacheBuilder {
|
||||
availability: Option<CacheAvailability>,
|
||||
session_ids: Option<SessionIds>,
|
||||
max_delta: Option<u8>,
|
||||
prune_delta_by_snapshot_size: Option<bool>,
|
||||
timing: Option<Timing>,
|
||||
serial: Option<u32>,
|
||||
snapshot: Option<Snapshot>,
|
||||
@@ -96,6 +99,7 @@ impl RtrCacheBuilder {
|
||||
availability: None,
|
||||
session_ids: None,
|
||||
max_delta: None,
|
||||
prune_delta_by_snapshot_size: None,
|
||||
timing: None,
|
||||
serial: None,
|
||||
snapshot: None,
|
||||
@@ -119,6 +123,11 @@ impl RtrCacheBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn prune_delta_by_snapshot_size(mut self, v: bool) -> Self {
|
||||
self.prune_delta_by_snapshot_size = Some(v);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn timing(mut self, v: Timing) -> Self {
|
||||
self.timing = Some(v);
|
||||
self
|
||||
@@ -147,6 +156,7 @@ impl RtrCacheBuilder {
|
||||
pub fn build(self) -> RtrCache {
|
||||
let now = DualTime::now();
|
||||
let max_delta = self.max_delta.unwrap_or(100);
|
||||
let prune_delta_by_snapshot_size = self.prune_delta_by_snapshot_size.unwrap_or(false);
|
||||
let timing = self.timing.unwrap_or_default();
|
||||
let snapshot = self.snapshot.unwrap_or_else(Snapshot::empty);
|
||||
let deltas = self
|
||||
@@ -167,6 +177,7 @@ impl RtrCacheBuilder {
|
||||
snapshot,
|
||||
deltas,
|
||||
max_delta,
|
||||
prune_delta_by_snapshot_size,
|
||||
timing,
|
||||
last_update_begin: now.clone(),
|
||||
last_update_end: now,
|
||||
@@ -231,24 +242,41 @@ impl RtrCache {
|
||||
}
|
||||
|
||||
fn push_delta(&mut self, delta: Arc<Delta>) {
|
||||
let dropped_serial = if self.deltas.len() >= self.max_delta as usize {
|
||||
self.deltas.front().map(|oldest| oldest.serial())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if self.deltas.len() >= self.max_delta as usize {
|
||||
self.deltas.pop_front();
|
||||
}
|
||||
debug!(
|
||||
"RTR cache pushing delta into window: delta_serial={}, announced={}, withdrawn={}, dropped_oldest_serial={:?}, window_size_before={}, max_delta={}",
|
||||
delta.serial(),
|
||||
delta.announced().len(),
|
||||
delta.withdrawn().len(),
|
||||
dropped_serial,
|
||||
self.deltas.len(),
|
||||
self.max_delta
|
||||
);
|
||||
self.deltas.push_back(delta);
|
||||
let mut dropped_serials = Vec::new();
|
||||
if self.prune_delta_by_snapshot_size {
|
||||
let snapshot_wire_size = estimate_snapshot_payload_wire_size(&self.snapshot);
|
||||
let mut cumulative_delta_wire_size =
|
||||
estimate_delta_window_payload_wire_size(&self.deltas);
|
||||
while !self.deltas.is_empty()
|
||||
&& cumulative_delta_wire_size >= snapshot_wire_size
|
||||
{
|
||||
if let Some(oldest) = self.deltas.pop_front() {
|
||||
dropped_serials.push(oldest.serial());
|
||||
cumulative_delta_wire_size =
|
||||
estimate_delta_window_payload_wire_size(&self.deltas);
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
"RTR cache delta-size pruning evaluated: snapshot_wire_size={}, cumulative_delta_wire_size={}, dropped_serials={:?}",
|
||||
snapshot_wire_size,
|
||||
cumulative_delta_wire_size,
|
||||
dropped_serials
|
||||
);
|
||||
}
|
||||
debug!(
|
||||
"RTR cache pushing delta into window: delta_serial={}, announced={}, withdrawn={}, dropped_oldest_serials={:?}, window_size_after={}, max_delta={}, prune_delta_by_snapshot_size={}",
|
||||
self.deltas.back().map(|d| d.serial()).unwrap_or(0),
|
||||
self.deltas.back().map(|d| d.announced().len()).unwrap_or(0),
|
||||
self.deltas.back().map(|d| d.withdrawn().len()).unwrap_or(0),
|
||||
dropped_serials,
|
||||
self.deltas.len(),
|
||||
self.max_delta,
|
||||
self.prune_delta_by_snapshot_size
|
||||
);
|
||||
}
|
||||
|
||||
fn replace_snapshot(&mut self, snapshot: Snapshot) {
|
||||
@@ -353,8 +381,8 @@ impl RtrCache {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.push_delta(delta.clone());
|
||||
self.replace_snapshot(new_snapshot.clone());
|
||||
self.push_delta(delta.clone());
|
||||
self.last_update_end = DualTime::now();
|
||||
let delta_window = self.delta_window();
|
||||
info!(
|
||||
@@ -574,6 +602,43 @@ impl RtrCache {
|
||||
}
|
||||
}
|
||||
|
||||
fn estimate_snapshot_payload_wire_size(snapshot: &Snapshot) -> usize {
|
||||
snapshot
|
||||
.payloads_for_rtr()
|
||||
.iter()
|
||||
.map(|payload| estimate_payload_wire_size(payload, true))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn estimate_delta_window_payload_wire_size(deltas: &VecDeque<Arc<Delta>>) -> usize {
|
||||
deltas
|
||||
.iter()
|
||||
.map(|delta| estimate_delta_wire_size(delta))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn estimate_delta_wire_size(delta: &Delta) -> usize {
|
||||
delta
|
||||
.payloads_for_rtr()
|
||||
.iter()
|
||||
.map(|(announce, payload)| estimate_payload_wire_size(payload, *announce))
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn estimate_payload_wire_size(payload: &Payload, announce: bool) -> usize {
|
||||
match payload {
|
||||
Payload::RouteOrigin(origin) => match origin.prefix().address {
|
||||
crate::data_model::resources::ip_resources::IPAddress::V4(_) => 20,
|
||||
crate::data_model::resources::ip_resources::IPAddress::V6(_) => 32,
|
||||
},
|
||||
Payload::RouterKey(key) => 8 + 20 + 4 + key.spki().len(),
|
||||
Payload::Aspa(aspa) => {
|
||||
let providers = if announce { aspa.provider_asns().len() } else { 0 };
|
||||
8 + 4 + providers * 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct LogicalState {
|
||||
before: Option<Payload>,
|
||||
|
||||
17
src/rtr/cache/store.rs
vendored
17
src/rtr/cache/store.rs
vendored
@@ -14,10 +14,16 @@ impl RtrCache {
|
||||
self,
|
||||
store: &RtrStore,
|
||||
max_delta: u8,
|
||||
prune_delta_by_snapshot_size: bool,
|
||||
timing: Timing,
|
||||
file_loader: impl Fn() -> Result<Vec<Payload>>,
|
||||
) -> Result<Self> {
|
||||
if let Some(cache) = try_restore_from_store(store, max_delta, timing)? {
|
||||
if let Some(cache) = try_restore_from_store(
|
||||
store,
|
||||
max_delta,
|
||||
prune_delta_by_snapshot_size,
|
||||
timing,
|
||||
)? {
|
||||
tracing::info!(
|
||||
"RTR cache restored from store: availability={:?}, session_ids={:?}, serial={}, snapshot(route_origins={}, router_keys={}, aspas={})",
|
||||
cache.availability(),
|
||||
@@ -80,6 +86,7 @@ impl RtrCache {
|
||||
.availability(availability)
|
||||
.session_ids(session_ids)
|
||||
.max_delta(max_delta)
|
||||
.prune_delta_by_snapshot_size(prune_delta_by_snapshot_size)
|
||||
.timing(timing)
|
||||
.serial(serial)
|
||||
.snapshot(snapshot)
|
||||
@@ -95,7 +102,12 @@ impl RtrCache {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_restore_from_store(store: &RtrStore, max_delta: u8, timing: Timing) -> Result<Option<RtrCache>> {
|
||||
fn try_restore_from_store(
|
||||
store: &RtrStore,
|
||||
max_delta: u8,
|
||||
prune_delta_by_snapshot_size: bool,
|
||||
timing: Timing,
|
||||
) -> Result<Option<RtrCache>> {
|
||||
let snapshot = store.get_snapshot()?;
|
||||
let session_ids = store.get_session_ids()?;
|
||||
let serial = store.get_serial()?;
|
||||
@@ -143,6 +155,7 @@ fn try_restore_from_store(store: &RtrStore, max_delta: u8, timing: Timing) -> Re
|
||||
.availability(availability)
|
||||
.session_ids(session_ids)
|
||||
.max_delta(max_delta)
|
||||
.prune_delta_by_snapshot_size(prune_delta_by_snapshot_size)
|
||||
.timing(timing)
|
||||
.serial(serial)
|
||||
.snapshot(snapshot)
|
||||
|
||||
361
src/rtr/ccr.rs
Normal file
361
src/rtr/ccr.rs
Normal file
@@ -0,0 +1,361 @@
|
||||
use std::fs;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use der_parser::ber::{BerObject, BerObjectContent};
|
||||
use der_parser::der::parse_der;
|
||||
|
||||
use crate::rtr::loader::{ParsedAspa, ParsedVrp, build_aspa, build_route_origin};
|
||||
use crate::rtr::payload::Payload;
|
||||
|
||||
const VRPS_INDEX: usize = 3;
|
||||
const VAPS_INDEX: usize = 4;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ParsedCcrSnapshot {
|
||||
pub content_type_oid: String,
|
||||
pub produced_at: Option<String>,
|
||||
pub vrps: Vec<ParsedVrp>,
|
||||
pub vaps: Vec<ParsedAspa>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct CcrPayloadConversion {
|
||||
pub payloads: Vec<Payload>,
|
||||
pub invalid_vrps: Vec<String>,
|
||||
pub invalid_vaps: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn load_ccr_snapshot_from_file(path: impl AsRef<Path>) -> Result<ParsedCcrSnapshot> {
|
||||
let path = path.as_ref();
|
||||
let bytes = fs::read(path)
|
||||
.with_context(|| format!("failed to read CCR file: {}", path.display()))?;
|
||||
parse_ccr_bytes(&bytes).with_context(|| format!("failed to parse CCR file: {}", path.display()))
|
||||
}
|
||||
|
||||
pub fn load_ccr_payloads_from_file(path: impl AsRef<Path>) -> Result<Vec<Payload>> {
|
||||
let snapshot = load_ccr_snapshot_from_file(path)?;
|
||||
snapshot_to_payloads(&snapshot)
|
||||
}
|
||||
|
||||
pub fn load_ccr_payloads_from_file_with_options(
|
||||
path: impl AsRef<Path>,
|
||||
strict: bool,
|
||||
) -> Result<CcrPayloadConversion> {
|
||||
let snapshot = load_ccr_snapshot_from_file(path)?;
|
||||
snapshot_to_payloads_with_options(&snapshot, strict)
|
||||
}
|
||||
|
||||
pub fn find_latest_ccr_file(dir: impl AsRef<Path>) -> Result<PathBuf> {
|
||||
let dir = dir.as_ref();
|
||||
let mut latest: Option<PathBuf> = None;
|
||||
|
||||
for entry in fs::read_dir(dir)
|
||||
.with_context(|| format!("failed to read CCR directory: {}", dir.display()))?
|
||||
{
|
||||
let entry =
|
||||
entry.with_context(|| format!("failed to iterate CCR directory: {}", dir.display()))?;
|
||||
let path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
if path.extension().and_then(|ext| ext.to_str()) != Some("ccr") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if latest.as_ref().is_none_or(|current| {
|
||||
file_name_key(&path) > file_name_key(current)
|
||||
}) {
|
||||
latest = Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
latest.ok_or_else(|| anyhow!("no .ccr files found in {}", dir.display()))
|
||||
}
|
||||
|
||||
pub fn snapshot_to_payloads(snapshot: &ParsedCcrSnapshot) -> Result<Vec<Payload>> {
|
||||
Ok(snapshot_to_payloads_with_options(snapshot, true)?.payloads)
|
||||
}
|
||||
|
||||
pub fn snapshot_to_payloads_with_options(
|
||||
snapshot: &ParsedCcrSnapshot,
|
||||
strict: bool,
|
||||
) -> Result<CcrPayloadConversion> {
|
||||
let mut payloads = Vec::with_capacity(snapshot.vrps.len() + snapshot.vaps.len());
|
||||
let mut invalid_vrps = Vec::new();
|
||||
let mut invalid_vaps = Vec::new();
|
||||
|
||||
for vrp in &snapshot.vrps {
|
||||
match build_route_origin(vrp.clone()) {
|
||||
Ok(origin) => payloads.push(Payload::RouteOrigin(origin)),
|
||||
Err(err) => {
|
||||
let msg = format!("invalid CCR VRP: {:?}: {}", vrp, err);
|
||||
if strict {
|
||||
return Err(anyhow!(msg));
|
||||
}
|
||||
invalid_vrps.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for vap in &snapshot.vaps {
|
||||
match build_aspa(vap.clone()) {
|
||||
Ok(aspa) => payloads.push(Payload::Aspa(aspa)),
|
||||
Err(err) => {
|
||||
let msg = format!("invalid CCR VAP/ASPA: {:?}: {}", vap, err);
|
||||
if strict {
|
||||
return Err(anyhow!(msg));
|
||||
}
|
||||
invalid_vaps.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CcrPayloadConversion {
|
||||
payloads,
|
||||
invalid_vrps,
|
||||
invalid_vaps,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_ccr_bytes(bytes: &[u8]) -> Result<ParsedCcrSnapshot> {
|
||||
let (rem, root) = parse_der(bytes).map_err(|err| anyhow!("failed to parse CCR DER: {err}"))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(anyhow!("CCR DER has {} trailing bytes", rem.len()));
|
||||
}
|
||||
|
||||
let root_items = sequence_items(&root)?;
|
||||
if root_items.len() != 2 {
|
||||
return Err(anyhow!("CCR root must contain exactly 2 items"));
|
||||
}
|
||||
|
||||
let content_type_oid = match &root_items[0].content {
|
||||
BerObjectContent::OID(oid) => oid.to_string(),
|
||||
other => {
|
||||
return Err(anyhow!(
|
||||
"CCR root first element must be content type OID, got {other:?}"
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let payload = decode_context_wrapped_sequence(&root_items[1])?;
|
||||
let payload_items = sequence_items(&payload)?;
|
||||
|
||||
let produced_at = payload_items.get(1).and_then(|obj| match &obj.content {
|
||||
BerObjectContent::GeneralizedTime(t) => Some(t.to_string()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let vrps = if let Some(vrps_field) = payload_items.get(VRPS_INDEX) {
|
||||
parse_vrps(vrps_field)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let vaps = if let Some(vaps_field) = payload_items.get(VAPS_INDEX) {
|
||||
parse_vaps(vaps_field)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
Ok(ParsedCcrSnapshot {
|
||||
content_type_oid,
|
||||
produced_at,
|
||||
vrps,
|
||||
vaps,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_vrps(field: &BerObject<'_>) -> Result<Vec<ParsedVrp>> {
|
||||
let vrp_state = decode_context_wrapped_sequence(field)?;
|
||||
let vrp_state_items = sequence_items(&vrp_state)?;
|
||||
let roa_payload_sets = vrp_state_items
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("ROA payload state missing payload set list"))?;
|
||||
let roa_payload_sets = sequence_items(roa_payload_sets)?;
|
||||
|
||||
let mut vrps = Vec::new();
|
||||
for payload_set in roa_payload_sets {
|
||||
let payload_set_items = sequence_items(payload_set)?;
|
||||
if payload_set_items.len() != 2 {
|
||||
return Err(anyhow!(
|
||||
"ROAPayloadSet must contain 2 items, got {}",
|
||||
payload_set_items.len()
|
||||
));
|
||||
}
|
||||
|
||||
let asn = as_u32(&payload_set_items[0], "ROAPayloadSet.asID")?;
|
||||
let families = sequence_items(&payload_set_items[1])?;
|
||||
|
||||
for family in families {
|
||||
let family_items = sequence_items(family)?;
|
||||
if family_items.len() != 2 {
|
||||
return Err(anyhow!(
|
||||
"ROAIPAddressFamily must contain 2 items, got {}",
|
||||
family_items.len()
|
||||
));
|
||||
}
|
||||
let address_family = as_octets(&family_items[0], "ROAIPAddressFamily.addressFamily")?;
|
||||
let addresses = sequence_items(&family_items[1])?;
|
||||
|
||||
for address in addresses {
|
||||
let address_items = sequence_items(address)?;
|
||||
let (prefix_addr, prefix_len, max_len) =
|
||||
parse_roa_address(address_family, address_items)?;
|
||||
vrps.push(ParsedVrp {
|
||||
prefix_addr,
|
||||
prefix_len,
|
||||
max_len,
|
||||
asn,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vrps)
|
||||
}
|
||||
|
||||
fn parse_vaps(field: &BerObject<'_>) -> Result<Vec<ParsedAspa>> {
|
||||
let vap_state = decode_context_wrapped_sequence(field)?;
|
||||
let vap_state_items = sequence_items(&vap_state)?;
|
||||
let aspa_payload_sets = vap_state_items
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("ASPA payload state missing payload set list"))?;
|
||||
let aspa_payload_sets = sequence_items(aspa_payload_sets)?;
|
||||
|
||||
let mut vaps = Vec::new();
|
||||
for payload_set in aspa_payload_sets {
|
||||
let payload_set_items = sequence_items(payload_set)?;
|
||||
if payload_set_items.len() != 2 {
|
||||
return Err(anyhow!(
|
||||
"ASPAPayloadSet must contain 2 items, got {}",
|
||||
payload_set_items.len()
|
||||
));
|
||||
}
|
||||
|
||||
let customer_asn = as_u32(&payload_set_items[0], "ASPAPayloadSet.customerASID")?;
|
||||
let provider_set = sequence_items(&payload_set_items[1])?;
|
||||
let mut provider_asns = Vec::with_capacity(provider_set.len());
|
||||
for provider in provider_set {
|
||||
provider_asns.push(as_u32(provider, "ASPAPayloadSet.providerASID")?);
|
||||
}
|
||||
|
||||
vaps.push(ParsedAspa {
|
||||
customer_asn,
|
||||
provider_asns,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(vaps)
|
||||
}
|
||||
|
||||
fn parse_roa_address(
|
||||
address_family: &[u8],
|
||||
items: &[BerObject<'_>],
|
||||
) -> Result<(IpAddr, u8, u8)> {
|
||||
let address = items
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("ROAIPAddress missing address field"))?;
|
||||
let (unused_bits, bit_string) = match &address.content {
|
||||
BerObjectContent::BitString(unused_bits, bit_string) => (*unused_bits, bit_string),
|
||||
other => {
|
||||
return Err(anyhow!(
|
||||
"ROAIPAddress.address must be BIT STRING, got {other:?}"
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let prefix_len = (bit_string.data.len() * 8)
|
||||
.checked_sub(usize::from(unused_bits))
|
||||
.ok_or_else(|| anyhow!("invalid ROAIPAddress BIT STRING length"))?;
|
||||
let prefix_len = u8::try_from(prefix_len)
|
||||
.map_err(|_| anyhow!("prefix length {prefix_len} does not fit in u8"))?;
|
||||
|
||||
let max_len = match items.get(1) {
|
||||
Some(value) => {
|
||||
let max_len = as_u32(value, "ROAIPAddress.maxLength")?;
|
||||
u8::try_from(max_len)
|
||||
.map_err(|_| anyhow!("maxLength {max_len} does not fit in u8"))?
|
||||
}
|
||||
None => prefix_len,
|
||||
};
|
||||
|
||||
let prefix_addr = match address_family {
|
||||
[0, 1] | [0, 1, ..] => {
|
||||
let mut octets = [0u8; 4];
|
||||
if bit_string.data.len() > octets.len() {
|
||||
return Err(anyhow!(
|
||||
"IPv4 ROAIPAddress too long: {} bytes",
|
||||
bit_string.data.len()
|
||||
));
|
||||
}
|
||||
octets[..bit_string.data.len()].copy_from_slice(bit_string.data);
|
||||
IpAddr::V4(Ipv4Addr::from(octets))
|
||||
}
|
||||
[0, 2] | [0, 2, ..] => {
|
||||
let mut octets = [0u8; 16];
|
||||
if bit_string.data.len() > octets.len() {
|
||||
return Err(anyhow!(
|
||||
"IPv6 ROAIPAddress too long: {} bytes",
|
||||
bit_string.data.len()
|
||||
));
|
||||
}
|
||||
octets[..bit_string.data.len()].copy_from_slice(bit_string.data);
|
||||
IpAddr::V6(Ipv6Addr::from(octets))
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"unsupported ROA address family octets: {:?}",
|
||||
address_family
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok((prefix_addr, prefix_len, max_len))
|
||||
}
|
||||
|
||||
fn sequence_items<'a>(obj: &'a BerObject<'a>) -> Result<&'a [BerObject<'a>]> {
|
||||
match &obj.content {
|
||||
BerObjectContent::Sequence(items) => Ok(items),
|
||||
other => Err(anyhow!("expected SEQUENCE, got {other:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_context_wrapped_sequence<'a>(obj: &'a BerObject<'a>) -> Result<BerObject<'a>> {
|
||||
match &obj.content {
|
||||
BerObjectContent::Unknown(any) => {
|
||||
let (rem, inner) = parse_der(any.data)
|
||||
.map_err(|err| anyhow!("failed to parse encapsulated DER: {err}"))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"encapsulated DER has {} trailing bytes",
|
||||
rem.len()
|
||||
));
|
||||
}
|
||||
Ok(inner)
|
||||
}
|
||||
other => Err(anyhow!(
|
||||
"expected context-specific wrapped field, got {other:?}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_u32(obj: &BerObject<'_>, field_name: &str) -> Result<u32> {
|
||||
obj.as_u32()
|
||||
.map_err(|err| anyhow!("{field_name} must be INTEGER fitting in u32: {err}"))
|
||||
}
|
||||
|
||||
fn as_octets<'a>(obj: &'a BerObject<'a>, field_name: &str) -> Result<&'a [u8]> {
|
||||
match &obj.content {
|
||||
BerObjectContent::OctetString(bytes) => Ok(bytes),
|
||||
other => Err(anyhow!("{field_name} must be OCTET STRING, got {other:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn file_name_key(path: &Path) -> String {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.map(|name| name.to_ascii_lowercase())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -7,3 +7,4 @@ pub mod error_type;
|
||||
pub mod state;
|
||||
pub mod server;
|
||||
pub mod loader;
|
||||
pub mod ccr;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::sync::{
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::{broadcast, watch, OwnedSemaphorePermit};
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{info, warn};
|
||||
use x509_parser::extensions::GeneralName;
|
||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
||||
|
||||
@@ -52,11 +52,7 @@ pub async fn handle_tcp_connection(
|
||||
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);
|
||||
}
|
||||
session.run().await?;
|
||||
|
||||
info!("RTR TCP session completed normally for {}", peer_addr);
|
||||
Ok(())
|
||||
@@ -81,16 +77,28 @@ pub async fn handle_tls_connection(
|
||||
info!("RTR TLS client certificate validated 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);
|
||||
}
|
||||
session.run().await?;
|
||||
|
||||
info!("RTR TLS session completed normally for {}", peer_addr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_expected_disconnect(err: &anyhow::Error) -> bool {
|
||||
err.chain().any(|cause| {
|
||||
if let Some(io_err) = cause.downcast_ref::<std::io::Error>() {
|
||||
return matches!(
|
||||
io_err.kind(),
|
||||
std::io::ErrorKind::BrokenPipe
|
||||
| std::io::ErrorKind::UnexpectedEof
|
||||
| std::io::ErrorKind::ConnectionAborted
|
||||
| std::io::ErrorKind::ConnectionReset
|
||||
| std::io::ErrorKind::NotConnected
|
||||
) || io_err.raw_os_error() == Some(10054);
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
fn verify_peer_certificate_ip(
|
||||
tls_stream: &tokio_rustls::server::TlsStream<TcpStream>,
|
||||
peer_ip: IpAddr,
|
||||
|
||||
@@ -16,7 +16,12 @@ 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::connection::{
|
||||
ConnectionGuard,
|
||||
handle_tcp_connection,
|
||||
handle_tls_connection,
|
||||
is_expected_disconnect,
|
||||
};
|
||||
use crate::rtr::server::config::RtrServiceConfig;
|
||||
use crate::rtr::server::tls::load_rustls_server_config_with_options;
|
||||
|
||||
@@ -136,12 +141,21 @@ impl RtrServer {
|
||||
if let Err(err) =
|
||||
handle_tcp_connection(cache, stream, peer_addr, notify_rx, shutdown_rx).await
|
||||
{
|
||||
warn!(
|
||||
"RTR TCP session closed with error: peer_addr={}, active_connections={}, err={:?}",
|
||||
peer_addr,
|
||||
guard.active_count(),
|
||||
err
|
||||
);
|
||||
if is_expected_disconnect(&err) {
|
||||
info!(
|
||||
"RTR TCP session closed by peer: peer_addr={}, active_connections={}, err={}",
|
||||
peer_addr,
|
||||
guard.active_count(),
|
||||
err
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"RTR TCP session closed with error: peer_addr={}, active_connections={}, err={}",
|
||||
peer_addr,
|
||||
guard.active_count(),
|
||||
err
|
||||
);
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
"RTR TCP session closed cleanly: peer_addr={}, active_connections={}",
|
||||
@@ -250,12 +264,21 @@ impl RtrServer {
|
||||
notify_rx,
|
||||
shutdown_rx,
|
||||
).await {
|
||||
warn!(
|
||||
"RTR TLS session closed with error: peer_addr={}, active_connections={}, err={:?}",
|
||||
peer_addr,
|
||||
guard.active_count(),
|
||||
err
|
||||
);
|
||||
if is_expected_disconnect(&err) {
|
||||
info!(
|
||||
"RTR TLS session closed by peer: peer_addr={}, active_connections={}, err={}",
|
||||
peer_addr,
|
||||
guard.active_count(),
|
||||
err
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"RTR TLS session closed with error: peer_addr={}, active_connections={}, err={}",
|
||||
peer_addr,
|
||||
guard.active_count(),
|
||||
err
|
||||
);
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
"RTR TLS session closed cleanly: peer_addr={}, active_connections={}",
|
||||
|
||||
Reference in New Issue
Block a user