Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

All notable changes to this project will be documented in this file.

## Unreleased

### Breaking changes

* change `path_id` from `u32` to `Option<u32>` for proper null handling
* `NetworkPrefix::path_id` field now properly represents absence with `None` instead of using `0`
* `NetworkPrefix::new()` and `NetworkPrefix::encode()` signatures updated accordingly
* `RibEntry` now stores `path_id` for TABLE_DUMP_V2 messages with AddPath support
* fixes issue [#217](https://github.com/bgpkit/bgpkit-parser/issues/217)

### Bug fixes

* fixed TABLE_DUMP_V2 parsing to properly store path_id when AddPath is enabled
* previously path_id was read but discarded, now properly stored in `RibEntry`

## v0.11.1 - 2025-06-06

### Bug fixes
Expand Down
10 changes: 6 additions & 4 deletions src/encoder/rib_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ impl MrtRibEncoder {
IpAddr::V6(_ip) => Ipv4Addr::from(0),
};
let peer = Peer::new(bgp_identifier, elem.peer_ip, elem.peer_asn);
let peer_id = self.index_table.add_peer(peer);
let peer_index = self.index_table.add_peer(peer);
let path_id = elem.prefix.path_id;
let prefix = elem.prefix.prefix;

let entries_map = self.per_prefix_entries_map.entry(prefix).or_default();
let entry = RibEntry {
peer_index: peer_id,
peer_index,
path_id,
originated_time: elem.timestamp as u32,
attributes: Attributes::from(elem),
};
entries_map.insert(peer_id, entry);
entries_map.insert(peer_index, entry);
}

