新增slurm功能
This commit is contained in:
@@ -1 +1 @@
|
||||
pub mod test_helper;
|
||||
pub mod test_helper;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Write;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
|
||||
use rpki::rtr::cache::SerialResult;
|
||||
@@ -14,7 +14,9 @@ pub struct RtrDebugDumper {
|
||||
|
||||
impl RtrDebugDumper {
|
||||
pub fn new() -> Self {
|
||||
Self { entries: Vec::new() }
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<T: serde::Serialize>(&mut self, pdu: u8, body: &T) {
|
||||
@@ -150,15 +152,7 @@ pub fn v6_prefix(addr: Ipv6Addr, prefix_len: u8) -> IPAddressPrefix {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn v4_origin(
|
||||
a: u8,
|
||||
b: u8,
|
||||
c: u8,
|
||||
d: u8,
|
||||
prefix_len: u8,
|
||||
max_len: u8,
|
||||
asn: u32,
|
||||
) -> RouteOrigin {
|
||||
pub fn v4_origin(a: u8, b: u8, c: u8, d: u8, prefix_len: u8, max_len: u8, asn: u32) -> RouteOrigin {
|
||||
let prefix = v4_prefix(a, b, c, d, prefix_len);
|
||||
RouteOrigin::new(prefix, max_len, asn.into())
|
||||
}
|
||||
@@ -238,7 +232,11 @@ pub fn serial_result_to_string(result: &SerialResult) -> String {
|
||||
}
|
||||
|
||||
pub fn print_serial_result(label: &str, result: &SerialResult) {
|
||||
println!("\n===== {} =====\n{}\n", label, serial_result_to_string(result));
|
||||
println!(
|
||||
"\n===== {} =====\n{}\n",
|
||||
label,
|
||||
serial_result_to_string(result)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn bytes_to_hex(bytes: &[u8]) -> String {
|
||||
@@ -290,12 +288,8 @@ pub fn snapshot_hashes_to_string(snapshot: &rpki::rtr::cache::Snapshot) -> Strin
|
||||
|
||||
pub fn serial_result_detail_to_string(result: &rpki::rtr::cache::SerialResult) -> String {
|
||||
match result {
|
||||
rpki::rtr::cache::SerialResult::UpToDate => {
|
||||
" result: UpToDate\n".to_string()
|
||||
}
|
||||
rpki::rtr::cache::SerialResult::ResetRequired => {
|
||||
" result: ResetRequired\n".to_string()
|
||||
}
|
||||
rpki::rtr::cache::SerialResult::UpToDate => " result: UpToDate\n".to_string(),
|
||||
rpki::rtr::cache::SerialResult::ResetRequired => " result: ResetRequired\n".to_string(),
|
||||
rpki::rtr::cache::SerialResult::Delta(delta) => {
|
||||
let mut out = String::new();
|
||||
let _ = writeln!(&mut out, " result: Delta");
|
||||
@@ -316,4 +310,4 @@ pub fn indent_block(text: &str, spaces: usize) -> String {
|
||||
let _ = writeln!(&mut out, "{}{}", pad, line);
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod common;
|
||||
mod common;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
@@ -12,8 +12,7 @@ use common::test_helper::{
|
||||
use rpki::data_model::resources::as_resources::Asn;
|
||||
use rpki::rtr::cache::{
|
||||
CacheAvailability, Delta, RtrCacheBuilder, SerialResult, SessionIds, Snapshot,
|
||||
validate_payload_updates_for_rtr,
|
||||
validate_payloads_for_rtr,
|
||||
validate_payload_updates_for_rtr, validate_payloads_for_rtr,
|
||||
};
|
||||
use rpki::rtr::payload::{Aspa, Payload, RouterKey, Ski, Timing};
|
||||
use rpki::rtr::store::RtrStore;
|
||||
@@ -40,7 +39,11 @@ fn deltas_window_to_string(deltas: &VecDeque<Arc<Delta>>) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
fn get_deltas_since_input_to_string(cache_session_id: u16, cache_serial: u32, client_serial: u32) -> String {
|
||||
fn get_deltas_since_input_to_string(
|
||||
cache_session_id: u16,
|
||||
cache_serial: u32,
|
||||
client_serial: u32,
|
||||
) -> String {
|
||||
format!(
|
||||
"cache.session_id: {}\ncache.serial: {}\nclient_serial: {}\n",
|
||||
cache_session_id, cache_serial, client_serial
|
||||
@@ -118,7 +121,9 @@ async fn init_keeps_cache_running_when_file_loader_returns_no_data() {
|
||||
let store = RtrStore::open(dir.path()).unwrap();
|
||||
|
||||
let cache = rpki::rtr::cache::RtrCache::default()
|
||||
.init(&store, 16, false, Timing::new(600, 600, 7200), || Ok(vec![]))
|
||||
.init(&store, 16, false, Timing::new(600, 600, 7200), || {
|
||||
Ok(vec![])
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(!cache.is_data_available());
|
||||
@@ -144,12 +149,16 @@ async fn init_restores_wraparound_delta_window_from_store() {
|
||||
);
|
||||
let d_zero = Delta::new(
|
||||
0,
|
||||
vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))],
|
||||
vec![Payload::RouteOrigin(v4_origin(
|
||||
198, 51, 100, 0, 24, 24, 64497,
|
||||
))],
|
||||
vec![],
|
||||
);
|
||||
let d_one = Delta::new(
|
||||
1,
|
||||
vec![Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498))],
|
||||
vec![Payload::RouteOrigin(v4_origin(
|
||||
203, 0, 113, 0, 24, 24, 64498,
|
||||
))],
|
||||
vec![],
|
||||
);
|
||||
|
||||
@@ -188,7 +197,9 @@ async fn init_restores_wraparound_delta_window_from_store() {
|
||||
.unwrap();
|
||||
|
||||
let cache = rpki::rtr::cache::RtrCache::default()
|
||||
.init(&store, 16, false, Timing::new(600, 600, 7200), || Ok(Vec::new()))
|
||||
.init(&store, 16, false, Timing::new(600, 600, 7200), || {
|
||||
Ok(Vec::new())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
match cache.get_deltas_since(u32::MAX.wrapping_sub(1)) {
|
||||
@@ -205,8 +216,8 @@ async fn update_prunes_delta_window_when_cumulative_delta_size_reaches_snapshot_
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = RtrStore::open(dir.path()).unwrap();
|
||||
let valid_spki = vec![
|
||||
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
|
||||
0x01, 0x01, 0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
|
||||
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
|
||||
0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let initial_snapshot = Snapshot::from_payloads(vec![Payload::RouterKey(RouterKey::new(
|
||||
@@ -226,11 +237,14 @@ async fn update_prunes_delta_window_when_cumulative_delta_size_reaches_snapshot_
|
||||
.build();
|
||||
|
||||
cache
|
||||
.update(vec![Payload::RouterKey(RouterKey::new(
|
||||
Ski::from_bytes([1u8; 20]),
|
||||
Asn::from(64496u32),
|
||||
valid_spki,
|
||||
))], &store)
|
||||
.update(
|
||||
vec![Payload::RouterKey(RouterKey::new(
|
||||
Ski::from_bytes([1u8; 20]),
|
||||
Asn::from(64496u32),
|
||||
valid_spki,
|
||||
))],
|
||||
&store,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
match cache.get_deltas_since(1) {
|
||||
@@ -414,7 +428,10 @@ fn delta_new_sorts_announced_descending_and_withdrawn_ascending() {
|
||||
|
||||
let w0 = as_v4_route_origin(&delta.withdrawn()[0]);
|
||||
let w1 = as_v4_route_origin(&delta.withdrawn()[1]);
|
||||
assert_eq!(w0.prefix().address.to_ipv4(), Some(Ipv4Addr::new(10, 0, 0, 0)));
|
||||
assert_eq!(
|
||||
w0.prefix().address.to_ipv4(),
|
||||
Some(Ipv4Addr::new(10, 0, 0, 0))
|
||||
);
|
||||
assert_eq!(
|
||||
w1.prefix().address.to_ipv4(),
|
||||
Some(Ipv4Addr::new(203, 0, 113, 0))
|
||||
@@ -433,7 +450,8 @@ fn get_deltas_since_returns_up_to_date_when_client_serial_matches_current() {
|
||||
|
||||
let result = cache.get_deltas_since(100);
|
||||
|
||||
let input = get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 100);
|
||||
let input =
|
||||
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 100);
|
||||
let output = serial_result_detail_to_string(&result);
|
||||
|
||||
test_report(
|
||||
@@ -458,7 +476,9 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old() {
|
||||
));
|
||||
let d2 = Arc::new(Delta::new(
|
||||
102,
|
||||
vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))],
|
||||
vec![Payload::RouteOrigin(v4_origin(
|
||||
198, 51, 100, 0, 24, 24, 64497,
|
||||
))],
|
||||
vec![],
|
||||
));
|
||||
|
||||
@@ -506,12 +526,16 @@ fn get_deltas_since_returns_minimal_merged_delta() {
|
||||
));
|
||||
let d2 = Arc::new(Delta::new(
|
||||
102,
|
||||
vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))],
|
||||
vec![Payload::RouteOrigin(v4_origin(
|
||||
198, 51, 100, 0, 24, 24, 64497,
|
||||
))],
|
||||
vec![],
|
||||
));
|
||||
let d3 = Arc::new(Delta::new(
|
||||
103,
|
||||
vec![Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498))],
|
||||
vec![Payload::RouteOrigin(v4_origin(
|
||||
203, 0, 113, 0, 24, 24, 64498,
|
||||
))],
|
||||
vec![],
|
||||
));
|
||||
|
||||
@@ -584,7 +608,8 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future() {
|
||||
|
||||
let result = cache.get_deltas_since(101);
|
||||
|
||||
let input = get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 101);
|
||||
let input =
|
||||
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 101);
|
||||
let output = serial_result_detail_to_string(&result);
|
||||
|
||||
test_report(
|
||||
@@ -610,11 +635,7 @@ fn get_deltas_since_supports_incremental_updates_across_serial_wraparound() {
|
||||
vec![Payload::RouteOrigin(a.clone())],
|
||||
vec![],
|
||||
));
|
||||
let d_zero = Arc::new(Delta::new(
|
||||
0,
|
||||
vec![Payload::RouteOrigin(b.clone())],
|
||||
vec![],
|
||||
));
|
||||
let d_zero = Arc::new(Delta::new(0, vec![Payload::RouteOrigin(b.clone())], vec![]));
|
||||
|
||||
let mut deltas = VecDeque::new();
|
||||
deltas.push_back(d_max);
|
||||
@@ -637,7 +658,11 @@ fn get_deltas_since_supports_incremental_updates_across_serial_wraparound() {
|
||||
|
||||
let input = format!(
|
||||
"{}delta_window:\n{}",
|
||||
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), u32::MAX.wrapping_sub(1)),
|
||||
get_deltas_since_input_to_string(
|
||||
cache.session_id_for_version(1),
|
||||
cache.serial(),
|
||||
u32::MAX.wrapping_sub(1)
|
||||
),
|
||||
indent_block(&deltas_window_to_string(&deltas), 2),
|
||||
);
|
||||
let output = serial_result_detail_to_string(&result);
|
||||
@@ -678,12 +703,16 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old_across_
|
||||
));
|
||||
let d_zero = Arc::new(Delta::new(
|
||||
0,
|
||||
vec![Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497))],
|
||||
vec![Payload::RouteOrigin(v4_origin(
|
||||
198, 51, 100, 0, 24, 24, 64497,
|
||||
))],
|
||||
vec![],
|
||||
));
|
||||
let d_one = Arc::new(Delta::new(
|
||||
1,
|
||||
vec![Payload::RouteOrigin(v4_origin(203, 0, 113, 0, 24, 24, 64498))],
|
||||
vec![Payload::RouteOrigin(v4_origin(
|
||||
203, 0, 113, 0, 24, 24, 64498,
|
||||
))],
|
||||
vec![],
|
||||
));
|
||||
|
||||
@@ -709,7 +738,11 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_too_old_across_
|
||||
|
||||
let input = format!(
|
||||
"{}delta_window:\n{}",
|
||||
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), client_serial),
|
||||
get_deltas_since_input_to_string(
|
||||
cache.session_id_for_version(1),
|
||||
cache.serial(),
|
||||
client_serial
|
||||
),
|
||||
indent_block(&deltas_window_to_string(&deltas), 2),
|
||||
);
|
||||
let output = serial_result_detail_to_string(&result);
|
||||
@@ -737,7 +770,8 @@ fn get_deltas_since_returns_reset_required_when_client_serial_is_in_future_acros
|
||||
|
||||
let result = cache.get_deltas_since(0);
|
||||
|
||||
let input = get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 0);
|
||||
let input =
|
||||
get_deltas_since_input_to_string(cache.session_id_for_version(1), cache.serial(), 0);
|
||||
let output = serial_result_detail_to_string(&result);
|
||||
|
||||
test_report(
|
||||
@@ -776,10 +810,7 @@ async fn update_no_change_keeps_serial_and_produces_no_delta() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = RtrStore::open(dir.path()).unwrap();
|
||||
|
||||
let new_payloads = vec![
|
||||
Payload::RouteOrigin(old_b),
|
||||
Payload::RouteOrigin(old_a),
|
||||
];
|
||||
let new_payloads = vec![Payload::RouteOrigin(old_b), Payload::RouteOrigin(old_a)];
|
||||
|
||||
cache.update(new_payloads.clone(), &store).unwrap();
|
||||
|
||||
@@ -795,7 +826,10 @@ async fn update_no_change_keeps_serial_and_produces_no_delta() {
|
||||
let output = format!(
|
||||
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
|
||||
cache.serial(),
|
||||
indent_block(&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2),
|
||||
indent_block(
|
||||
&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot),
|
||||
2
|
||||
),
|
||||
indent_block(&serial_result_detail_to_string(&result), 2),
|
||||
);
|
||||
|
||||
@@ -854,7 +888,10 @@ async fn update_add_only_increments_serial_and_generates_announced_delta() {
|
||||
let output = format!(
|
||||
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
|
||||
cache.serial(),
|
||||
indent_block(&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2),
|
||||
indent_block(
|
||||
&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot),
|
||||
2
|
||||
),
|
||||
indent_block(&serial_result_detail_to_string(&result), 2),
|
||||
);
|
||||
|
||||
@@ -921,7 +958,10 @@ async fn update_remove_only_increments_serial_and_generates_withdrawn_delta() {
|
||||
let output = format!(
|
||||
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
|
||||
cache.serial(),
|
||||
indent_block(&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2),
|
||||
indent_block(
|
||||
&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot),
|
||||
2
|
||||
),
|
||||
indent_block(&serial_result_detail_to_string(&result), 2),
|
||||
);
|
||||
|
||||
@@ -997,7 +1037,10 @@ async fn update_add_and_remove_increments_serial_and_generates_both_sides() {
|
||||
let output = format!(
|
||||
"cache.serial_after_update: {}\ncurrent_snapshot:\n{}get_deltas_since(100):\n{}",
|
||||
cache.serial(),
|
||||
indent_block(&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot), 2),
|
||||
indent_block(
|
||||
&snapshot_hashes_and_sorted_view_to_string(¤t_snapshot),
|
||||
2
|
||||
),
|
||||
indent_block(&serial_result_detail_to_string(&result), 2),
|
||||
);
|
||||
|
||||
@@ -1282,10 +1325,7 @@ fn get_deltas_since_merges_multiple_deltas_to_final_minimal_view() {
|
||||
|
||||
#[test]
|
||||
fn snapshot_from_payloads_unions_aspas_by_customer() {
|
||||
let first = Payload::Aspa(Aspa::new(
|
||||
Asn::from(64496u32),
|
||||
vec![Asn::from(64497u32)],
|
||||
));
|
||||
let first = Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64497u32)]));
|
||||
let second = Payload::Aspa(Aspa::new(
|
||||
Asn::from(64496u32),
|
||||
vec![Asn::from(64498u32), Asn::from(64497u32)],
|
||||
@@ -1369,31 +1409,21 @@ fn validate_payloads_for_rtr_rejects_unsorted_snapshot_payloads() {
|
||||
let high = Payload::RouteOrigin(v4_origin(198, 51, 100, 0, 24, 24, 64497));
|
||||
|
||||
let err = validate_payloads_for_rtr(&[low, high], true).unwrap_err();
|
||||
assert!(err
|
||||
.to_string()
|
||||
.contains("RTR payload ordering violation"));
|
||||
assert!(err.to_string().contains("RTR payload ordering violation"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_payload_updates_for_rtr_rejects_unsorted_aspa_updates() {
|
||||
let withdraw = (
|
||||
false,
|
||||
Payload::Aspa(Aspa::new(
|
||||
Asn::from(64497u32),
|
||||
vec![Asn::from(64500u32)],
|
||||
)),
|
||||
Payload::Aspa(Aspa::new(Asn::from(64497u32), vec![Asn::from(64500u32)])),
|
||||
);
|
||||
let announce = (
|
||||
true,
|
||||
Payload::Aspa(Aspa::new(
|
||||
Asn::from(64496u32),
|
||||
vec![Asn::from(64499u32)],
|
||||
)),
|
||||
Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64499u32)])),
|
||||
);
|
||||
|
||||
let err = validate_payload_updates_for_rtr(&[withdraw, announce]).unwrap_err();
|
||||
assert!(err.to_string().contains("withdraw ASPA"));
|
||||
assert!(err.to_string().contains("announce ASPA"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rpki::rtr::ccr::{
|
||||
ParsedCcrSnapshot,
|
||||
find_latest_ccr_file,
|
||||
load_ccr_snapshot_from_file,
|
||||
snapshot_to_payloads_with_options,
|
||||
};
|
||||
use rpki::rtr::loader::{ParsedAspa, ParsedVrp};
|
||||
use tempfile::tempdir;
|
||||
use rpki::source::ccr::{find_latest_ccr_file, load_ccr_snapshot_from_file,
|
||||
snapshot_to_payloads_with_options, ParsedCcrSnapshot};
|
||||
|
||||
fn fixture_path(name: &str) -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("data").join(name)
|
||||
|
||||
311
tests/test_slurm.rs
Normal file
311
tests/test_slurm.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD_NO_PAD;
|
||||
|
||||
use rpki::data_model::resources::as_resources::Asn;
|
||||
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
|
||||
use rpki::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski};
|
||||
use rpki::slurm::file::{SlurmFile, SlurmVersion};
|
||||
|
||||
fn sample_spki() -> Vec<u8> {
|
||||
vec![
|
||||
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
|
||||
0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
|
||||
]
|
||||
}
|
||||
|
||||
fn sample_ski() -> [u8; 20] {
|
||||
[
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff, 0x10, 0x20, 0x30, 0x40,
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_rfc8416_v1_slurm() {
|
||||
let ski_hex = hex::encode(sample_ski());
|
||||
let router_public_key = STANDARD_NO_PAD.encode(sample_spki());
|
||||
let json = format!(
|
||||
r#"{{
|
||||
"slurmVersion": 1,
|
||||
"validationOutputFilters": {{
|
||||
"prefixFilters": [
|
||||
{{ "prefix": "192.0.2.0/24", "asn": 64496, "comment": "drop roa" }}
|
||||
],
|
||||
"bgpsecFilters": [
|
||||
{{ "asn": 64497, "SKI": "{ski_hex}" }}
|
||||
]
|
||||
}},
|
||||
"locallyAddedAssertions": {{
|
||||
"prefixAssertions": [
|
||||
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
||||
],
|
||||
"bgpsecAssertions": [
|
||||
{{ "asn": 64501, "SKI": "{ski_hex}", "routerPublicKey": "{router_public_key}" }}
|
||||
]
|
||||
}}
|
||||
}}"#
|
||||
);
|
||||
|
||||
let slurm = SlurmFile::from_slice(json.as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(slurm.version(), SlurmVersion::V1);
|
||||
assert_eq!(slurm.validation_output_filters().prefix_filters.len(), 1);
|
||||
assert_eq!(slurm.validation_output_filters().bgpsec_filters.len(), 1);
|
||||
assert!(slurm.validation_output_filters().aspa_filters.is_empty());
|
||||
assert_eq!(slurm.locally_added_assertions().prefix_assertions.len(), 1);
|
||||
assert_eq!(slurm.locally_added_assertions().bgpsec_assertions.len(), 1);
|
||||
assert!(slurm.locally_added_assertions().aspa_assertions.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_v2_slurm_with_aspa_extensions() {
|
||||
let json = r#"{
|
||||
"slurmVersion": 2,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [],
|
||||
"bgpsecFilters": [],
|
||||
"aspaFilters": [
|
||||
{ "customerAsn": 64496 }
|
||||
]
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [],
|
||||
"bgpsecAssertions": [],
|
||||
"aspaAssertions": [
|
||||
{ "customerAsn": 64510, "providerAsns": [64511, 64512] }
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
let slurm = SlurmFile::from_slice(json.as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(slurm.version(), SlurmVersion::V2);
|
||||
assert_eq!(slurm.validation_output_filters().aspa_filters.len(), 1);
|
||||
assert_eq!(slurm.locally_added_assertions().aspa_assertions.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_v1_file_with_aspa_members() {
|
||||
let json = r#"{
|
||||
"slurmVersion": 1,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [],
|
||||
"bgpsecFilters": [],
|
||||
"aspaFilters": []
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [],
|
||||
"bgpsecAssertions": []
|
||||
}
|
||||
}"#;
|
||||
|
||||
let err = SlurmFile::from_slice(json.as_bytes()).unwrap_err();
|
||||
assert!(err.to_string().contains("unknown field"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_non_canonical_prefixes_and_unsorted_aspa_providers() {
|
||||
let non_canonical = r#"{
|
||||
"slurmVersion": 1,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [
|
||||
{ "prefix": "192.0.2.1/24" }
|
||||
],
|
||||
"bgpsecFilters": []
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [],
|
||||
"bgpsecAssertions": []
|
||||
}
|
||||
}"#;
|
||||
let non_canonical_err = SlurmFile::from_slice(non_canonical.as_bytes()).unwrap_err();
|
||||
assert!(non_canonical_err.to_string().contains("not canonical"));
|
||||
|
||||
let unsorted_aspa = r#"{
|
||||
"slurmVersion": 2,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [],
|
||||
"bgpsecFilters": [],
|
||||
"aspaFilters": []
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [],
|
||||
"bgpsecAssertions": [],
|
||||
"aspaAssertions": [
|
||||
{ "customerAsn": 64500, "providerAsns": [64502, 64501] }
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
let aspa_err = SlurmFile::from_slice(unsorted_aspa.as_bytes()).unwrap_err();
|
||||
assert!(aspa_err.to_string().contains("strictly increasing"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_filters_before_assertions_and_excludes_duplicates() {
|
||||
let ski = Ski::from_bytes(sample_ski());
|
||||
let spki = sample_spki();
|
||||
let spki_b64 = STANDARD_NO_PAD.encode(&spki);
|
||||
let ski_hex = hex::encode(sample_ski());
|
||||
let json = format!(
|
||||
r#"{{
|
||||
"slurmVersion": 2,
|
||||
"validationOutputFilters": {{
|
||||
"prefixFilters": [
|
||||
{{ "prefix": "192.0.2.0/24", "asn": 64496 }}
|
||||
],
|
||||
"bgpsecFilters": [
|
||||
{{ "SKI": "{ski_hex}" }}
|
||||
],
|
||||
"aspaFilters": [
|
||||
{{ "customerAsn": 64496 }}
|
||||
]
|
||||
}},
|
||||
"locallyAddedAssertions": {{
|
||||
"prefixAssertions": [
|
||||
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }},
|
||||
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
||||
],
|
||||
"bgpsecAssertions": [
|
||||
{{ "asn": 64501, "SKI": "{ski_hex}", "routerPublicKey": "{spki_b64}" }}
|
||||
],
|
||||
"aspaAssertions": [
|
||||
{{ "customerAsn": 64510, "providerAsns": [64511, 64512] }}
|
||||
]
|
||||
}}
|
||||
}}"#
|
||||
);
|
||||
let slurm = SlurmFile::from_slice(json.as_bytes()).unwrap();
|
||||
|
||||
let input = vec![
|
||||
Payload::RouteOrigin(RouteOrigin::new(
|
||||
IPAddressPrefix::new(IPAddress::from_ipv4("192.0.2.0".parse().unwrap()), 24),
|
||||
24,
|
||||
Asn::from(64496u32),
|
||||
)),
|
||||
Payload::RouteOrigin(RouteOrigin::new(
|
||||
IPAddressPrefix::new(IPAddress::from_ipv4("203.0.113.0".parse().unwrap()), 24),
|
||||
24,
|
||||
Asn::from(64497u32),
|
||||
)),
|
||||
Payload::RouterKey(RouterKey::new(ski, Asn::from(64497u32), spki.clone())),
|
||||
Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64498u32)])),
|
||||
];
|
||||
|
||||
let output = slurm.apply(&input);
|
||||
|
||||
assert_eq!(output.len(), 4);
|
||||
assert!(output.iter().any(|payload| matches!(
|
||||
payload,
|
||||
Payload::RouteOrigin(route_origin)
|
||||
if route_origin.prefix().address() == IPAddress::from_ipv4("203.0.113.0".parse().unwrap())
|
||||
)));
|
||||
assert!(output.iter().any(|payload| matches!(
|
||||
payload,
|
||||
Payload::RouteOrigin(route_origin)
|
||||
if route_origin.prefix().address() == IPAddress::from_ipv4("198.51.100.0".parse().unwrap())
|
||||
)));
|
||||
assert!(output.iter().any(|payload| matches!(
|
||||
payload,
|
||||
Payload::RouterKey(router_key)
|
||||
if router_key.asn() == Asn::from(64501u32)
|
||||
)));
|
||||
assert!(output.iter().any(|payload| matches!(
|
||||
payload,
|
||||
Payload::Aspa(aspa)
|
||||
if aspa.customer_asn() == Asn::from(64510u32)
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merges_multiple_slurm_files_without_conflict() {
|
||||
let a = r#"{
|
||||
"slurmVersion": 1,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [],
|
||||
"bgpsecFilters": []
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [
|
||||
{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }
|
||||
],
|
||||
"bgpsecAssertions": []
|
||||
}
|
||||
}"#;
|
||||
|
||||
let b = r#"{
|
||||
"slurmVersion": 2,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [],
|
||||
"bgpsecFilters": [],
|
||||
"aspaFilters": [
|
||||
{ "customerAsn": 64510 }
|
||||
]
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [],
|
||||
"bgpsecAssertions": [],
|
||||
"aspaAssertions": []
|
||||
}
|
||||
}"#;
|
||||
|
||||
let merged = SlurmFile::merge_named(vec![
|
||||
(
|
||||
"a.slurm".to_string(),
|
||||
SlurmFile::from_slice(a.as_bytes()).unwrap(),
|
||||
),
|
||||
(
|
||||
"b.slurm".to_string(),
|
||||
SlurmFile::from_slice(b.as_bytes()).unwrap(),
|
||||
),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(merged.version(), SlurmVersion::V2);
|
||||
assert_eq!(merged.locally_added_assertions().prefix_assertions.len(), 1);
|
||||
assert_eq!(merged.validation_output_filters().aspa_filters.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_conflicting_multiple_slurm_files() {
|
||||
let a = r#"{
|
||||
"slurmVersion": 1,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [],
|
||||
"bgpsecFilters": []
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [
|
||||
{ "prefix": "10.0.0.0/8", "asn": 64500, "maxPrefixLength": 24 }
|
||||
],
|
||||
"bgpsecAssertions": []
|
||||
}
|
||||
}"#;
|
||||
|
||||
let b = r#"{
|
||||
"slurmVersion": 1,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [
|
||||
{ "prefix": "10.0.0.0/16" }
|
||||
],
|
||||
"bgpsecFilters": []
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [],
|
||||
"bgpsecAssertions": []
|
||||
}
|
||||
}"#;
|
||||
|
||||
let err = SlurmFile::merge_named(vec![
|
||||
(
|
||||
"a.slurm".to_string(),
|
||||
SlurmFile::from_slice(a.as_bytes()).unwrap(),
|
||||
),
|
||||
(
|
||||
"b.slurm".to_string(),
|
||||
SlurmFile::from_slice(b.as_bytes()).unwrap(),
|
||||
),
|
||||
])
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("conflicting SLURM files"));
|
||||
}
|
||||
Reference in New Issue
Block a user