Skip to main content

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

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