pub mod bgp4mp;
pub mod table_dump;
pub mod table_dump_v2;
pub use bgp4mp::*;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::fmt::{Display, Formatter};
pub use table_dump::*;
pub use table_dump_v2::*;
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MrtRecord {
pub common_header: CommonHeader,
pub message: MrtMessage,
}
#[derive(Debug, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CommonHeader {
pub timestamp: u32,
pub microsecond_timestamp: Option<u32>,
pub entry_type: EntryType,
pub entry_subtype: u16,
pub length: u32,
}
impl PartialEq for CommonHeader {
fn eq(&self, other: &Self) -> bool {
self.timestamp == other.timestamp
&& self.microsecond_timestamp == other.microsecond_timestamp
&& self.entry_type == other.entry_type
&& self.entry_subtype == other.entry_subtype
}
}
impl Display for CommonHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let ts = match self.microsecond_timestamp {
Some(us) => format!("{}.{:06}", self.timestamp, us),
None => self.timestamp.to_string(),
};
write!(
f,
"MRT|{}|{:?}|{}|{}",
ts, self.entry_type, self.entry_subtype, self.length
)
}
}
impl Display for MrtRecord {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let ts = match self.common_header.microsecond_timestamp {
Some(us) => format!("{}.{:06}", self.common_header.timestamp, us),
None => self.common_header.timestamp.to_string(),
};
write!(
f,
"MRT|{}|{:?}|{}|{}",
ts, self.common_header.entry_type, self.common_header.entry_subtype, self.message
)
}
}
impl Display for MrtMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
MrtMessage::TableDumpMessage(msg) => {
write!(f, "TABLE_DUMP|{}|{}", msg.prefix, msg.peer_ip)
}
MrtMessage::TableDumpV2Message(msg) => match msg {
TableDumpV2Message::PeerIndexTable(pit) => {
write!(f, "PEER_INDEX_TABLE|{}", pit.id_peer_map.len())
}
TableDumpV2Message::RibAfi(rib) => {
write!(
f,
"RIB|{:?}|{}|{} entries",
rib.rib_type,
rib.prefix,
rib.rib_entries.len()
)
}
TableDumpV2Message::RibGeneric(rib) => {
write!(
f,
"RIB_GENERIC|AFI {:?}|SAFI {:?}|{} entries",
rib.afi,
rib.safi,
rib.rib_entries.len()
)
}
TableDumpV2Message::GeoPeerTable(gpt) => {
write!(f, "GEO_PEER_TABLE|{} peers", gpt.geo_peers.len())
}
},
MrtMessage::Bgp4Mp(bgp4mp) => match bgp4mp {
Bgp4MpEnum::StateChange(sc) => {
write!(
f,
"STATE_CHANGE|{}|{}|{:?}->{:?}",
sc.peer_ip, sc.peer_asn, sc.old_state, sc.new_state
)
}
Bgp4MpEnum::Message(msg) => {
let msg_type = match &msg.bgp_message {
crate::models::BgpMessage::Open(_) => "OPEN",
crate::models::BgpMessage::Update(_) => "UPDATE",
crate::models::BgpMessage::Notification(_) => "NOTIFICATION",
crate::models::BgpMessage::KeepAlive => "KEEPALIVE",
};
write!(f, "BGP4MP|{}|{}|{}", msg.peer_ip, msg.peer_asn, msg_type)
}
},
}
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MrtMessage {
TableDumpMessage(TableDumpMessage),
TableDumpV2Message(TableDumpV2Message),
Bgp4Mp(Bgp4MpEnum),
}
#[derive(Debug, TryFromPrimitive, IntoPrimitive, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(non_camel_case_types)]
#[repr(u16)]
pub enum EntryType {
NULL = 0,
START = 1,
DIE = 2,
I_AM_DEAD = 3,
PEER_DOWN = 4,
BGP = 5,
RIP = 6,
IDRP = 7,
RIPNG = 8,
BGP4PLUS = 9,
BGP4PLUS_01 = 10,
OSPFv2 = 11,
TABLE_DUMP = 12,
TABLE_DUMP_V2 = 13,
BGP4MP = 16,
BGP4MP_ET = 17,
ISIS = 32,
ISIS_ET = 33,
OSPFv3 = 48,
OSPFv3_ET = 49,
}
#[cfg(test)]
mod tests {
#[test]
fn test_mrt_record_display() {
use super::*;
use crate::models::Asn;
use std::net::IpAddr;
use std::str::FromStr;
let mrt_record = MrtRecord {
common_header: CommonHeader {
timestamp: 1609459200,
microsecond_timestamp: None,
entry_type: EntryType::BGP4MP,
entry_subtype: 0,
length: 0,
},
message: MrtMessage::Bgp4Mp(Bgp4MpEnum::StateChange(Bgp4MpStateChange {
msg_type: Bgp4MpType::StateChange,
peer_asn: Asn::new_32bit(65000),
local_asn: Asn::new_32bit(65001),
interface_index: 1,
peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
local_addr: IpAddr::from_str("10.0.0.2").unwrap(),
old_state: BgpState::Idle,
new_state: BgpState::Connect,
})),
};
let display = format!("{}", mrt_record);
assert!(display.contains("1609459200"));
assert!(display.contains("BGP4MP"));
assert!(display.contains("STATE_CHANGE"));
assert!(display.contains("10.0.0.1"));
assert!(display.contains("65000"));
}
#[test]
fn test_common_header_display() {
use super::*;
let header = CommonHeader {
timestamp: 1609459200,
microsecond_timestamp: Some(500000),
entry_type: EntryType::BGP4MP_ET,
entry_subtype: 4,
length: 128,
};
let display = format!("{}", header);
assert!(display.contains("1609459200.500000"));
assert!(display.contains("BGP4MP_ET"));
assert!(display.contains("128"));
}
#[test]
#[cfg(feature = "serde")]
fn test_entry_type_serialize_and_deserialize() {
use super::*;
let types = vec![
EntryType::NULL,
EntryType::START,
EntryType::DIE,
EntryType::I_AM_DEAD,
EntryType::PEER_DOWN,
EntryType::BGP,
EntryType::RIP,
EntryType::IDRP,
EntryType::RIPNG,
EntryType::BGP4PLUS,
EntryType::BGP4PLUS_01,
EntryType::OSPFv2,
EntryType::TABLE_DUMP,
EntryType::TABLE_DUMP_V2,
EntryType::BGP4MP,
EntryType::BGP4MP_ET,
EntryType::ISIS,
EntryType::ISIS_ET,
EntryType::OSPFv3,
EntryType::OSPFv3_ET,
];
for entry_type in types {
let serialized = serde_json::to_string(&entry_type).unwrap();
let deserialized: EntryType = serde_json::from_str(&serialized).unwrap();
assert_eq!(entry_type, deserialized);
}
}
#[test]
#[cfg(feature = "serde")]
fn test_serialization() {
use super::*;
use serde_json;
use std::net::IpAddr;
use std::str::FromStr;
let mrt_record = MrtRecord {
common_header: CommonHeader {
timestamp: 0,
microsecond_timestamp: None,
entry_type: EntryType::BGP4MP,
entry_subtype: 0,
length: 0,
},
message: MrtMessage::Bgp4Mp(Bgp4MpEnum::StateChange(Bgp4MpStateChange {
msg_type: Bgp4MpType::StateChange,
peer_asn: crate::models::Asn::new_32bit(0),
local_asn: crate::models::Asn::new_32bit(0),
interface_index: 1,
peer_ip: IpAddr::from_str("10.0.0.0").unwrap(),
local_addr: IpAddr::from_str("10.0.0.0").unwrap(),
old_state: BgpState::Idle,
new_state: BgpState::Connect,
})),
};
let serialized = serde_json::to_string(&mrt_record).unwrap();
let deserialized: MrtRecord = serde_json::from_str(&serialized).unwrap();
assert_eq!(mrt_record, deserialized);
}
}