Skip to main content

bgpkit_parser/parser/mrt/messages/
table_dump.rs

1use crate::encoder::sink::with_u16_len;
2use crate::error::*;
3use crate::models::*;
4use crate::parser::bgp::attributes::parse_attributes;
5use crate::parser::ReadUtils;
6use bytes::{BufMut, Bytes, BytesMut};
7use ipnet::IpNet;
8use std::net::IpAddr;
9
10/// Parse MRT TABLE_DUMP type message.
11///
12/// <https://www.rfc-editor.org/rfc/rfc6396#section-4.2>
13///
14/// ```text
15/// The TABLE_DUMP Type does not permit 4-byte Peer AS numbers, nor does
16//  it allow the AFI of the peer IP to differ from the AFI of the Prefix
17//  field.  The TABLE_DUMP_V2 Type MUST be used in these situations.
18/// ```
19///
20/// ```text
21///  0                   1                   2                   3
22///  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
23/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24/// |         View Number           |       Sequence Number         |
25/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26/// |                        Prefix (variable)                      |
27/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28/// | Prefix Length |    Status     |
29/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30/// |                         Originated Time                       |
31/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32/// |                    Peer IP Address (variable)                 |
33/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34/// |           Peer AS             |       Attribute Length        |
35/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36/// |                   BGP Attribute... (variable)
37/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38/// ```
39pub fn parse_table_dump_message(
40    sub_type: u16,
41    data: Bytes,
42) -> Result<TableDumpMessage, ParserError> {
43    let mut messages = parse_table_dump_messages(sub_type, data)?;
44    if messages.len() != 1 {
45        return Err(ParserError::ParseError(format!(
46            "expected one TABLE_DUMP entry, found {}",
47            messages.len()
48        )));
49    }
50    Ok(messages.remove(0))
51}
52
53/// Parse all entries carried by one physical TABLE_DUMP record.
54///
55/// RFC 6396 records contain one entry. Early MRT implementations batched
56/// multiple entries behind one view/sequence header.
57pub fn parse_table_dump_messages(
58    sub_type: u16,
59    mut data: Bytes,
60) -> Result<Vec<TableDumpMessage>, ParserError> {
61    // ####
62    // Step 0. prepare
63    //   - define AS number length
64    //   - determine address family
65    //   - create data slice reader cursor
66
67    // determine address family based on the sub_type value defined in the MRT [CommonHeader].
68    let afi = match sub_type {
69        1 => Afi::Ipv4,
70        2 => Afi::Ipv6,
71        _ => {
72            return Err(ParserError::ParseError(format!(
73                "Invalid subtype found for TABLE_DUMP (V1) message: {sub_type}"
74            )))
75        }
76    };
77
78    // ####
79    // Step 1. read simple fields
80    //   - view number
81    //   - sequence number
82    //   - prefix
83    //   - prefix-length
84    //   - status
85    //   - originated time
86    //   - peer IP address
87    //   - peer ASN
88    //   - attribute length
89
90    let view_number = data.read_u16()?;
91    let sequence_number = data.read_u16()?;
92    let mut messages = Vec::new();
93
94    while !data.is_empty() {
95        messages.push(parse_table_dump_entry(
96            &mut data,
97            &afi,
98            view_number,
99            sequence_number,
100        )?);
101    }
102
103    if messages.is_empty() {
104        return Err(ParserError::TruncatedMsg(
105            "TABLE_DUMP record contains no entries".to_string(),
106        ));
107    }
108
109    Ok(messages)
110}
111
112fn parse_table_dump_entry(
113    data: &mut Bytes,
114    afi: &Afi,
115    view_number: u16,
116    sequence_number: u16,
117) -> Result<TableDumpMessage, ParserError> {
118    let prefix = match afi {
119        Afi::Ipv4 => data.read_ipv4_prefix().map(ipnet::IpNet::V4),
120        Afi::Ipv6 => data.read_ipv6_prefix().map(ipnet::IpNet::V6),
121        Afi::LinkState => {
122            // Link-State doesn't use traditional prefixes, but we need a placeholder
123            // Use 0.0.0.0/0 as a placeholder for now
124            Ok(ipnet::IpNet::V4(
125                ipnet::Ipv4Net::new(std::net::Ipv4Addr::new(0, 0, 0, 0), 0).unwrap(),
126            ))
127        }
128    }?;
129
130    let status = data.read_u8()?;
131    let time = data.read_u32()? as u64;
132
133    let peer_ip: IpAddr = data.read_address(afi)?;
134    let peer_asn = Asn::new_16bit(data.read_u16()?);
135
136    let attribute_length = data.read_u16()? as usize;
137
138    // ####
139    // Step 2. read the attributes
140    //   - create subslice based on the cursor's current position
141    //   - pass the data into the parser function
142
143    data.has_n_remaining(attribute_length)?;
144    let attr_data_slice = data.split_to(attribute_length);
145
146    // for TABLE_DUMP type, the AS number length is always 2-byte.
147    let mut attributes =
148        parse_attributes(attr_data_slice, &AsnLength::Bits16, false, None, None, None)?;
149
150    // validate mandatory attributes (TABLE_DUMP is always an announcement)
151    attributes.check_mandatory_attributes(true, *afi == Afi::Ipv4);
152
153    Ok(TableDumpMessage {
154        view_number,
155        sequence_number,
156        prefix: NetworkPrefix::new(prefix, None),
157        status,
158        originated_time: time,
159        peer_ip,
160        peer_asn,
161        attributes,
162    })
163}
164
165impl TableDumpMessage {
166    pub fn encode(&self) -> Result<Bytes, EncodingError> {
167        let mut bytes = BytesMut::new();
168        bytes.put_u16(self.view_number);
169        bytes.put_u16(self.sequence_number);
170        self.encode_entry_to(&mut bytes)?;
171        Ok(bytes.freeze())
172    }
173
174    fn encode_entry_to(&self, bytes: &mut BytesMut) -> Result<(), EncodingError> {
175        match &self.prefix.prefix {
176            IpNet::V4(p) => {
177                bytes.put_u32(p.addr().into());
178                bytes.put_u8(p.prefix_len());
179            }
180            IpNet::V6(p) => {
181                bytes.put_u128(p.addr().into());
182                bytes.put_u8(p.prefix_len());
183            }
184        }
185        bytes.put_u8(self.status);
186        bytes.put_u32(self.originated_time as u32);
187
188        // peer address and peer asn
189        match self.peer_ip {
190            IpAddr::V4(a) => {
191                bytes.put_u32(a.into());
192            }
193            IpAddr::V6(a) => {
194                bytes.put_u128(a.into());
195            }
196        }
197        bytes.put_u16(self.peer_asn.into());
198
199        // encode attributes; asn_len is always 16-bit for TABLE_DUMP
200        with_u16_len(bytes, "TABLE_DUMP attribute length", |b| {
201            self.attributes.encode_to(AsnLength::Bits16, b)
202        })?;
203        Ok(())
204    }
205}
206
207pub(crate) fn encode_table_dump_batch(
208    messages: &[TableDumpMessage],
209    sub_type: u16,
210) -> Result<Bytes, EncodingError> {
211    let Some(first) = messages.first() else {
212        return Err(EncodingError::unencodable(
213            "TABLE_DUMP batch",
214            "batch is empty",
215        ));
216    };
217
218    let expected_ipv4 = match sub_type {
219        1 => true,
220        2 => false,
221        _ => {
222            return Err(EncodingError::unencodable(
223                "TABLE_DUMP batch",
224                format!("invalid subtype {sub_type}"),
225            ));
226        }
227    };
228
229    let mut bytes = BytesMut::new();
230    bytes.put_u16(first.view_number);
231    bytes.put_u16(first.sequence_number);
232    for message in messages {
233        if message.view_number != first.view_number
234            || message.sequence_number != first.sequence_number
235        {
236            return Err(EncodingError::unencodable(
237                "TABLE_DUMP batch",
238                "all entries must have the same view and sequence numbers",
239            ));
240        }
241        if matches!(message.prefix.prefix, IpNet::V4(_)) != expected_ipv4
242            || matches!(message.peer_ip, IpAddr::V4(_)) != expected_ipv4
243        {
244            return Err(EncodingError::unencodable(
245                "TABLE_DUMP batch",
246                "entry address family does not match the MRT subtype",
247            ));
248        }
249        message.encode_entry_to(&mut bytes)?;
250    }
251    Ok(bytes.freeze())
252}
253
254/// Return true when a historical batched TABLE_DUMP body is structurally
255/// complete except for the final four attribute bytes.
256///
257/// This recovers files written by an old MRT writer, likely MRT Toolkit. The
258/// surviving MRT Toolkit source already has the fix: `bgp_table_dump_write`
259/// includes the 4-byte View Number and Sequence Number in the declared length.
260/// The fixture `bview.20000111.0032.gz` omits those four bytes from the length.
261///
262/// [original MRT writer]: https://fossies.org/linux/misc/old/mrt-2.2.2a-src.tar.gz/mrt-2.2.2a/src/lib/bgp_proto/bgp_dump2.c
263pub(crate) fn needs_legacy_length_correction(sub_type: u16, data: &[u8]) -> bool {
264    let address_len = match sub_type {
265        1 => 4usize,
266        2 => 16usize,
267        _ => return false,
268    };
269    if data.len() < 4 {
270        return false;
271    }
272
273    // Prefix, prefix length, status, originated time, peer IP, peer ASN,
274    // and the attribute-length field.
275    let fixed_entry_len = address_len * 2 + 10;
276    let mut offset = 4usize;
277    while offset < data.len() {
278        let Some(attr_len_offset) = offset.checked_add(fixed_entry_len - 2) else {
279            return false;
280        };
281        if attr_len_offset + 2 > data.len() {
282            return false;
283        }
284        let attr_len =
285            u16::from_be_bytes([data[attr_len_offset], data[attr_len_offset + 1]]) as usize;
286        let Some(entry_end) = offset
287            .checked_add(fixed_entry_len)
288            .and_then(|value| value.checked_add(attr_len))
289        else {
290            return false;
291        };
292        if entry_end == data.len() + 4 {
293            return true;
294        }
295        if entry_end <= offset || entry_end > data.len() {
296            return false;
297        }
298        offset = entry_end;
299    }
300    false
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306    use bytes::{BufMut, BytesMut};
307    use std::net::{Ipv4Addr, Ipv6Addr};
308
309    const VIEW_NUMBER: u16 = 0;
310    const SEQUENCE_NUMBER: u16 = 0;
311    const IPV4_PREFIX: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
312    const IPV6_PREFIX: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
313    const PREFIX_LEN: u8 = 0;
314    const STATUS: u8 = 0;
315    const TIME: u64 = 0;
316    const PEER_IPV4: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
317    const PEER_IPV6: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
318    const PEER_ASN_16BIT: u16 = 0;
319    const ATTRIBUTE_LENGTH: usize = 0;
320    const DUMMY_ATTRIBUTES: &[u8] = &[];
321
322    #[test]
323    fn test_parse_table_dump_message_ipv4() {
324        let mut bytes_mut = BytesMut::new();
325        // Populate the bytes_mut with the same sequence that parse_table_dump_message() expects to parse
326        bytes_mut.put_u16(VIEW_NUMBER);
327        bytes_mut.put_u16(SEQUENCE_NUMBER);
328        bytes_mut.put_u32(IPV4_PREFIX.into());
329        bytes_mut.put_u8(PREFIX_LEN);
330        bytes_mut.put_u8(STATUS);
331        bytes_mut.put_u32(TIME as u32);
332        bytes_mut.put_u32(PEER_IPV4.into());
333        bytes_mut.put_u16(PEER_ASN_16BIT);
334        bytes_mut.put_u16(ATTRIBUTE_LENGTH as u16);
335        bytes_mut.put_slice(DUMMY_ATTRIBUTES);
336
337        // Convert from BytesMut to Bytes
338        let bytes = bytes_mut.freeze();
339
340        let table_dump_message_res = parse_table_dump_message(1, bytes.clone());
341        assert!(
342            table_dump_message_res.is_ok(),
343            "Failed to parse TABLE_DUMP_V1 message"
344        );
345
346        let table_dump_message = table_dump_message_res.unwrap();
347        assert_eq!(
348            table_dump_message.view_number, VIEW_NUMBER,
349            "VIEW_NUMBER mismatch"
350        );
351        assert_eq!(
352            table_dump_message.sequence_number, SEQUENCE_NUMBER,
353            "SEQUENCE_NUMBER mismatch"
354        );
355        // Add more assertions here as per your actual requirements
356        let encoded = table_dump_message.encode().unwrap();
357        assert_eq!(encoded, bytes);
358    }
359    #[test]
360    fn test_parse_table_dump_message_ipv6() {
361        let mut bytes_mut = BytesMut::new();
362        // Populate the bytes_mut with the same sequence that parse_table_dump_message() expects to parse
363        bytes_mut.put_u16(VIEW_NUMBER);
364        bytes_mut.put_u16(SEQUENCE_NUMBER);
365        bytes_mut.put_u128(IPV6_PREFIX.into());
366        bytes_mut.put_u8(PREFIX_LEN);
367        bytes_mut.put_u8(STATUS);
368        bytes_mut.put_u32(TIME as u32);
369        bytes_mut.put_u128(PEER_IPV6.into());
370        bytes_mut.put_u16(PEER_ASN_16BIT);
371        bytes_mut.put_u16(ATTRIBUTE_LENGTH as u16);
372        bytes_mut.put_slice(DUMMY_ATTRIBUTES);
373
374        // Convert from BytesMut to Bytes
375        let bytes = bytes_mut.freeze();
376
377        let table_dump_message_res = parse_table_dump_message(2, bytes.clone());
378        assert!(
379            table_dump_message_res.is_ok(),
380            "Failed to parse TABLE_DUMP_V1 message"
381        );
382
383        let table_dump_message = table_dump_message_res.unwrap();
384        assert_eq!(
385            table_dump_message.view_number, VIEW_NUMBER,
386            "VIEW_NUMBER mismatch"
387        );
388        assert_eq!(
389            table_dump_message.sequence_number, SEQUENCE_NUMBER,
390            "SEQUENCE_NUMBER mismatch"
391        );
392        // Add more assertions here as per your actual requirements
393
394        // test encoding
395        let encoded = table_dump_message.encode().unwrap();
396        assert_eq!(encoded, bytes);
397    }
398
399    #[test]
400    fn test_parse_table_dump_message_invalid_subtype() {
401        // Create a simple byte array for testing
402        let mut bytes_mut = BytesMut::new();
403        bytes_mut.put_u16(VIEW_NUMBER);
404        bytes_mut.put_u16(SEQUENCE_NUMBER);
405        let bytes = bytes_mut.freeze();
406
407        // Test with an invalid sub_type (not 1 or 2)
408        let result = parse_table_dump_message(0, bytes.clone());
409        assert!(result.is_err(), "Expected error for invalid sub_type");
410
411        if let Err(ParserError::ParseError(msg)) = result {
412            assert!(
413                msg.contains("Invalid subtype"),
414                "Expected error message to mention invalid subtype"
415            );
416        } else {
417            panic!("Expected ParseError for invalid sub_type");
418        }
419
420        // Test with another invalid sub_type
421        let result = parse_table_dump_message(3, bytes);
422        assert!(result.is_err(), "Expected error for invalid sub_type");
423
424        if let Err(ParserError::ParseError(msg)) = result {
425            assert!(
426                msg.contains("Invalid subtype"),
427                "Expected error message to mention invalid subtype"
428            );
429        } else {
430            panic!("Expected ParseError for invalid sub_type");
431        }
432    }
433
434    #[test]
435    fn test_table_dump_message_encode_with_attributes() {
436        use crate::models::{Asn, AttributeValue, Attributes, Origin};
437        use std::str::FromStr;
438
439        let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
440        let mut attributes = Attributes::default();
441        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
442
443        let table_dump = TableDumpMessage {
444            view_number: 1,
445            sequence_number: 2,
446            prefix: NetworkPrefix::new(prefix, None),
447            status: 1,
448            originated_time: 12345,
449            peer_ip: IpAddr::V4("10.0.0.1".parse().unwrap()),
450            peer_asn: Asn::from(65000),
451            attributes,
452        };
453
454        // This should exercise the attr.encode(AsnLength::Bits16).unwrap() line
455        let _encoded = table_dump.encode().unwrap();
456    }
457
458    #[test]
459    fn parses_and_encodes_batched_table_dump_messages() {
460        use crate::models::{AttributeValue, Origin};
461        use std::str::FromStr;
462
463        let mut attributes = Attributes::default();
464        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
465        let first = TableDumpMessage {
466            view_number: 7,
467            sequence_number: 9,
468            prefix: NetworkPrefix::from_str("192.0.2.0/24").unwrap(),
469            status: 1,
470            originated_time: 12345,
471            peer_ip: IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)),
472            peer_asn: Asn::new_16bit(64512),
473            attributes: attributes.clone(),
474        };
475        let second = TableDumpMessage {
476            prefix: NetworkPrefix::from_str("198.51.100.0/24").unwrap(),
477            ..first.clone()
478        };
479
480        let wire = encode_table_dump_batch(&[first.clone(), second.clone()], 1).unwrap();
481        let parsed = parse_table_dump_messages(1, wire.clone()).unwrap();
482        assert_eq!(parsed.len(), 2);
483        assert_eq!(parsed[0].prefix, first.prefix);
484        assert_eq!(parsed[1].prefix, second.prefix);
485        assert!(!needs_legacy_length_correction(1, &wire));
486        assert!(needs_legacy_length_correction(1, &wire[..wire.len() - 4]));
487        assert!(!needs_legacy_length_correction(1, &wire[..wire.len() - 3]));
488    }
489}