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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ If you would like to see any specific RFC's support, please submit an issue on G
- [X] [RFC 4456](https://datatracker.ietf.org/doc/html/rfc4456): BGP Route Reflection: An Alternative to Full Mesh Internal BGP (IBGP)
- [X] [RFC 5065](https://datatracker.ietf.org/doc/html/rfc5065): Autonomous System Confederations for BGP
- [X] [RFC 6793](https://datatracker.ietf.org/doc/html/rfc6793): BGP Support for Four-Octet Autonomous System (AS) Number Space
- [X] [RFC 7606](https://datatracker.ietf.org/doc/html/rfc7606): Revised Error Handling for BGP UPDATE Messages
- [X] [RFC 7911](https://datatracker.ietf.org/doc/html/rfc7911): Advertisement of Multiple Paths in BGP (ADD-PATH)
- [X] [RFC 8950](https://datatracker.ietf.org/doc/html/rfc8950): Advertising IPv4 Network Layer Reachability Information (NLRI) with an IPv6 Next Hop
- [X] [RFC 9072](https://datatracker.ietf.org/doc/html/rfc9072): Extended Optional Parameters Length for BGP OPEN Message Updates
Expand Down
114 changes: 113 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*!
error module defines the error types used in bgpkit-parser.
*/
use crate::models::{Afi, Bgp4MpType, BgpState, EntryType, Safi, TableDumpV2Type};
use crate::models::{Afi, AttrType, Bgp4MpType, BgpState, EntryType, Safi, TableDumpV2Type};
use num_enum::TryFromPrimitiveError;
#[cfg(feature = "oneio")]
use oneio::OneIoError;
Expand Down Expand Up @@ -123,3 +123,115 @@ impl From<TryFromPrimitiveError<Safi>> for ParserError {
ParserError::ParseError(format!("Unknown SAFI type: {}", value.number))
}
}

/// BGP validation warnings for RFC 7606 compliant error handling.
/// These represent non-fatal validation issues that don't prevent parsing.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BgpValidationWarning {
/// Attribute flags conflict with attribute type code (RFC 4271 Section 6.3)
AttributeFlagsError {
attr_type: AttrType,
expected_flags: u8,
actual_flags: u8,
},
/// Attribute length conflicts with expected length (RFC 4271 Section 6.3)
AttributeLengthError {
attr_type: AttrType,
expected_length: Option<usize>,
actual_length: usize,
},
/// Missing well-known mandatory attribute (RFC 4271 Section 6.3)
MissingWellKnownAttribute { attr_type: AttrType },
/// Unrecognized well-known attribute (RFC 4271 Section 6.3)
UnrecognizedWellKnownAttribute { attr_type_code: u8 },
/// Invalid origin attribute value (RFC 4271 Section 6.3)
InvalidOriginAttribute { value: u8 },
/// Invalid next hop attribute (RFC 4271 Section 6.3)
InvalidNextHopAttribute { reason: String },
/// Malformed AS_PATH attribute (RFC 4271 Section 6.3)
MalformedAsPath { reason: String },
/// Optional attribute error (RFC 4271 Section 6.3)
OptionalAttributeError { attr_type: AttrType, reason: String },
/// Attribute appears more than once (RFC 4271 Section 6.3)
DuplicateAttribute { attr_type: AttrType },
/// Invalid network field in NLRI (RFC 4271 Section 6.3)
InvalidNetworkField { reason: String },
/// Malformed attribute list (RFC 4271 Section 6.3)
MalformedAttributeList { reason: String },
/// Partial attribute with errors (RFC 7606)
PartialAttributeError { attr_type: AttrType, reason: String },
}

impl Display for BgpValidationWarning {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
BgpValidationWarning::AttributeFlagsError { attr_type, expected_flags, actual_flags } => {
write!(f, "Attribute flags error for {attr_type:?}: expected 0x{expected_flags:02x}, got 0x{actual_flags:02x}")
}
BgpValidationWarning::AttributeLengthError { attr_type, expected_length, actual_length } => {
match expected_length {
Some(expected) => write!(f, "Attribute length error for {attr_type:?}: expected {expected}, got {actual_length}"),
None => write!(f, "Attribute length error for {attr_type:?}: invalid length {actual_length}"),
}
}
BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
write!(f, "Missing well-known mandatory attribute: {attr_type:?}")
}
BgpValidationWarning::UnrecognizedWellKnownAttribute { attr_type_code } => {
write!(f, "Unrecognized well-known attribute: type code {attr_type_code}")
}
BgpValidationWarning::InvalidOriginAttribute { value } => {
write!(f, "Invalid origin attribute value: {value}")
}
BgpValidationWarning::InvalidNextHopAttribute { reason } => {
write!(f, "Invalid next hop attribute: {reason}")
}
BgpValidationWarning::MalformedAsPath { reason } => {
write!(f, "Malformed AS_PATH: {reason}")
}
BgpValidationWarning::OptionalAttributeError { attr_type, reason } => {
write!(f, "Optional attribute error for {attr_type:?}: {reason}")
}
BgpValidationWarning::DuplicateAttribute { attr_type } => {
write!(f, "Duplicate attribute: {attr_type:?}")
}
BgpValidationWarning::InvalidNetworkField { reason } => {
write!(f, "Invalid network field: {reason}")
}
BgpValidationWarning::MalformedAttributeList { reason } => {
write!(f, "Malformed attribute list: {reason}")
}
BgpValidationWarning::PartialAttributeError { attr_type, reason } => {
write!(f, "Partial attribute error for {attr_type:?}: {reason}")
}
}
}
}

/// Result type for BGP attribute parsing that includes validation warnings
#[derive(Debug, Clone)]
pub struct BgpValidationResult<T> {
pub value: T,
pub warnings: Vec<BgpValidationWarning>,
}

impl<T> BgpValidationResult<T> {
pub fn new(value: T) -> Self {
Self {
value,
warnings: Vec::new(),
}
}

pub fn with_warnings(value: T, warnings: Vec<BgpValidationWarning>) -> Self {
Self { value, warnings }
}

pub fn add_warning(&mut self, warning: BgpValidationWarning) {
self.warnings.push(warning);
}

pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ If you would like to see any specific RFC's support, please submit an issue on G
- [X] [RFC 4456](https://datatracker.ietf.org/doc/html/rfc4456): BGP Route Reflection: An Alternative to Full Mesh Internal BGP (IBGP)
- [X] [RFC 5065](https://datatracker.ietf.org/doc/html/rfc5065): Autonomous System Confederations for BGP
- [X] [RFC 6793](https://datatracker.ietf.org/doc/html/rfc6793): BGP Support for Four-Octet Autonomous System (AS) Number Space
- [X] [RFC 7606](https://datatracker.ietf.org/doc/html/rfc7606): Revised Error Handling for BGP UPDATE Messages
- [X] [RFC 7911](https://datatracker.ietf.org/doc/html/rfc7911): Advertisement of Multiple Paths in BGP (ADD-PATH)
- [X] [RFC 8950](https://datatracker.ietf.org/doc/html/rfc8950): Advertising IPv4 Network Layer Reachability Information (NLRI) with an IPv6 Next Hop
- [X] [RFC 9072](https://datatracker.ietf.org/doc/html/rfc9072): Extended Optional Parameters Length for BGP OPEN Message Updates
Expand Down Expand Up @@ -411,7 +412,6 @@ We support normal communities, extended communities, and large communities.

#[cfg(feature = "parser")]
pub mod encoder;
#[cfg(feature = "parser")]
pub mod error;
pub mod models;
#[cfg(feature = "parser")]
Expand Down
26 changes: 25 additions & 1 deletion src/models/bgp/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::net::IpAddr;
use std::slice::Iter;
use std::vec::IntoIter;

use crate::error::BgpValidationWarning;
use crate::models::*;

pub use aspath::*;
Expand Down Expand Up @@ -138,6 +139,8 @@ pub struct Attributes {
// Black box type to allow for later changes/optimizations. The most common attributes could be
// added as fields to allow for easier lookup.
pub(crate) inner: Vec<Attribute>,
/// RFC 7606 validation warnings collected during parsing
pub(crate) validation_warnings: Vec<BgpValidationWarning>,
}

impl Attributes {
Expand All @@ -156,6 +159,21 @@ impl Attributes {
self.inner.push(attr);
}

/// Add a validation warning to the attributes
pub fn add_validation_warning(&mut self, warning: BgpValidationWarning) {
self.validation_warnings.push(warning);
}

/// Get all validation warnings for these attributes
pub fn validation_warnings(&self) -> &[BgpValidationWarning] {
&self.validation_warnings
}

/// Check if there are any validation warnings
pub fn has_validation_warnings(&self) -> bool {
!self.validation_warnings.is_empty()
}

/// Get the `ORIGIN` attribute. In the event that this attribute is not present,
/// [Origin::INCOMPLETE] will be returned instead.
pub fn origin(&self) -> Origin {
Expand Down Expand Up @@ -310,13 +328,17 @@ impl FromIterator<Attribute> for Attributes {
fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
Attributes {
inner: iter.into_iter().collect(),
validation_warnings: Vec::new(),
}
}
}

impl From<Vec<Attribute>> for Attributes {
fn from(value: Vec<Attribute>) -> Self {
Attributes { inner: value }
Attributes {
inner: value,
validation_warnings: Vec::new(),
}
}
}

Expand All @@ -342,6 +364,7 @@ impl FromIterator<AttributeValue> for Attributes {
flag: AttrFlags::empty(),
})
.collect(),
validation_warnings: Vec::new(),
}
}
}
Expand Down Expand Up @@ -385,6 +408,7 @@ mod serde_impl {
{
Ok(Attributes {
inner: <Vec<Attribute>>::deserialize(deserializer)?,
validation_warnings: Vec::new(),
})
}
}
Expand Down
20 changes: 18 additions & 2 deletions src/models/bgp/capabilities.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::error::ParserError;
use crate::models::network::{Afi, Safi};
#[cfg(feature = "parser")]
use crate::parser::ReadUtils;
use crate::ParserError;
#[cfg(feature = "parser")]
use bytes::{BufMut, Bytes, BytesMut};
use num_enum::{FromPrimitive, IntoPrimitive};

Expand Down Expand Up @@ -101,6 +103,7 @@ impl ExtendedNextHopCapability {
/// - NLRI AFI (2 bytes)
/// - NLRI SAFI (2 bytes)
/// - NextHop AFI (2 bytes)
#[cfg(feature = "parser")]
pub fn parse(mut data: Bytes) -> Result<Self, ParserError> {
let mut entries = Vec::new();

Expand Down Expand Up @@ -132,6 +135,7 @@ impl ExtendedNextHopCapability {
}

/// Encode Extended Next Hop capability to raw bytes - RFC 8950, Section 3
#[cfg(feature = "parser")]
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::with_capacity(self.entries.len() * 6);

Expand Down Expand Up @@ -168,6 +172,7 @@ impl MultiprotocolExtensionsCapability {
/// - AFI (2 bytes)
/// - Reserved (1 byte) - should be 0
/// - SAFI (1 byte)
#[cfg(feature = "parser")]
pub fn parse(mut data: Bytes) -> Result<Self, ParserError> {
if data.len() != 4 {
return Err(ParserError::ParseError(format!(
Expand All @@ -186,6 +191,7 @@ impl MultiprotocolExtensionsCapability {
}

/// Encode Multiprotocol Extensions capability to raw bytes - RFC 2858, Section 7
#[cfg(feature = "parser")]
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::with_capacity(4);
bytes.put_u16(self.afi as u16); // AFI (2 bytes)
Expand Down Expand Up @@ -238,6 +244,7 @@ impl GracefulRestartCapability {
/// Format:
/// - Restart Flags (4 bits) + Restart Time (12 bits) = 2 bytes total
/// - Followed by 0 or more address family entries (4 bytes each)
#[cfg(feature = "parser")]
pub fn parse(mut data: Bytes) -> Result<Self, ParserError> {
if data.len() < 2 {
return Err(ParserError::ParseError(format!(
Expand Down Expand Up @@ -284,6 +291,7 @@ impl GracefulRestartCapability {
}

/// Encode Graceful Restart capability to raw bytes - RFC 4724
#[cfg(feature = "parser")]
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::with_capacity(2 + self.address_families.len() * 4);

Expand Down Expand Up @@ -367,6 +375,7 @@ impl AddPathCapability {
/// - AFI (2 bytes)
/// - SAFI (1 byte)
/// - Send/Receive (1 byte)
#[cfg(feature = "parser")]
pub fn parse(mut data: Bytes) -> Result<Self, ParserError> {
let mut address_families = Vec::new();

Expand Down Expand Up @@ -397,6 +406,7 @@ impl AddPathCapability {
}

/// Encode ADD-PATH capability to raw bytes - RFC 7911
#[cfg(feature = "parser")]
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::with_capacity(self.address_families.len() * 4);

Expand Down Expand Up @@ -424,6 +434,7 @@ impl RouteRefreshCapability {

/// Parse Route Refresh capability from raw bytes - RFC 2918
/// This capability has length 0, so data should be empty
#[cfg(feature = "parser")]
pub fn parse(data: Bytes) -> Result<Self, ParserError> {
if !data.is_empty() {
return Err(ParserError::ParseError(format!(
Expand All @@ -436,6 +447,7 @@ impl RouteRefreshCapability {

/// Encode Route Refresh capability to raw bytes - RFC 2918
/// Always returns empty bytes since this capability has no parameters
#[cfg(feature = "parser")]
pub fn encode(&self) -> Bytes {
Bytes::new()
}
Expand Down Expand Up @@ -463,6 +475,7 @@ impl FourOctetAsCapability {

/// Parse 4-octet AS capability from raw bytes - RFC 6793
/// Format: 4 bytes containing the AS number
#[cfg(feature = "parser")]
pub fn parse(mut data: Bytes) -> Result<Self, ParserError> {
if data.len() != 4 {
return Err(ParserError::ParseError(format!(
Expand All @@ -476,6 +489,7 @@ impl FourOctetAsCapability {
}

/// Encode 4-octet AS capability to raw bytes - RFC 6793
#[cfg(feature = "parser")]
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::with_capacity(4);
bytes.put_u32(self.asn);
Expand Down Expand Up @@ -533,6 +547,7 @@ impl BgpRoleCapability {

/// Parse BGP Role capability from raw bytes - RFC 9234
/// Format: 1 byte containing the role value
#[cfg(feature = "parser")]
pub fn parse(mut data: Bytes) -> Result<Self, ParserError> {
if data.len() != 1 {
return Err(ParserError::ParseError(format!(
Expand All @@ -547,14 +562,15 @@ impl BgpRoleCapability {
}

/// Encode BGP Role capability to raw bytes - RFC 9234
#[cfg(feature = "parser")]
pub fn encode(&self) -> Bytes {
let mut bytes = BytesMut::with_capacity(1);
bytes.put_u8(self.role as u8);
bytes.freeze()
}
}

#[cfg(test)]
#[cfg(all(test, feature = "parser"))]
mod tests {
use super::*;

Expand Down
Loading
Loading