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