/// Export the data stored in the struct to a byte array.
Expand Down Expand Up @@ -99,7 +101,7 @@ impl MrtRibEncoder {
let mut prefix_rib_entry = RibAfiEntries {
rib_type,
sequence_number: entry_count as u32,
prefix: NetworkPrefix::new(*prefix, 0),
prefix: NetworkPrefix::new(*prefix, None),
rib_entries: vec![],
};
for entry in entries_map.values() {
Expand Down
4 changes: 4 additions & 0 deletions src/models/mrt/table_dump_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ pub struct RibGenericEntries {
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Originated Time |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Optional Path ID |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Attribute Length |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | BGP Attributes... (variable)
Expand All @@ -141,6 +143,7 @@ pub struct RibGenericEntries {
pub struct RibEntry {
pub peer_index: u16,
pub originated_time: u32,
pub path_id: Option<u32>,
pub attributes: Attributes,
}

Expand Down Expand Up @@ -310,6 +313,7 @@ mod tests {
let rib_entry = RibEntry {
peer_index: 1,
originated_time: 1,
path_id: None,
attributes: Attributes::default(),
};
let rib_afi = TableDumpV2Message::RibAfi(RibAfiEntries {
Expand Down
78 changes: 48 additions & 30 deletions src/models/network/prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ use std::str::FromStr;
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
pub struct NetworkPrefix {
pub prefix: IpNet,
pub path_id: u32,
pub path_id: Option<u32>,
}

// Attempt to reduce the size of the debug output
impl Debug for NetworkPrefix {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.path_id == 0 {
write!(f, "{}", self.prefix)
} else {
write!(f, "{}#{}", self.prefix, self.path_id)
match self.path_id {
Some(path_id) => write!(f, "{}#{}", self.prefix, path_id),
None => write!(f, "{}", self.prefix),
}
}
}
Expand All @@ -28,12 +27,15 @@ impl FromStr for NetworkPrefix {

fn from_str(s: &str) -> Result<Self, Self::Err> {
let prefix = IpNet::from_str(s)?;
Ok(NetworkPrefix { prefix, path_id: 0 })
Ok(NetworkPrefix {
prefix,
path_id: None,
})
}
}

impl NetworkPrefix {
pub fn new(prefix: IpNet, path_id: u32) -> NetworkPrefix {
pub fn new(prefix: IpNet, path_id: Option<u32>) -> NetworkPrefix {
NetworkPrefix { prefix, path_id }
}

Expand All @@ -57,16 +59,19 @@ impl NetworkPrefix {
/// use bgpkit_parser::models::NetworkPrefix;
///
/// let prefix = NetworkPrefix::from_str("192.168.0.0/24").unwrap();
/// let encoded_bytes = prefix.encode(false);
/// let encoded_bytes = prefix.encode();
///
/// assert_eq!(encoded_bytes.iter().as_slice(), &[24, 192, 168, 0]);
/// ```
pub fn encode(&self, add_path: bool) -> Bytes {
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::new();
if add_path {

// encode path identifier if it exists
if let Some(path_id) = self.path_id {
// encode path identifier
bytes.put_u32(self.path_id);
bytes.put_u32(path_id);
}

// encode prefix

let bit_len = self.prefix.prefix_len();
Expand Down Expand Up @@ -108,14 +113,13 @@ mod serde_impl {
where
S: Serializer,
{
if serializer.is_human_readable() && self.path_id == 0 {
self.prefix.serialize(serializer)
} else {
SerdeNetworkPrefixRepr::WithPathId {
match self.path_id {
Some(path_id) => SerdeNetworkPrefixRepr::WithPathId {
prefix: self.prefix,
path_id: self.path_id,
path_id,
}
.serialize(serializer)
.serialize(serializer),
None => self.prefix.serialize(serializer),
}
}
}
Expand All @@ -126,12 +130,14 @@ mod serde_impl {
D: Deserializer<'de>,
{
match SerdeNetworkPrefixRepr::deserialize(deserializer)? {
SerdeNetworkPrefixRepr::PlainPrefix(prefix) => {
Ok(NetworkPrefix { prefix, path_id: 0 })
}
SerdeNetworkPrefixRepr::WithPathId { prefix, path_id } => {
Ok(NetworkPrefix { prefix, path_id })
}
SerdeNetworkPrefixRepr::PlainPrefix(prefix) => Ok(NetworkPrefix {
prefix,
path_id: None,
}),
SerdeNetworkPrefixRepr::WithPathId { prefix, path_id } => Ok(NetworkPrefix {
prefix,
path_id: Some(path_id),
}),
}
}
}
Expand All @@ -150,28 +156,28 @@ mod tests {
network_prefix.prefix,
IpNet::from_str("192.168.0.0/24").unwrap()
);
assert_eq!(network_prefix.path_id, 0);
assert_eq!(network_prefix.path_id, None);
}

#[test]
fn test_encode() {
let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
let network_prefix = NetworkPrefix::new(prefix, 1);
let _encoded = network_prefix.encode(true);
let network_prefix = NetworkPrefix::new(prefix, Some(1));
let _encoded = network_prefix.encode();
}

#[test]
fn test_display() {
let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
let network_prefix = NetworkPrefix::new(prefix, 1);
let network_prefix = NetworkPrefix::new(prefix, Some(1));
assert_eq!(network_prefix.to_string(), "192.168.0.0/24");
}

#[test]
#[cfg(feature = "serde")]
fn test_serialization() {
let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
let network_prefix = NetworkPrefix::new(prefix, 1);
let network_prefix = NetworkPrefix::new(prefix, Some(1));
let serialized = serde_json::to_string(&network_prefix).unwrap();
assert_eq!(serialized, "{\"prefix\":\"192.168.0.0/24\",\"path_id\":1}");
}
Expand All @@ -185,13 +191,25 @@ mod tests {
deserialized.prefix,
IpNet::from_str("192.168.0.0/24").unwrap()
);
assert_eq!(deserialized.path_id, 1);
assert_eq!(deserialized.path_id, Some(1));
}

#[test]
#[cfg(feature = "serde")]
fn test_binary_serialization_with_path_id() {
let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
let network_prefix = NetworkPrefix::new(prefix, Some(42));
// Test non-human readable serialization (binary-like)
let serialized = serde_json::to_vec(&network_prefix).unwrap();
let deserialized: NetworkPrefix = serde_json::from_slice(&serialized).unwrap();
assert_eq!(deserialized.prefix, prefix);
assert_eq!(deserialized.path_id, Some(42));
}

#[test]
fn test_debug() {
let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
let network_prefix = NetworkPrefix::new(prefix, 1);
let network_prefix = NetworkPrefix::new(prefix, Some(1));
assert_eq!(format!("{network_prefix:?}"), "192.168.0.0/24#1");
}
}
22 changes: 11 additions & 11 deletions src/parser/bgp/attributes/attr_14_15_nlri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn parse_nlri(
}

/// Encode a NLRI attribute.
pub fn encode_nlri(nlri: &Nlri, reachable: bool, add_path: bool) -> Bytes {
pub fn encode_nlri(nlri: &Nlri, reachable: bool) -> Bytes {
let mut bytes = BytesMut::new();

// encode address family
Expand Down Expand Up @@ -138,7 +138,7 @@ pub fn encode_nlri(nlri: &Nlri, reachable: bool, add_path: bool) -> Bytes {

// NLRI
for prefix in &nlri.prefixes {
bytes.extend(prefix.encode(add_path));
bytes.extend(prefix.encode());
}

bytes.freeze()
Expand Down Expand Up @@ -266,7 +266,7 @@ mod tests {
Ipv4Addr::from_str("192.0.2.1").unwrap()
))
);
let prefix = NetworkPrefix::new(IpNet::from_str("192.0.2.0/24").unwrap(), 123);
let prefix = NetworkPrefix::new(IpNet::from_str("192.0.2.0/24").unwrap(), Some(123));
assert_eq!(nlri.prefixes[0], prefix);
assert_eq!(nlri.prefixes[0].path_id, prefix.path_id);
} else {
Expand All @@ -284,10 +284,10 @@ mod tests {
)),
prefixes: vec![NetworkPrefix {
prefix: IpNet::from_str("192.0.1.0/24").unwrap(),
path_id: 0,
path_id: None,
}],
};
let bytes = encode_nlri(&nlri, true, false);
let bytes = encode_nlri(&nlri, true);
assert_eq!(
bytes,
Bytes::from(vec![
Expand All @@ -312,10 +312,10 @@ mod tests {
)),
prefixes: vec![NetworkPrefix {
prefix: IpNet::from_str("192.0.1.0/24").unwrap(),
path_id: 123,
path_id: Some(123),
}],
};
let bytes = encode_nlri(&nlri, true, true);
let bytes = encode_nlri(&nlri, true);
assert_eq!(
bytes,
Bytes::from(vec![
Expand Down Expand Up @@ -368,10 +368,10 @@ mod tests {
next_hop: None,
prefixes: vec![NetworkPrefix {
prefix: IpNet::from_str("192.0.1.0/24").unwrap(),
path_id: 0,
path_id: None,
}],
};
let bytes = encode_nlri(&nlri, false, false);
let bytes = encode_nlri(&nlri, false);
assert_eq!(
bytes,
Bytes::from(vec![
Expand All @@ -398,10 +398,10 @@ mod tests {
)),
prefixes: vec![NetworkPrefix {
prefix: IpNet::from_str("192.0.1.0/24").unwrap(),
path_id: 0,
path_id: None,
}],
};
let bytes = encode_nlri(&nlri_with_next_hop, false, false);
let bytes = encode_nlri(&nlri_with_next_hop, false);
// The encoded bytes should include the next_hop
assert_eq!(
bytes,
Expand Down
10 changes: 5 additions & 5 deletions src/parser/bgp/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ pub fn parse_attributes(
}

impl Attribute {
pub fn encode(&self, add_path: bool, asn_len: AsnLength) -> Bytes {
pub fn encode(&self, asn_len: AsnLength) -> Bytes {
let mut bytes = BytesMut::new();

let flag = self.flag.bits();
Expand Down Expand Up @@ -247,8 +247,8 @@ impl Attribute {
}
AttributeValue::OriginatorId(v) => encode_originator_id(&IpAddr::from(*v)),
AttributeValue::Clusters(v) => encode_clusters(v),
AttributeValue::MpReachNlri(v) => encode_nlri(v, true, add_path),
AttributeValue::MpUnreachNlri(v) => encode_nlri(v, false, add_path),
AttributeValue::MpReachNlri(v) => encode_nlri(v, true),
AttributeValue::MpUnreachNlri(v) => encode_nlri(v, false),
AttributeValue::Development(v) => Bytes::from(v.to_owned()),
AttributeValue::Deprecated(v) => Bytes::from(v.bytes.to_owned()),
AttributeValue::Unknown(v) => Bytes::from(v.bytes.to_owned()),
Expand All @@ -268,10 +268,10 @@ impl Attribute {
}

impl Attributes {
pub fn encode(&self, add_path: bool, asn_len: AsnLength) -> Bytes {
pub fn encode(&self, asn_len: AsnLength) -> Bytes {
let mut bytes = BytesMut::new();
for attr in &self.inner {
bytes.extend(attr.encode(add_path, asn_len));
bytes.extend(attr.encode(asn_len));
}
bytes.freeze()
}
Expand Down
Loading