Skip to main content

bgpkit_parser/parser/mrt/messages/table_dump_v2/
rib_afi_entries.rs

1use crate::bgp::attributes::parse_attributes;
2use crate::encoder::sink::with_u16_len;
3use crate::error::{check_max, EncodingError};
4use crate::models::{
5    Afi, AsnLength, NetworkPrefix, RibAfiEntries, RibEntry, Safi, TableDumpV2Type,
6};
7use crate::parser::ReadUtils;
8use crate::ParserError;
9use bytes::{Buf, BufMut, Bytes, BytesMut};
10use log::warn;
11
12fn extract_afi_safi_from_rib_type(rib_type: &TableDumpV2Type) -> Result<(Afi, Safi), ParserError> {
13    let afi: Afi;
14    let safi: Safi;
15    match rib_type {
16        TableDumpV2Type::RibIpv4Unicast | TableDumpV2Type::RibIpv4UnicastAddPath => {
17            afi = Afi::Ipv4;
18            safi = Safi::Unicast
19        }
20        TableDumpV2Type::RibIpv4Multicast | TableDumpV2Type::RibIpv4MulticastAddPath => {
21            afi = Afi::Ipv4;
22            safi = Safi::Multicast
23        }
24        TableDumpV2Type::RibIpv6Unicast | TableDumpV2Type::RibIpv6UnicastAddPath => {
25            afi = Afi::Ipv6;
26            safi = Safi::Unicast
27        }
28        TableDumpV2Type::RibIpv6Multicast | TableDumpV2Type::RibIpv6MulticastAddPath => {
29            afi = Afi::Ipv6;
30            safi = Safi::Multicast
31        }
32        _ => {
33            return Err(ParserError::ParseError(format!(
34                "wrong RIB type for parsing: {rib_type:?}"
35            )))
36        }
37    };
38
39    Ok((afi, safi))
40}
41
42fn is_add_path_rib_type(rib_type: TableDumpV2Type) -> bool {
43    matches!(
44        rib_type,
45        TableDumpV2Type::RibIpv4UnicastAddPath
46            | TableDumpV2Type::RibIpv4MulticastAddPath
47            | TableDumpV2Type::RibIpv6UnicastAddPath
48            | TableDumpV2Type::RibIpv6MulticastAddPath
49    )
50}
51
52pub(crate) const fn rib_entry_min_len(is_add_path: bool) -> usize {
53    2 /*peer_index*/ + 4 /*time*/ + 2 /*attr_len*/ + if is_add_path { 4 } else { 0 }
54}
55
56/// RIB AFI-specific entries
57///
58/// https://tools.ietf.org/html/rfc6396#section-4.3
59pub fn parse_rib_afi_entries(
60    data: &mut Bytes,
61    rib_type: TableDumpV2Type,
62) -> Result<RibAfiEntries, ParserError> {
63    let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type)?;
64    let is_add_path = is_add_path_rib_type(rib_type);
65
66    let sequence_number = data.read_u32()?;
67
68    // NOTE: here we parse the prefix as only length and prefix, the path identifier for add_path
69    //       entry is not handled here. We follow RFC6396 here https://www.rfc-editor.org/rfc/rfc6396.html#section-4.3.2
70    let prefix = data.read_nlri_prefix(&afi, false)?;
71
72    let entry_count = data.read_u16()?;
73    // Pre-allocate cautiously to avoid overflow/OOM with malformed inputs
74    let min_entry_size = rib_entry_min_len(is_add_path);
75    let max_possible = data.remaining() / min_entry_size;
76    let reserve = (entry_count as usize).min(max_possible).saturating_mul(2);
77    let mut rib_entries = Vec::with_capacity(reserve);
78
79    // get the u8 slice of the rest of the data
80    // let attr_data_slice = &input.into_inner()[(input.position() as usize)..];
81
82    for _i in 0..entry_count {
83        let entry = match parse_rib_entry(data, is_add_path, &afi, &safi, prefix) {
84            Ok(entry) => entry,
85            Err(e) => {
86                warn!(
87                    "early break due to error {} while parsing RIB AFI entries",
88                    e
89                );
90                break;
91            }
92        };
93        rib_entries.push(entry);
94    }
95
96    Ok(RibAfiEntries {
97        rib_type,
98        sequence_number,
99        prefix,
100        rib_entries,
101    })
102}
103
104/// RIB entry: one prefix per entry
105///
106///
107/// https://datatracker.ietf.org/doc/html/rfc6396#section-4.3.4
108/// ```text
109///         0                   1                   2                   3
110///         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
111///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
112///        |         Peer Index            |
113///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
114///        |                         Originated Time                       |
115///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
116///        |      Attribute Length         |
117///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
118///        |                    BGP Attributes... (variable)
119///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
120///
121///                           Figure 10: RIB Entries
122/// ```
123pub fn parse_rib_entry(
124    input: &mut Bytes,
125    is_add_path: bool,
126    afi: &Afi,
127    safi: &Safi,
128    prefix: NetworkPrefix,
129) -> Result<RibEntry, ParserError> {
130    if input.remaining() < 8 {
131        // total length - current position less than 16 --
132        // meaning less than 16 bytes available to read
133        return Err(ParserError::TruncatedMsg("truncated msg".to_string()));
134    }
135
136    let peer_index = input.read_u16()?;
137    let originated_time = input.read_u32()?;
138
139    let path_id = match is_add_path {
140        true => Some(input.read_u32()?),
141        false => None,
142    };
143
144    let attribute_length = input.read_u16()? as usize;
145
146    input.has_n_remaining(attribute_length)?;
147    let attr_data_slice = input.split_to(attribute_length);
148    let mut attributes = parse_attributes(
149        attr_data_slice,
150        &AsnLength::Bits32,
151        is_add_path,
152        Some(*afi),
153        Some(*safi),
154        Some(&[prefix]),
155    )?;
156
157    // validate mandatory attributes (RIB entry is always an announcement)
158    attributes.check_mandatory_attributes(true, *afi == Afi::Ipv4);
159
160    Ok(RibEntry {
161        peer_index,
162        originated_time,
163        path_id,
164        attributes,
165    })
166}
167
168impl RibAfiEntries {
169    pub fn encode(&self) -> Result<Bytes, EncodingError> {
170        let mut bytes = BytesMut::new();
171        let is_add_path = is_add_path_rib_type(self.rib_type);
172
173        bytes.put_u32(self.sequence_number);
174        bytes.extend(self.prefix.encode());
175
176        let entry_count = self.rib_entries.len();
177        check_max("RIB entry count", entry_count, u16::MAX as usize)?;
178        bytes.put_u16(entry_count as u16);
179
180        for entry in &self.rib_entries {
181            entry.encode_for_rib_type(is_add_path, &mut bytes)?;
182        }
183
184        Ok(bytes.freeze())
185    }
186}
187
188impl RibEntry {
189    pub fn encode(&self) -> Result<Bytes, EncodingError> {
190        let mut bytes = BytesMut::new();
191        self.encode_for_rib_type(self.path_id.is_some(), &mut bytes)?;
192        Ok(bytes.freeze())
193    }
194
195    fn encode_for_rib_type(
196        &self,
197        include_path_id: bool,
198        bytes: &mut BytesMut,
199    ) -> Result<(), EncodingError> {
200        bytes.put_u16(self.peer_index);
201        bytes.put_u32(self.originated_time);
202        if include_path_id {
203            if let Some(path_id) = self.path_id {
204                bytes.put_u32(path_id);
205            }
206        }
207        with_u16_len(bytes, "RIB entry attribute length", |b| {
208            self.attributes.encode_to(AsnLength::Bits32, b)
209        })
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use bytes::Buf;
217    use std::str::FromStr;
218
219    #[test]
220    fn test_extract_afi_safi_from_rib_type() {
221        let rib_type = TableDumpV2Type::RibIpv4Unicast;
222        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
223        assert_eq!(afi, Afi::Ipv4);
224        assert_eq!(safi, Safi::Unicast);
225
226        let rib_type = TableDumpV2Type::RibIpv4Multicast;
227        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
228        assert_eq!(afi, Afi::Ipv4);
229        assert_eq!(safi, Safi::Multicast);
230
231        let rib_type = TableDumpV2Type::RibIpv6Unicast;
232        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
233        assert_eq!(afi, Afi::Ipv6);
234        assert_eq!(safi, Safi::Unicast);
235
236        let rib_type = TableDumpV2Type::RibIpv6Multicast;
237        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
238        assert_eq!(afi, Afi::Ipv6);
239        assert_eq!(safi, Safi::Multicast);
240
241        let rib_type = TableDumpV2Type::RibIpv4UnicastAddPath;
242        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
243        assert_eq!(afi, Afi::Ipv4);
244        assert_eq!(safi, Safi::Unicast);
245
246        let rib_type = TableDumpV2Type::RibIpv4MulticastAddPath;
247        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
248        assert_eq!(afi, Afi::Ipv4);
249        assert_eq!(safi, Safi::Multicast);
250
251        let rib_type = TableDumpV2Type::RibIpv6UnicastAddPath;
252        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
253        assert_eq!(afi, Afi::Ipv6);
254        assert_eq!(safi, Safi::Unicast);
255
256        let rib_type = TableDumpV2Type::RibIpv6MulticastAddPath;
257        let (afi, safi) = extract_afi_safi_from_rib_type(&rib_type).unwrap();
258        assert_eq!(afi, Afi::Ipv6);
259        assert_eq!(safi, Safi::Multicast);
260
261        let rib_type = TableDumpV2Type::RibGeneric;
262        let res = extract_afi_safi_from_rib_type(&rib_type);
263        assert!(res.is_err());
264    }
265
266    #[test]
267    fn test_rib_entry_encode() {
268        use crate::models::{AttributeValue, Attributes, Origin};
269
270        let mut attributes = Attributes::default();
271        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
272
273        let rib_entry = RibEntry {
274            peer_index: 1,
275            originated_time: 12345,
276            path_id: Some(42),
277            attributes,
278        };
279
280        let mut encoded = rib_entry.encode().unwrap();
281        assert_eq!(encoded.read_u16().unwrap(), 1);
282        assert_eq!(encoded.read_u32().unwrap(), 12345);
283        assert_eq!(encoded.read_u32().unwrap(), 42);
284        let attr_len = encoded.read_u16().unwrap() as usize;
285        assert_eq!(encoded.remaining(), attr_len);
286    }
287
288    #[test]
289    fn test_rib_afi_entries_encode_roundtrip_add_path() {
290        use crate::models::{AttributeValue, Attributes, Origin};
291
292        let mut attributes = Attributes::default();
293        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
294
295        let rib = RibAfiEntries {
296            rib_type: TableDumpV2Type::RibIpv4UnicastAddPath,
297            sequence_number: 7,
298            prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
299            rib_entries: vec![RibEntry {
300                peer_index: 3,
301                originated_time: 12345,
302                path_id: Some(42),
303                attributes,
304            }],
305        };
306
307        let encoded = rib.encode().unwrap();
308        let parsed = parse_rib_afi_entries(&mut encoded.clone(), rib.rib_type).unwrap();
309        assert_eq!(parsed.rib_type, rib.rib_type);
310        assert_eq!(parsed.sequence_number, rib.sequence_number);
311        assert_eq!(parsed.prefix, rib.prefix);
312        assert_eq!(parsed.rib_entries.len(), 1);
313        assert_eq!(parsed.rib_entries[0].peer_index, 3);
314        assert_eq!(parsed.rib_entries[0].originated_time, 12345);
315        assert_eq!(parsed.rib_entries[0].path_id, Some(42));
316        assert_eq!(
317            parsed.rib_entries[0].attributes.inner,
318            rib.rib_entries[0].attributes.inner
319        );
320    }
321}