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
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ Always run clippy with `--all-features`:
cargo clippy --all-features
```

The pre-push hook uses the stricter form below; run it before pushing if you want to match the exact CI check:
```
cargo clippy --all-targets --all-features -- -D warnings
```

### Formatting
`cargo fmt` does not accept `--all-features` (formatting is feature-independent):
```
Expand All @@ -43,6 +48,30 @@ To check formatting without modifying files:
cargo fmt -- --check
```

### Documentation
`README.md` is **auto-generated** from `src/lib.rs` doc comments via [`cargo-readme`](https://github.com/livioribeiro/cargo-readme).

- Edit documentation in **`src/lib.rs`** (inside the top-level `//!` doc comment block), not in `README.md` directly.
- After editing `src/lib.rs`, regenerate `README.md`:
```
cargo readme > README.md
```
- The pre-push hook runs a `cargo readme` diff check; if `README.md` is out of sync with `src/lib.rs`, the push will be rejected.
- If you do not have `cargo-readme` installed:
```
cargo install cargo-readme
```

## Pre-push Checks

The repository has a `.git/hooks/pre-push` script that runs automatically on every push. It performs three checks in order:

1. **Formatting**: `cargo fmt --check`
2. **README sync**: `cargo readme > TMP_README.md && diff -b TMP_README.md README.md`
3. **Clippy**: `cargo clippy --all-targets --all-features -- -D warnings`

If any check fails, the push is aborted. Fix the issue, commit, and push again.

## Notes

- The `bgpkit-parser` binary requires the `cli` feature (`required-features = ["cli"]`).
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ All notable changes to this project will be documented in this file.
* **`Attributes::attr_mask`**: Internal `[u64; 4]` bitmask field for O(1) attribute presence tracking (32 bytes vs 256 bytes for `[bool; 256]`)
* **`serde` feature**: Enabled `serde/rc` for `Arc<AsPath>` serialization support in `BgpRouteElem` when the `serde` feature is active.

