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 @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions src/models/bgp/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,45 @@ pub struct SfpAttribute {
pub tlvs: Vec<RawTlv8Ext>,
}

/// 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions src/parser/bgp/attributes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |
Expand All @@ -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
[iana-bgp]: https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml
225 changes: 225 additions & 0 deletions src/parser/bgp/attributes/attr_24_traffic_engineering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
//! BGP Traffic Engineering attribute parsing and encoding - RFC 5543
//!
//! RFC 5543: <https://datatracker.ietf.org/doc/html/rfc5543>

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<AttributeValue, ParserError> {
// 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);
}
}
Loading
Loading