Files
rpki/src/rtr/cache/ordering.rs

344 lines
9.5 KiB
Rust

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