344 lines
9.5 KiB
Rust
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)
|
|
)
|
|
}
|