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, 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) ) }