332 lines
9.2 KiB
Rust
332 lines
9.2 KiB
Rust
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
use std::fmt::Write;
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
|
|
use rpki::rtr::payload::{Payload, RouteOrigin};
|
|
use rpki::rtr::pdu::{CacheResponse, EndOfDataV1, IPv4Prefix, IPv6Prefix};
|
|
use rpki::rtr::cache::SerialResult;
|
|
|
|
pub struct RtrDebugDumper {
|
|
entries: Vec<Value>,
|
|
}
|
|
|
|
impl RtrDebugDumper {
|
|
pub fn new() -> Self {
|
|
Self { entries: Vec::new() }
|
|
}
|
|
|
|
pub fn push<T: serde::Serialize>(&mut self, pdu: u8, body: &T) {
|
|
self.entries.push(json!({
|
|
"pdu": pdu,
|
|
"pdu_name": pdu_type_name(pdu),
|
|
"body": body
|
|
}));
|
|
}
|
|
|
|
pub fn push_value(&mut self, pdu: u8, body: Value) {
|
|
self.entries.push(json!({
|
|
"pdu": pdu,
|
|
"pdu_name": pdu_type_name(pdu),
|
|
"body": body
|
|
}));
|
|
}
|
|
|
|
pub fn print_pretty(&self, test_name: &str) {
|
|
println!(
|
|
"\n===== RTR Debug Dump: {} =====\n{}\n",
|
|
test_name,
|
|
serde_json::to_string_pretty(&self.entries).unwrap()
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn pdu_type_name(pdu: u8) -> &'static str {
|
|
match pdu {
|
|
0 => "Serial Notify",
|
|
1 => "Serial Query",
|
|
2 => "Reset Query",
|
|
3 => "Cache Response",
|
|
4 => "IPv4 Prefix",
|
|
6 => "IPv6 Prefix",
|
|
7 => "End of Data",
|
|
8 => "Cache Reset",
|
|
9 => "Router Key",
|
|
10 => "Error Report",
|
|
11 => "ASPA",
|
|
255 => "Reserved",
|
|
_ => "Unknown",
|
|
}
|
|
}
|
|
|
|
pub fn dump_cache_response(resp: &CacheResponse) -> Value {
|
|
json!({
|
|
"header": {
|
|
"version": resp.version(),
|
|
"pdu": resp.pdu(),
|
|
"session_id": resp.session_id(),
|
|
"length": 8
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn dump_ipv4_prefix(p: &IPv4Prefix) -> Value {
|
|
json!({
|
|
"header": {
|
|
"version": p.version(),
|
|
"pdu": p.pdu(),
|
|
"session_id": 0,
|
|
"length": 20
|
|
},
|
|
"flags": {
|
|
"raw": if p.flag().is_announce() { 1 } else { 0 },
|
|
"announce": p.flag().is_announce()
|
|
},
|
|
"prefix": p.prefix().to_string(),
|
|
"prefix_len": p.prefix_len(),
|
|
"max_len": p.max_len(),
|
|
"asn": p.asn().into_u32()
|
|
})
|
|
}
|
|
|
|
pub fn dump_ipv6_prefix(p: &IPv6Prefix) -> Value {
|
|
json!({
|
|
"header": {
|
|
"version": p.version(),
|
|
"pdu": p.pdu(),
|
|
"session_id": 0,
|
|
"length": 32
|
|
},
|
|
"flags": {
|
|
"raw": if p.flag().is_announce() { 1 } else { 0 },
|
|
"announce": p.flag().is_announce()
|
|
},
|
|
"prefix": p.prefix().to_string(),
|
|
"prefix_len": p.prefix_len(),
|
|
"max_len": p.max_len(),
|
|
"asn": p.asn().into_u32()
|
|
})
|
|
}
|
|
|
|
pub fn dump_eod_v1(eod: &EndOfDataV1) -> Value {
|
|
let timing = eod.timing();
|
|
json!({
|
|
"header": {
|
|
"version": eod.version(),
|
|
"pdu": eod.pdu(),
|
|
"session_id": eod.session_id(),
|
|
"length": 24
|
|
},
|
|
"serial_number": eod.serial_number(),
|
|
"refresh_interval": timing.refresh,
|
|
"retry_interval": timing.retry,
|
|
"expire_interval": timing.expire
|
|
})
|
|
}
|
|
|
|
pub fn dump_cache_reset(version: u8, pdu: u8) -> Value {
|
|
json!({
|
|
"header": {
|
|
"version": version,
|
|
"pdu": pdu,
|
|
"session_id": 0,
|
|
"length": 8
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn v4_prefix(a: u8, b: u8, c: u8, d: u8, prefix_len: u8) -> IPAddressPrefix {
|
|
IPAddressPrefix {
|
|
address: IPAddress::from_ipv4(Ipv4Addr::new(a, b, c, d)),
|
|
prefix_length: prefix_len,
|
|
}
|
|
}
|
|
|
|
pub fn v6_prefix(addr: Ipv6Addr, prefix_len: u8) -> IPAddressPrefix {
|
|
IPAddressPrefix {
|
|
address: IPAddress::from_ipv6(addr),
|
|
prefix_length: prefix_len,
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
pub fn v6_origin(
|
|
addr: Ipv6Addr,
|
|
prefix_len: u8,
|
|
max_len: u8,
|
|
asn: u32,
|
|
) -> RouteOrigin {
|
|
let prefix = v6_prefix(addr, prefix_len);
|
|
RouteOrigin::new(prefix, max_len, asn.into())
|
|
}
|
|
|
|
pub fn as_route_origin(payload: &Payload) -> &RouteOrigin {
|
|
match payload {
|
|
Payload::RouteOrigin(ro) => ro,
|
|
_ => panic!("expected RouteOrigin payload"),
|
|
}
|
|
}
|
|
|
|
pub fn as_v4_route_origin(payload: &Payload) -> &RouteOrigin {
|
|
let ro = as_route_origin(payload);
|
|
assert!(ro.prefix().address.is_ipv4(), "expected IPv4 RouteOrigin");
|
|
ro
|
|
}
|
|
|
|
pub fn as_v6_route_origin(payload: &Payload) -> &RouteOrigin {
|
|
let ro = as_route_origin(payload);
|
|
assert!(ro.prefix().address.is_ipv6(), "expected IPv6 RouteOrigin");
|
|
ro
|
|
}
|
|
|
|
pub fn route_origin_to_string(ro: &RouteOrigin) -> String {
|
|
let prefix = ro.prefix();
|
|
let addr = match prefix.address {
|
|
IPAddress::V4(v4) => v4.to_string(),
|
|
IPAddress::V6(v6) => v6.to_string(),
|
|
};
|
|
|
|
format!(
|
|
"{}/{}-{} AS{}",
|
|
addr,
|
|
prefix.prefix_length,
|
|
ro.max_length(),
|
|
ro.asn().into_u32()
|
|
)
|
|
}
|
|
|
|
pub fn payload_to_string(payload: &Payload) -> String {
|
|
match payload {
|
|
Payload::RouteOrigin(ro) => format!("RouteOrigin({})", route_origin_to_string(ro)),
|
|
Payload::RouterKey(_) => "RouterKey(...)".to_string(),
|
|
Payload::Aspa(_) => "Aspa(...)".to_string(),
|
|
}
|
|
}
|
|
|
|
pub fn payloads_to_pretty_lines(payloads: &[Payload]) -> String {
|
|
let mut out = String::new();
|
|
for (idx, payload) in payloads.iter().enumerate() {
|
|
let _ = writeln!(&mut out, " [{}] {}", idx, payload_to_string(payload));
|
|
}
|
|
out
|
|
}
|
|
|
|
pub fn print_payloads(label: &str, payloads: &[Payload]) {
|
|
println!(
|
|
"\n===== {} =====\n{}",
|
|
label,
|
|
payloads_to_pretty_lines(payloads)
|
|
);
|
|
}
|
|
|
|
pub fn serial_result_to_string(result: &SerialResult) -> String {
|
|
match result {
|
|
SerialResult::UpToDate => "UpToDate".to_string(),
|
|
SerialResult::ResetRequired => "ResetRequired".to_string(),
|
|
SerialResult::Deltas(deltas) => {
|
|
let serials: Vec<u32> = deltas.iter().map(|d| d.serial()).collect();
|
|
format!("Deltas {:?}", serials)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn print_serial_result(label: &str, result: &SerialResult) {
|
|
println!("\n===== {} =====\n{}\n", label, serial_result_to_string(result));
|
|
}
|
|
|
|
pub fn bytes_to_hex(bytes: &[u8]) -> String {
|
|
let mut out = String::with_capacity(bytes.len() * 2);
|
|
for b in bytes {
|
|
let _ = write!(&mut out, "{:02x}", b);
|
|
}
|
|
out
|
|
}
|
|
|
|
pub fn print_snapshot_hashes(label: &str, snapshot: &rpki::rtr::cache::Snapshot) {
|
|
println!(
|
|
"\n===== {} =====\norigins_hash={}\nrouter_keys_hash={}\naspas_hash={}\nsnapshot_hash={}\n",
|
|
label,
|
|
bytes_to_hex(&snapshot.origins_hash()),
|
|
bytes_to_hex(&snapshot.router_keys_hash()),
|
|
bytes_to_hex(&snapshot.aspas_hash()),
|
|
bytes_to_hex(&snapshot.snapshot_hash()),
|
|
);
|
|
}
|
|
|
|
pub fn test_report(
|
|
name: &str,
|
|
purpose: &str,
|
|
input: &str,
|
|
output: &str,
|
|
) {
|
|
println!(
|
|
"\n==================== TEST REPORT ====================\n测试名称: {}\n测试目的: {}\n\n【输入】\n{}\n【输出】\n{}\n====================================================\n",
|
|
name, purpose, input, output
|
|
);
|
|
}
|
|
|
|
pub fn payloads_to_string(payloads: &[Payload]) -> String {
|
|
let mut out = String::new();
|
|
for (idx, payload) in payloads.iter().enumerate() {
|
|
let _ = writeln!(&mut out, " [{}] {}", idx, payload_to_string(payload));
|
|
}
|
|
if out.is_empty() {
|
|
out.push_str(" <empty>\n");
|
|
}
|
|
out
|
|
}
|
|
|
|
pub fn snapshot_hashes_to_string(snapshot: &rpki::rtr::cache::Snapshot) -> String {
|
|
format!(
|
|
" origins_hash: {}\n router_keys_hash: {}\n aspas_hash: {}\n snapshot_hash: {}\n",
|
|
bytes_to_hex(&snapshot.origins_hash()),
|
|
bytes_to_hex(&snapshot.router_keys_hash()),
|
|
bytes_to_hex(&snapshot.aspas_hash()),
|
|
bytes_to_hex(&snapshot.snapshot_hash()),
|
|
)
|
|
}
|
|
|
|
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::Deltas(deltas) => {
|
|
let mut out = String::new();
|
|
let _ = writeln!(&mut out, " result: Deltas");
|
|
for (idx, delta) in deltas.iter().enumerate() {
|
|
let _ = writeln!(&mut out, " delta[{}].serial: {}", idx, delta.serial());
|
|
let _ = writeln!(&mut out, " delta[{}].announced:", idx);
|
|
out.push_str(&indent_block(&payloads_to_string(delta.announced()), 4));
|
|
let _ = writeln!(&mut out, " delta[{}].withdrawn:", idx);
|
|
out.push_str(&indent_block(&payloads_to_string(delta.withdrawn()), 4));
|
|
}
|
|
out
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn indent_block(text: &str, spaces: usize) -> String {
|
|
let pad = " ".repeat(spaces);
|
|
let mut out = String::new();
|
|
for line in text.lines() {
|
|
let _ = writeln!(&mut out, "{}{}", pad, line);
|
|
}
|
|
out
|
|
} |