diff --git a/README.md b/README.md index 7e120e6..ba389a4 100644 --- a/README.md +++ b/README.md @@ -814,6 +814,7 @@ Full support for standard, extended, and large communities: Typed parsing for these RFC-defined BGP path attributes: +- [RFC 5543](https://datatracker.ietf.org/doc/html/rfc5543): BGP Traffic Engineering Attribute - [RFC 7311](https://datatracker.ietf.org/doc/html/rfc7311): Accumulated IGP Metric (AIGP) Attribute - [RFC 9015](https://datatracker.ietf.org/doc/html/rfc9015): BGP SFP Attribute - [RFC 9026](https://datatracker.ietf.org/doc/html/rfc9026): BFD Discriminator Attribute diff --git a/src/lib.rs b/src/lib.rs index f90566c..291840d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -810,6 +810,7 @@ Full support for standard, extended, and large communities: Typed parsing for these RFC-defined BGP path attributes: +- [RFC 5543](https://datatracker.ietf.org/doc/html/rfc5543): BGP Traffic Engineering Attribute - [RFC 7311](https://datatracker.ietf.org/doc/html/rfc7311): Accumulated IGP Metric (AIGP) Attribute - [RFC 9015](https://datatracker.ietf.org/doc/html/rfc9015): BGP SFP Attribute - [RFC 9026](https://datatracker.ietf.org/doc/html/rfc9026): BFD Discriminator Attribute diff --git a/src/models/bgp/attributes/mod.rs b/src/models/bgp/attributes/mod.rs index ee85ba4..5580870 100644 --- a/src/models/bgp/attributes/mod.rs +++ b/src/models/bgp/attributes/mod.rs @@ -615,6 +615,45 @@ pub struct SfpAttribute { pub tlvs: Vec, } +/// BGP Traffic Engineering Attribute - RFC 5543 +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TrafficEngineering { + /// Switching Capability code defined by GMPLS. + pub switching_capability: u8, + + /// Interface encoding code defined by GMPLS. + pub encoding: u8, + + /// Reserved wire value. + /// + /// RFC 5543 says originators should set this to zero and receivers + /// must ignore it. + pub reserved: u16, + + /// Maximum LSP bandwidth for priorities 0 through 7, in bytes per second. + pub max_lsp_bandwidth: [f32; 8], + + /// Switching-capability-specific information retained as raw bytes. + pub switching_capability_specific: Bytes, +} + +impl PartialEq for TrafficEngineering { + fn eq(&self, other: &Self) -> bool { + self.switching_capability == other.switching_capability + && self.encoding == other.encoding + && self.reserved == other.reserved + && self + .max_lsp_bandwidth + .iter() + .zip(other.max_lsp_bandwidth.iter()) + .all(|(left, right)| left.to_bits() == right.to_bits()) + && self.switching_capability_specific == other.switching_capability_specific + } +} + +impl Eq for TrafficEngineering {} + /// ATTR_SET Attribute - RFC 6368 /// /// Used in BGP/MPLS IP VPNs to transparently carry customer BGP path attributes @@ -662,6 +701,8 @@ pub enum AttributeValue { LinkState(crate::models::bgp::linkstate::LinkStateAttribute), /// BGP Tunnel Encapsulation attribute - RFC 9012 TunnelEncapsulation(crate::models::bgp::tunnel_encap::TunnelEncapAttribute), + /// BGP Traffic Engineering attribute - RFC 5543 + TrafficEngineering(TrafficEngineering), /// BFD Discriminator attribute - RFC 9026 BfdDiscriminator(BfdDiscriminatorAttribute), /// BGP Prefix-SID attribute - RFC 8669 @@ -733,6 +774,7 @@ impl AttributeValue { AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI, AttributeValue::LinkState(_) => AttrType::BGP_LS_ATTRIBUTE, AttributeValue::TunnelEncapsulation(_) => AttrType::TUNNEL_ENCAPSULATION, + AttributeValue::TrafficEngineering(_) => AttrType::TRAFFIC_ENGINEERING, AttributeValue::BfdDiscriminator(_) => AttrType::BFD_DISCRIMINATOR, AttributeValue::BgpPrefixSid(_) => AttrType::BGP_PREFIX_SID, AttributeValue::Bier(_) => AttrType::BIER, @@ -777,6 +819,7 @@ impl AttributeValue { AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive), AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive), AttributeValue::LinkState(_) => Some(OptionalNonTransitive), + AttributeValue::TrafficEngineering(_) => Some(OptionalNonTransitive), AttributeValue::Aigp(_) => Some(OptionalNonTransitive), AttributeValue::BfdDiscriminator(_) => Some(OptionalTransitive), AttributeValue::BgpPrefixSid(_) => Some(OptionalTransitive), @@ -1143,6 +1186,24 @@ mod tests { mp_reach_nlri_attr.attr_category(), Some(AttributeCategory::OptionalNonTransitive) ); + + let traffic_engineering_attr = AttributeValue::TrafficEngineering(TrafficEngineering { + switching_capability: 1, + encoding: 1, + reserved: 0, + max_lsp_bandwidth: [0.0; 8], + switching_capability_specific: Bytes::new(), + }); + + assert_eq!( + traffic_engineering_attr.attr_category(), + Some(AttributeCategory::OptionalNonTransitive) + ); + + assert_eq!( + traffic_engineering_attr.default_flags(), + AttrFlags::OPTIONAL + ); } #[test] diff --git a/src/parser/bgp/attributes/README.md b/src/parser/bgp/attributes/README.md index 6cbafc8..5925539 100644 --- a/src/parser/bgp/attributes/README.md +++ b/src/parser/bgp/attributes/README.md @@ -18,6 +18,7 @@ | Extended Community | [RFC4360][rfc4360] | 16,25 | Yes | | Large Community | [RFC8092][rfc8092] | 32 | Yes | | Only To Customer | [RFC9234][rfc9234] | 35 | Yes | +| Traffic Engineering | [RFC5543][rfc5543] | 24 | Yes | | AIGP | [RFC7311][rfc7311] | 26 | Yes | | BFD Discriminator | [RFC9026][rfc9026] | 38 | Yes | | BGP Prefix-SID | [RFC8669][rfc8669] | 40 | Yes | @@ -30,7 +31,6 @@ |---------------------------------|-------------------------------|-----------|-----------------------------|----------------------------------| | ATTR_SET | [RFC6368][rfc6368] | 128 | Raw-retained / model only | Structured nested parser not yet implemented | | PMSI_TUNNEL | [RFC6514][rfc6514] | 22 | Raw-retained | Structured parser not implemented | -| TRAFFIC_ENGINEERING | [RFC5543][rfc5543] | 24 | Raw-retained | Structured parser not implemented | | IPv6_EXT_COMMUNITIES | [RFC5701][rfc5701] | 25 | ✅ Implemented | Listed in main table above | | PE_DISTINGUISHER_LABELS | [RFC6514][rfc6514] | 27 | Raw-retained | Structured parser not implemented | | BGPSEC_PATH | [RFC8205][rfc8205] | 33 | Raw-retained | Structured parser not implemented | @@ -57,4 +57,4 @@ [rfc9026]: https://datatracker.ietf.org/doc/html/rfc9026 [rfc9234]: https://datatracker.ietf.org/doc/html/rfc9234 [rfc9793]: https://datatracker.ietf.org/doc/html/rfc9793 -[iana-bgp]: https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml \ No newline at end of file +[iana-bgp]: https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml diff --git a/src/parser/bgp/attributes/attr_24_traffic_engineering.rs b/src/parser/bgp/attributes/attr_24_traffic_engineering.rs new file mode 100644 index 0000000..5cdf821 --- /dev/null +++ b/src/parser/bgp/attributes/attr_24_traffic_engineering.rs @@ -0,0 +1,225 @@ +//! BGP Traffic Engineering attribute parsing and encoding - RFC 5543 +//! +//! RFC 5543: + +use bytes::{Buf, BufMut, Bytes, BytesMut}; + +use crate::error::{EncodingError, ParserError}; +use crate::models::*; +use crate::parser::ReadUtils; + +/// Parse the BGP Traffic Engineering attribute (type 24). +/// +/// Parses the 36-octet fixed portion defined by RFC 5543 and retains any +/// remaining switching-capability-specific information as raw bytes. +pub fn parse_traffic_engineering(mut input: Bytes) -> Result { + // RFC 5543 fixed portion: + // 1 octet switching capability, 1 octet encoding, 2 octets reserved, + // and eight 4-octet maximum LSP bandwidth values. + const FIXED_LENGTH: usize = 36; + + if input.remaining() < FIXED_LENGTH { + return Err(ParserError::TruncatedMsg(format!( + "truncated Traffic Engineering attribute: need at least {FIXED_LENGTH} bytes, have {}", + input.remaining() + ))); + } + + let switching_capability = input.read_u8()?; + let encoding = input.read_u8()?; + let reserved = input.read_u16()?; + + let mut max_lsp_bandwidth = [0.0_f32; 8]; + for bandwidth in &mut max_lsp_bandwidth { + *bandwidth = f32::from_bits(input.read_u32()?); + } + + let switching_capability_specific = input; + + Ok(AttributeValue::TrafficEngineering(TrafficEngineering { + switching_capability, + encoding, + reserved, + max_lsp_bandwidth, + switching_capability_specific, + })) +} + +/// Encode a BGP Traffic Engineering attribute. +/// +/// Bandwidth values are written as their IEEE-754 single-precision bit +/// representations, and switching-capability-specific information is +/// appended unchanged. +pub fn encode_traffic_engineering( + attr: &TrafficEngineering, + output: &mut BytesMut, +) -> Result<(), EncodingError> { + output.put_u8(attr.switching_capability); + output.put_u8(attr.encoding); + output.put_u16(attr.reserved); + + for bandwidth in &attr.max_lsp_bandwidth { + output.put_u32(bandwidth.to_bits()); + } + + output.put_slice(&attr.switching_capability_specific); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use bytes::{BufMut, BytesMut}; + + #[test] + fn test_parse_traffic_engineering_psc1() { + let mut input = BytesMut::new(); + + input.put_u8(1); // PSC-1 + input.put_u8(1); // Encoding value + input.put_u16(0x1234); // Reserved field + + for bandwidth in [ + 1000.0_f32, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0, 7000.0, 8000.0, + ] { + input.put_f32(bandwidth); + } + + let mut capability_specific = BytesMut::new(); + capability_specific.put_f32(500.0); // Minimum LSP bandwidth + capability_specific.put_u16(1500); // Interface MTU + input.extend_from_slice(&capability_specific); + + let value = parse_traffic_engineering(input.freeze()).unwrap(); + + let attr = match value { + AttributeValue::TrafficEngineering(attr) => attr, + other => panic!("expected TrafficEngineering, got {other:?}"), + }; + + assert_eq!(attr.switching_capability, 1); + assert_eq!(attr.encoding, 1); + assert_eq!(attr.reserved, 0x1234); + + let expected_bandwidths = [ + 1000.0_f32, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0, 7000.0, 8000.0, + ]; + + for (actual, expected) in attr + .max_lsp_bandwidth + .iter() + .zip(expected_bandwidths.iter()) + { + assert_eq!(actual.to_bits(), expected.to_bits()); + } + + assert_eq!( + attr.switching_capability_specific, + capability_specific.freeze() + ); + } + + #[test] + fn test_parse_traffic_engineering_unknown_capability() { + let mut input = BytesMut::new(); + + input.put_u8(0xFE); // Unknown switching capability + input.put_u8(0xFD); // Unknown encoding + input.put_u16(0); // Reserved + + for bandwidth in [1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] { + input.put_f32(bandwidth); + } + + let capability_specific = Bytes::from_static(&[0xDE, 0xAD, 0xBE, 0xEF]); + input.extend_from_slice(&capability_specific); + + let value = parse_traffic_engineering(input.freeze()).unwrap(); + + let attr = match value { + AttributeValue::TrafficEngineering(attr) => attr, + other => panic!("expected TrafficEngineering, got {other:?}"), + }; + + assert_eq!(attr.switching_capability, 0xFE); + assert_eq!(attr.encoding, 0xFD); + assert_eq!(attr.switching_capability_specific, capability_specific); + } + + #[test] + fn test_parse_traffic_engineering_rejects_truncated_fixed_part() { + let input = Bytes::from(vec![0_u8; 35]); + + let error = parse_traffic_engineering(input).unwrap_err(); + + assert!(matches!(error, ParserError::TruncatedMsg(_))); + } + + #[test] + fn test_encode_traffic_engineering_psc1() { + let attr = TrafficEngineering { + switching_capability: 1, + encoding: 1, + reserved: 0x1234, + max_lsp_bandwidth: [ + 1000.0_f32, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0, 7000.0, 8000.0, + ], + switching_capability_specific: Bytes::from_static(&[ + 0x43, 0xFA, 0x00, 0x00, // Minimum LSP bandwidth: 500.0 + 0x05, 0xDC, // Interface MTU: 1500 + ]), + }; + + let mut output = BytesMut::new(); + encode_traffic_engineering(&attr, &mut output).unwrap(); + + assert_eq!( + output.freeze(), + Bytes::from_static(&[ + 0x01, // PSC-1 + 0x01, // Encoding + 0x12, 0x34, // Reserved + 0x44, 0x7A, 0x00, 0x00, // 1000.0 + 0x44, 0xFA, 0x00, 0x00, // 2000.0 + 0x45, 0x3B, 0x80, 0x00, // 3000.0 + 0x45, 0x7A, 0x00, 0x00, // 4000.0 + 0x45, 0x9C, 0x40, 0x00, // 5000.0 + 0x45, 0xBB, 0x80, 0x00, // 6000.0 + 0x45, 0xDA, 0xC0, 0x00, // 7000.0 + 0x45, 0xFA, 0x00, 0x00, // 8000.0 + 0x43, 0xFA, 0x00, 0x00, // Minimum LSP bandwidth: 500.0 + 0x05, 0xDC, // Interface MTU: 1500 + ]) + ); + } + + #[test] + fn test_traffic_engineering_unknown_capability_round_trip() { + let mut input = BytesMut::new(); + + input.put_u8(0xFE); // Unknown switching capability + input.put_u8(0xFD); // Unknown encoding + input.put_u16(0x1234); // Reserved + + for bandwidth in [1.0_f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] { + input.put_f32(bandwidth); + } + + input.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); + + let original = input.freeze(); + + let value = parse_traffic_engineering(original.clone()).unwrap(); + + let attr = match value { + AttributeValue::TrafficEngineering(attr) => attr, + other => panic!("expected TrafficEngineering, got {other:?}"), + }; + + let mut encoded = BytesMut::new(); + encode_traffic_engineering(&attr, &mut encoded).unwrap(); + + assert_eq!(encoded.freeze(), original); + } +} diff --git a/src/parser/bgp/attributes/mod.rs b/src/parser/bgp/attributes/mod.rs index 4004106..1e6cd25 100644 --- a/src/parser/bgp/attributes/mod.rs +++ b/src/parser/bgp/attributes/mod.rs @@ -10,6 +10,7 @@ mod attr_10_13_cluster; mod attr_14_15_nlri; mod attr_16_25_extended_communities; mod attr_23_tunnel_encap; +mod attr_24_traffic_engineering; mod attr_26_aigp; mod attr_29_linkstate; mod attr_32_large_communities; @@ -50,6 +51,9 @@ use crate::parser::bgp::attributes::attr_16_25_extended_communities::{ use crate::parser::bgp::attributes::attr_23_tunnel_encap::{ encode_tunnel_encapsulation_attribute, parse_tunnel_encapsulation_attribute, }; +use crate::parser::bgp::attributes::attr_24_traffic_engineering::{ + encode_traffic_engineering, parse_traffic_engineering, +}; use crate::parser::bgp::attributes::attr_26_aigp::{encode_aigp, parse_aigp}; use crate::parser::bgp::attributes::attr_29_linkstate::{ encode_link_state_attribute, parse_link_state_attribute, @@ -86,7 +90,8 @@ fn validate_attribute_flags( | AttrType::ORIGINATOR_ID | AttrType::CLUSTER_LIST | AttrType::MP_REACHABLE_NLRI - | AttrType::MP_UNREACHABLE_NLRI => AttrFlags::OPTIONAL, + | AttrType::MP_UNREACHABLE_NLRI + | AttrType::TRAFFIC_ENGINEERING => AttrFlags::OPTIONAL, // Optional transitive attributes AttrType::AGGREGATOR | AttrType::AS4_AGGREGATOR @@ -125,7 +130,8 @@ fn validate_attribute_flags( | AttrType::ORIGINATOR_ID | AttrType::CLUSTER_LIST | AttrType::MP_REACHABLE_NLRI - | AttrType::MP_UNREACHABLE_NLRI => { + | AttrType::MP_UNREACHABLE_NLRI + | AttrType::TRAFFIC_ENGINEERING => { warnings.push(BgpValidationWarning::AttributeFlagsError { attr_type, expected_flags: expected_flags.bits(), @@ -142,7 +148,6 @@ fn is_raw_retained_attr(attr_type: AttrType) -> bool { attr_type, AttrType::RESERVED | AttrType::PMSI_TUNNEL - | AttrType::TRAFFIC_ENGINEERING | AttrType::PE_DISTINGUISHER_LABELS | AttrType::BGPSEC_PATH | AttrType::ATTR_SET @@ -462,6 +467,7 @@ pub fn parse_attributes( AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data), AttrType::AIGP => parse_aigp(attr_data), AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data), + AttrType::TRAFFIC_ENGINEERING => parse_traffic_engineering(attr_data), AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data), AttrType::SFP_ATTRIBUTE => parse_sfp(attr_data), AttrType::BFD_DISCRIMINATOR => parse_bfd_discriminator(attr_data), @@ -560,6 +566,7 @@ impl Attribute { AttributeValue::TunnelEncapsulation(v) => { encode_tunnel_encapsulation_attribute(v, b)? } + AttributeValue::TrafficEngineering(v) => encode_traffic_engineering(v, b)?, AttributeValue::BfdDiscriminator(v) => encode_bfd_discriminator(v, b)?, AttributeValue::BgpPrefixSid(v) => encode_bgp_prefix_sid(v, b)?, AttributeValue::Bier(v) => encode_bier(v, b)?, diff --git a/src/parser/mrt/mrt_elem.rs b/src/parser/mrt/mrt_elem.rs index df52724..420da3a 100644 --- a/src/parser/mrt/mrt_elem.rs +++ b/src/parser/mrt/mrt_elem.rs @@ -149,6 +149,7 @@ fn get_relevant_attributes( | AttributeValue::Development(_) | AttributeValue::LinkState(_) | AttributeValue::TunnelEncapsulation(_) + | AttributeValue::TrafficEngineering(_) | AttributeValue::Aigp(_) | AttributeValue::BfdDiscriminator(_) | AttributeValue::BgpPrefixSid(_)