* **BMP RFC 9515, 9736, 9972 support**:
- RFC 9972: Added 26 new `StatType` variants for advanced Adj-RIB-In/Adj-RIB-Out monitoring statistics (types 18–43)
- RFC 9736: Separated Peer Up TLV namespace from Initiation TLVs; both enums now use `FromPrimitive` with catch-all `Unknown(u16)` for forward compatibility with unassigned types
- RFC 9515: Documentation-only update (registration procedures changed to "First Come First Served")
* **Integration tests**: Added `tests/test_bgp_update_validation.rs` with comprehensive scenario coverage for all validation cases
* **Example `examples/parse_bmp_mpls.rs`**: Demonstrates extracting MPLS-labeled NLRI from BMP Route Monitoring messages, showing how to access label stack information via `MpReachNlri` attribute

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,9 @@ BGPKIT Parser implements comprehensive BGP, MRT, BMP, and related protocol stand
- [RFC 7854](https://datatracker.ietf.org/doc/html/rfc7854): BGP Monitoring Protocol (BMP)
- [RFC 8671](https://datatracker.ietf.org/doc/html/rfc8671): Support for Adj-RIB-Out in BMP
- [RFC 9069](https://datatracker.ietf.org/doc/html/rfc9069): Support for Local RIB in BMP
- [RFC 9515](https://datatracker.ietf.org/doc/html/rfc9515): Revision to Registration Procedures for Multiple BMP Registries
- [RFC 9736](https://datatracker.ietf.org/doc/html/rfc9736): The BGP Monitoring Protocol (BMP) Peer Up Message Namespace
- [RFC 9972](https://datatracker.ietf.org/doc/html/rfc9972): Advanced BGP Monitoring Protocol (BMP) Statistics Types

### BGP Communities

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,9 @@ BGPKIT Parser implements comprehensive BGP, MRT, BMP, and related protocol stand
- [RFC 7854](https://datatracker.ietf.org/doc/html/rfc7854): BGP Monitoring Protocol (BMP)
- [RFC 8671](https://datatracker.ietf.org/doc/html/rfc8671): Support for Adj-RIB-Out in BMP
- [RFC 9069](https://datatracker.ietf.org/doc/html/rfc9069): Support for Local RIB in BMP
- [RFC 9515](https://datatracker.ietf.org/doc/html/rfc9515): Revision to Registration Procedures for Multiple BMP Registries
- [RFC 9736](https://datatracker.ietf.org/doc/html/rfc9736): The BGP Monitoring Protocol (BMP) Peer Up Message Namespace
- [RFC 9972](https://datatracker.ietf.org/doc/html/rfc9972): Advanced BGP Monitoring Protocol (BMP) Statistics Types

## BGP Communities

Expand Down
22 changes: 0 additions & 22 deletions src/parser/bmp/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::bmp::messages::headers::BmpPeerType;
use crate::bmp::messages::initiation_message::InitiationTlvType;
use crate::bmp::messages::peer_down_notification::PeerDownReason;
use crate::bmp::messages::peer_up_notification::PeerUpTlvType;
use crate::bmp::messages::route_mirroring::RouteMirroringInfo;
use crate::bmp::messages::termination_message::{TerminationReason, TerminationTlvType};
use crate::bmp::messages::BmpMsgType;
Expand Down Expand Up @@ -76,18 +74,6 @@ impl From<TryFromPrimitiveError<BmpPeerType>> for ParserBmpError {
}
}

impl From<TryFromPrimitiveError<InitiationTlvType>> for ParserBmpError {
fn from(_: TryFromPrimitiveError<InitiationTlvType>) -> Self {
ParserBmpError::UnknownTlvType
}
}

impl From<TryFromPrimitiveError<PeerUpTlvType>> for ParserBmpError {
fn from(_: TryFromPrimitiveError<PeerUpTlvType>) -> Self {
ParserBmpError::UnknownTlvType
}
}

impl From<TryFromPrimitiveError<RouteMirroringInfo>> for ParserBmpError {
fn from(_: TryFromPrimitiveError<RouteMirroringInfo>) -> Self {
ParserBmpError::CorruptedBmpMessage
Expand Down Expand Up @@ -166,10 +152,6 @@ mod tests {
ParserBmpError::from(TryFromPrimitiveError::<BmpPeerType>::new(0)),
ParserBmpError::CorruptedBmpMessage
);
assert_eq!(
ParserBmpError::from(TryFromPrimitiveError::<InitiationTlvType>::new(0)),
ParserBmpError::UnknownTlvType
);
assert_eq!(
ParserBmpError::from(TryFromPrimitiveError::<RouteMirroringInfo>::new(0)),
ParserBmpError::CorruptedBmpMessage
Expand All @@ -178,10 +160,6 @@ mod tests {
ParserBmpError::from(TryFromPrimitiveError::<TerminationTlvType>::new(0)),
ParserBmpError::UnknownTlvType
);
assert_eq!(
ParserBmpError::from(TryFromPrimitiveError::<PeerUpTlvType>::new(0)),
ParserBmpError::UnknownTlvType
);
assert_eq!(
ParserBmpError::from(TryFromPrimitiveError::<TerminationReason>::new(0)),
ParserBmpError::UnknownTlvValue
Expand Down
43 changes: 37 additions & 6 deletions src/parser/bmp/messages/initiation_message.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::parser::bmp::error::ParserBmpError;
use crate::parser::ReadUtils;
use bytes::{Buf, Bytes};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::convert::TryFrom;
use num_enum::{FromPrimitive, IntoPrimitive};

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -18,18 +17,27 @@ pub struct InitiationTlv {
pub info: String,
}

///Type-Length-Value Type
/// BMP Initiation Information TLV Type
///
/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-peer-up-tlvs>
#[derive(Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-information-tlvs>
///
/// Per RFC 9736, the "BMP Initiation and Peer Up Information TLVs" registry was renamed
/// to "BMP Initiation Information TLVs", and types 3, 4, and 65535 are reserved in this
/// namespace. The VrTableName and AdminLabel variants are retained for backward compatibility
/// with pre-RFC 9736 implementations.
#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
Comment thread
digizeph marked this conversation as resolved.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u16)]
pub enum InitiationTlvType {
String = 0,
SysDescr = 1,
SysName = 2,
/// Reserved per RFC 9736, retained for backward compatibility.
VrTableName = 3,
/// Reserved per RFC 9736, retained for backward compatibility.
AdminLabel = 4,
#[num_enum(catch_all)]
Unknown(u16) = 65535,
}

/// Parse BMP initiation message
Expand All @@ -39,7 +47,7 @@ pub fn parse_initiation_message(data: &mut Bytes) -> Result<InitiationMessage, P
let mut tlvs = vec![];

while data.remaining() > 4 {
let info_type: InitiationTlvType = InitiationTlvType::try_from(data.read_u16()?)?;
let info_type = InitiationTlvType::from(data.read_u16()?);
let info_len = data.read_u16()?;
if data.remaining() < info_len as usize {
// not enough bytes to read
Expand Down Expand Up @@ -82,6 +90,29 @@ mod tests {
}
}

#[test]
fn test_parse_unknown_initiation_tlv() {
let mut buffer = BytesMut::new();
// Known TLV (SysDescr)
buffer.put_u16(1); // InitiationTlvType::SysDescr
buffer.put_u16(4);
buffer.put_slice(b"Test");
// Unknown TLV (unassigned type 255)
buffer.put_u16(255);
buffer.put_u16(3);
buffer.put_slice(b"Foo");

let mut bytes = buffer.freeze();
let result = parse_initiation_message(&mut bytes).unwrap();

assert_eq!(result.tlvs.len(), 2);
assert_eq!(result.tlvs[0].info_type, InitiationTlvType::SysDescr);
assert_eq!(result.tlvs[0].info, "Test");
assert_eq!(result.tlvs[1].info_type, InitiationTlvType::Unknown(255));
assert_eq!(result.tlvs[1].info_len, 3);
assert_eq!(result.tlvs[1].info, "Foo");
}

#[test]
fn test_debug() {
let initiation_message = InitiationMessage {
Expand Down
72 changes: 67 additions & 5 deletions src/parser/bmp/messages/peer_up_notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::parser::bmp::messages::BmpPeerType;
use crate::parser::ReadUtils;
use bytes::{Buf, Bytes};
use log::warn;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use num_enum::{FromPrimitive, IntoPrimitive};
use std::net::IpAddr;

#[derive(Debug, PartialEq, Clone)]
Expand All @@ -20,18 +20,27 @@ pub struct PeerUpNotification {
pub tlvs: Vec<PeerUpNotificationTlv>,
}

///Type-Length-Value Type
/// BMP Peer Up Message TLV Type
///
/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-peer-up-tlvs>
#[derive(Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#peer-up-message-tlvs>
///
/// RFC 9736 created an independent namespace for Peer Up Information TLVs, separate from
/// the Initiation message namespace. Per the new registry, types 1, 2, and 65535 are reserved.
/// The SysDescr and SysName variants are retained for backward compatibility with pre-RFC 9736
/// implementations.
#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u16)]
pub enum PeerUpTlvType {
String = 0,
/// Reserved per RFC 9736, retained for backward compatibility.
SysDescr = 1,
/// Reserved per RFC 9736, retained for backward compatibility.
SysName = 2,
VrTableName = 3,
AdminLabel = 4,
#[num_enum(catch_all)]
Unknown(u16) = 65535,
}

#[derive(Debug, PartialEq, Clone)]
Expand Down Expand Up @@ -104,7 +113,7 @@ pub fn parse_peer_up_notification(
let mut has_vr_table_name = false;

while data.remaining() >= 4 {
let info_type = PeerUpTlvType::try_from(data.read_u16()?)?;
let info_type = PeerUpTlvType::from(data.read_u16()?);
let info_len = data.read_u16()?;
let info_value = data.read_n_bytes_to_string(info_len as usize)?;

Expand Down Expand Up @@ -821,6 +830,59 @@ mod tests {
// In production, a warning would be logged about oversized VrTableName
}

#[test]
fn test_parse_peer_up_unknown_tlv() {
let mut data = BytesMut::new();

// Local address setup
data.extend_from_slice(&[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
0x01, 0x01, // 192.168.1.1
0x00, 0xB3, // local port 179
0x00, 0xB3, // remote port 179
]);

// Add two complete BGP OPEN messages
let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
version: 4,
asn: crate::models::Asn::new_32bit(65001),
hold_time: 180,
bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
extended_length: false,
opt_params: vec![],
});
let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
data.extend_from_slice(&bgp_bytes);
data.extend_from_slice(&bgp_bytes);

// Add a known TLV (String) and an unknown TLV (type 255)
data.extend_from_slice(&[0x00, 0x00]); // String TLV type
data.extend_from_slice(&[0x00, 0x04]); // length 4
data.extend_from_slice(b"Test");

data.extend_from_slice(&[0x00, 0xFF]); // Unknown TLV type (255)
data.extend_from_slice(&[0x00, 0x03]); // length 3
data.extend_from_slice(b"Foo");

let afi = Afi::Ipv4;
let asn_len = AsnLength::Bits32;
let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);

assert!(
result.is_ok(),
"Should parse unknown TLV types without error"
);
let peer_notification = result.unwrap();
assert_eq!(peer_notification.tlvs.len(), 2);
assert_eq!(peer_notification.tlvs[0].info_type, PeerUpTlvType::String);
assert_eq!(peer_notification.tlvs[0].info_value, "Test");
assert_eq!(
peer_notification.tlvs[1].info_type,
PeerUpTlvType::Unknown(255)
);
assert_eq!(peer_notification.tlvs[1].info_value, "Foo");
}

#[test]
fn test_non_local_rib_no_validation() {
// This test verifies that non-LocalRib peer types don't trigger LocalRib validations
Expand Down
24 changes: 24 additions & 0 deletions src/parser/bmp/messages/stats_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,30 @@ pub enum StatType {
RoutesInPostPolicyAdjRibOut = 15,
RoutesInPerAfiSafiPrePolicyAdjRibOut = 16,
RoutesInPerAfiSafiPostPolicyAdjRibOut = 17,
RoutesInPrePolicyAdjRibIn = 18,
RoutesInPerAfiSafiPrePolicyAdjRibIn = 19,
RoutesInPostPolicyAdjRibIn = 20,
RoutesInPerAfiSafiPostPolicyAdjRibIn = 21,
RoutesInPerAfiSafiPrePolicyAdjRibInRejectedByPolicy = 22,
RoutesInPerAfiSafiPostPolicyAdjRibInAcceptedByPolicy = 23,
RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibSuppressed = 26,
RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibStaleByGracefulRestart = 27,
RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibStaleByLongLivedGracefulRestart = 28,
RoutesInPostPolicyAdjRibInLeftBeforeThreshold = 29,
RoutesInPerAfiSafiPostPolicyAdjRibInLeftBeforeThreshold = 30,
RoutesInPostPolicyAdjRibInOrLocRibLeftBeforeLicenseThreshold = 31,
RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibLeftBeforeLicenseThreshold = 32,
RoutesInPrePolicyAdjRibInRejectedDueToMaxAsPathLength = 33,
RoutesInPerAfiSafiPrePolicyAdjRibInRejectedDueToMaxAsPathLength = 34,
RoutesInPerAfiSafiPostPolicyAdjRibInInvalidatedByRoa = 35,
RoutesInPerAfiSafiPostPolicyAdjRibInValidatedByRoa = 36,
RoutesInPerAfiSafiPostPolicyAdjRibInRpkiStateNotFound = 37,
RoutesInPerAfiSafiPrePolicyAdjRibOutRejectedByPolicy = 38,
RoutesInPrePolicyAdjRibOutFilteredDueToMaxAsPathLength = 39,
RoutesInPerAfiSafiPrePolicyAdjRibOutFilteredDueToMaxAsPathLength = 40,
RoutesInPerAfiSafiPostPolicyAdjRibOutInvalidatedByRoa = 41,
RoutesInPerAfiSafiPostPolicyAdjRibOutValidatedByRoa = 42,
RoutesInPerAfiSafiPostPolicyAdjRibOutRpkiStateNotFound = 43,
#[num_enum(catch_all)]
Other(u16) = 65535,
}
Expand Down
Loading