1use crate::error::{BgpValidationWarning, ParserError};
4use crate::models::*;
5use crate::parser::mrt::mrt_record::{chunk_mrt_record_with_context, raw_record_uses_zebra_compat};
6use crate::parser::mrt::RawMrtRecord;
7use crate::parser::BgpkitParser;
8use std::io::Read;
9
10#[derive(Debug)]
12#[non_exhaustive]
13pub enum DiagnosticEvent {
14 Record(MrtRecord),
16 Validation {
18 record: MrtRecord,
20 warnings: Vec<BgpValidationWarning>,
22 raw_record: RawMrtRecord,
24 },
25 ParseError {
27 error: ParserError,
29 common_header: Option<CommonHeader>,
31 raw_bytes: Option<Vec<u8>>,
33 },
34}
35
36pub struct DiagnosticIterator<R> {
38 parser: BgpkitParser<R>,
39 terminated: bool,
40}
41
42impl<R> DiagnosticIterator<R> {
43 pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
44 Self {
45 parser,
46 terminated: false,
47 }
48 }
49}
50
51impl<R: Read> Iterator for DiagnosticIterator<R> {
52 type Item = DiagnosticEvent;
53
54 fn next(&mut self) -> Option<Self::Item> {
55 if self.terminated || self.parser.text_dump_iter.is_some() {
57 return None;
58 }
59
60 let raw_record = match chunk_mrt_record_with_context(&mut self.parser.reader) {
61 Ok(raw_record) => raw_record,
62 Err(error) if matches!(error.error, ParserError::EofExpected) => {
63 self.terminated = true;
64 return None;
65 }
66 Err(error) => {
67 self.terminated = true;
70 return Some(DiagnosticEvent::ParseError {
71 error: error.error,
72 common_header: error.common_header,
73 raw_bytes: error.bytes,
74 });
75 }
76 };
77
78 let used_zebra_compat = raw_record_uses_zebra_compat(&raw_record);
79 let record = match raw_record.clone().parse() {
80 Ok(record) => record,
81 Err(error) => {
82 return Some(DiagnosticEvent::ParseError {
83 error,
84 common_header: Some(raw_record.common_header),
85 raw_bytes: Some(raw_record.raw_bytes().to_vec()),
86 });
87 }
88 };
89 if used_zebra_compat {
90 self.parser.warn_zebra_compat_once();
91 }
92
93 let warnings = record_validation_warnings(&record);
94 if warnings.is_empty() {
95 Some(DiagnosticEvent::Record(record))
96 } else {
97 Some(DiagnosticEvent::Validation {
98 record,
99 warnings,
100 raw_record,
101 })
102 }
103 }
104}
105
106fn record_validation_warnings(record: &MrtRecord) -> Vec<BgpValidationWarning> {
107 match &record.message {
108 MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(message)) => update_warnings(&message.bgp_message),
109 MrtMessage::LegacyBgp(LegacyBgp::Message(message)) => update_warnings(&message.bgp_message),
110 MrtMessage::TableDumpMessage(message) => message.attributes.validation_warnings().to_vec(),
111 MrtMessage::TableDumpMessageBatch(messages) => messages
112 .iter()
113 .flat_map(|message| message.attributes.validation_warnings().iter().cloned())
114 .collect(),
115 MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(rib)) => rib
116 .rib_entries
117 .iter()
118 .flat_map(|entry| entry.attributes.validation_warnings().iter().cloned())
119 .collect(),
120 _ => Vec::new(),
121 }
122}
123
124fn update_warnings(message: &BgpMessage) -> Vec<BgpValidationWarning> {
125 match message {
126 BgpMessage::Update(update) => update.attributes.validation_warnings().to_vec(),
127 BgpMessage::RouteRefresh(refresh) => refresh.validation_warnings(),
128 _ => Vec::new(),
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use std::io::Cursor;
136 use std::net::IpAddr;
137 use std::str::FromStr;
138
139 fn update_record(attributes: Attributes) -> MrtRecord {
140 MrtRecord {
141 common_header: CommonHeader {
142 timestamp: 1_700_000_000,
143 microsecond_timestamp: None,
144 entry_type: EntryType::BGP4MP,
145 entry_subtype: Bgp4MpType::MessageAs4 as u16,
146 length: 0,
147 },
148 message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
149 msg_type: Bgp4MpType::MessageAs4,
150 peer_asn: Asn::new_32bit(64496),
151 local_asn: Asn::new_32bit(64497),
152 interface_index: 0,
153 peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
154 local_ip: IpAddr::from_str("192.0.2.2").unwrap(),
155 bgp_message: BgpMessage::Update(BgpUpdateMessage {
156 withdrawn_prefixes: vec![],
157 attributes,
158 announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
159 }),
160 })),
161 }
162 }
163
164 fn valid_attributes() -> Attributes {
165 let mut attributes = Attributes::default();
166 attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
167 attributes.add_attr(
168 AttributeValue::AsPath {
169 path: AsPath::from_sequence([64500]),
170 is_as4: false,
171 }
172 .into(),
173 );
174 attributes
175 .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
176 attributes
177 }
178
179 fn update_wire_body(withdrawn: &[u8], attributes: &[u8], announced: &[u8]) -> Vec<u8> {
180 let mut body = Vec::new();
181 body.extend_from_slice(&(withdrawn.len() as u16).to_be_bytes());
182 body.extend_from_slice(withdrawn);
183 body.extend_from_slice(&(attributes.len() as u16).to_be_bytes());
184 body.extend_from_slice(attributes);
185 body.extend_from_slice(announced);
186 body
187 }
188
189 fn bgp4mp_update_wire(withdrawn: &[u8], attributes: &[u8], announced: &[u8]) -> Vec<u8> {
190 let update_body = update_wire_body(withdrawn, attributes, announced);
191 bgp4mp_message_wire(BgpMessageType::UPDATE, &update_body)
192 }
193
194 fn bgp4mp_route_refresh_wire(subtype: u8, orf_data: &[u8]) -> Vec<u8> {
195 let mut refresh_body = vec![0x00, 0x01, subtype, 0x01];
196 refresh_body.extend_from_slice(orf_data);
197 bgp4mp_message_wire(BgpMessageType::ROUTE_REFRESH, &refresh_body)
198 }
199
200 fn bgp4mp_message_wire(msg_type: BgpMessageType, msg_body: &[u8]) -> Vec<u8> {
201 let mut bgp_message = vec![0xff; 16];
202 bgp_message.extend_from_slice(&((19 + msg_body.len()) as u16).to_be_bytes());
203 bgp_message.push(msg_type as u8);
204 bgp_message.extend_from_slice(msg_body);
205
206 let mut body = Vec::new();
207 body.extend_from_slice(&64496u32.to_be_bytes());
208 body.extend_from_slice(&64497u32.to_be_bytes());
209 body.extend_from_slice(&0u16.to_be_bytes());
210 body.extend_from_slice(&1u16.to_be_bytes());
211 body.extend_from_slice(&[192, 0, 2, 1]);
212 body.extend_from_slice(&[192, 0, 2, 2]);
213 body.extend_from_slice(&bgp_message);
214
215 let header = CommonHeader {
216 timestamp: 1_700_000_000,
217 microsecond_timestamp: None,
218 entry_type: EntryType::BGP4MP,
219 entry_subtype: Bgp4MpType::MessageAs4 as u16,
220 length: body.len() as u32,
221 };
222 let mut wire = header.encode().to_vec();
223 wire.extend_from_slice(&body);
224 wire
225 }
226
227 fn valid_update_attributes_wire() -> Vec<u8> {
228 vec![
229 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x04, 192, 0, 2, 254, ]
233 }
234
235 fn assert_wire_validation(
236 wire: Vec<u8>,
237 expected_warning: impl Fn(&BgpValidationWarning) -> bool,
238 ) {
239 let mut iter = BgpkitParser::from_reader(Cursor::new(wire.clone())).into_diagnostic_iter();
240 match iter.next().unwrap() {
241 DiagnosticEvent::Validation {
242 warnings,
243 raw_record,
244 ..
245 } => {
246 assert!(
247 warnings.iter().any(expected_warning),
248 "expected validation warning, got {warnings:?}"
249 );
250 assert_eq!(raw_record.raw_bytes().as_ref(), wire.as_slice());
251 }
252 event => panic!("expected validation event, got {event:?}"),
253 }
254 assert!(iter.next().is_none());
255 }
256
257 #[test]
258 fn diagnostic_iterator_flags_unknown_route_refresh_subtype() {
259 let wire = bgp4mp_route_refresh_wire(3, &[]);
261 assert_wire_validation(wire, |warning| {
262 matches!(
263 warning,
264 BgpValidationWarning::UnknownRouteRefreshSubtype { subtype: 3 }
265 )
266 });
267 }
268
269 #[test]
270 fn diagnostic_iterator_flags_borr_with_trailing_data() {
271 let wire = bgp4mp_route_refresh_wire(1, &[0xDE, 0xAD]);
273 assert_wire_validation(wire, |warning| {
274 matches!(
275 warning,
276 BgpValidationWarning::InvalidRouteRefreshLength {
277 subtype: 1,
278 length: 6
279 }
280 )
281 });
282 }
283
284 #[test]
285 fn diagnostic_iterator_passes_normal_route_refresh_with_orf_data_clean() {
286 let wire = bgp4mp_route_refresh_wire(0, &[0x01, 0x80, 0x00, 0x00]);
288 let mut iter = BgpkitParser::from_reader(Cursor::new(wire)).into_diagnostic_iter();
289 match iter.next().unwrap() {
290 DiagnosticEvent::Record(record) => {
291 assert!(matches!(
292 record.message,
293 MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
294 bgp_message: BgpMessage::RouteRefresh(_),
295 ..
296 }))
297 ));
298 }
299 event => panic!("expected clean record event, got {event:?}"),
300 }
301 assert!(iter.next().is_none());
302 }
303
304 #[test]
305 fn diagnostic_iterator_yields_clean_record() {
306 let wire = update_record(valid_attributes()).encode().unwrap().to_vec();
307 let mut iter = BgpkitParser::from_reader(Cursor::new(wire)).into_diagnostic_iter();
308
309 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record(_))));
310 assert!(iter.next().is_none());
311 }
312
313 #[test]
314 fn diagnostic_iterator_yields_validation_with_original_bytes() {
315 let wire = update_record(Attributes::default())
316 .encode()
317 .unwrap()
318 .to_vec();
319 let mut iter = BgpkitParser::from_reader(Cursor::new(wire.clone())).into_diagnostic_iter();
320
321 match iter.next().unwrap() {
322 DiagnosticEvent::Validation {
323 record,
324 warnings,
325 raw_record,
326 } => {
327 assert!(matches!(record.message, MrtMessage::Bgp4Mp(_)));
328 assert!(warnings.iter().any(|warning| {
329 matches!(
330 warning,
331 BgpValidationWarning::MissingWellKnownAttribute {
332 attr_type: AttrType::ORIGIN
333 }
334 )
335 }));
336 assert_eq!(raw_record.raw_bytes().as_ref(), wire.as_slice());
337 }
338 event => panic!("expected validation event, got {event:?}"),
339 }
340 }
341
342 #[test]
343 fn diagnostic_iterator_preserves_body_error_and_continues() {
344 let header = CommonHeader {
345 timestamp: 2,
346 microsecond_timestamp: None,
347 entry_type: EntryType::TABLE_DUMP,
348 entry_subtype: 0,
349 length: 4,
350 };
351 let mut input = header.encode().to_vec();
352 input.extend_from_slice(&[0xff; 4]);
353 let invalid_record = input.clone();
354 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
355
356 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
357 match iter.next().unwrap() {
358 DiagnosticEvent::ParseError {
359 common_header,
360 raw_bytes,
361 ..
362 } => {
363 assert_eq!(common_header, Some(header));
364 assert_eq!(raw_bytes.as_deref(), Some(invalid_record.as_slice()));
365 }
366 event => panic!("expected parse error event, got {event:?}"),
367 }
368 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record(_))));
369 assert!(iter.next().is_none());
370 }
371
372 #[test]
373 fn diagnostic_iterator_preserves_partial_body_context() {
374 let header = CommonHeader {
375 timestamp: 3,
376 microsecond_timestamp: None,
377 entry_type: EntryType::BGP4MP,
378 entry_subtype: 0,
379 length: 4,
380 };
381 let mut input = header.encode().to_vec();
382 input.extend_from_slice(&[0, 1]);
383
384 match BgpkitParser::from_reader(Cursor::new(input.clone()))
385 .into_diagnostic_iter()
386 .next()
387 .unwrap()
388 {
389 DiagnosticEvent::ParseError {
390 common_header,
391 raw_bytes,
392 ..
393 } => {
394 assert_eq!(common_header, Some(header));
395 assert_eq!(raw_bytes.as_deref(), Some(input.as_slice()));
396 }
397 event => panic!("expected parse error event, got {event:?}"),
398 }
399 }
400
401 #[test]
402 fn diagnostic_iterator_terminates_after_framing_error() {
403 let header = CommonHeader {
404 timestamp: 3,
405 microsecond_timestamp: None,
406 entry_type: EntryType::BGP4MP,
407 entry_subtype: Bgp4MpType::MessageAs4 as u16,
408 length: 16 * 1024 * 1024 + 1,
409 };
410 let mut input = header.encode().to_vec();
411 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
412
413 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
414 match iter.next().unwrap() {
415 DiagnosticEvent::ParseError {
416 common_header,
417 raw_bytes,
418 ..
419 } => {
420 assert_eq!(common_header, Some(header));
421 assert_eq!(raw_bytes.as_deref(), Some(header.encode().as_ref()));
422 }
423 event => panic!("expected parse error event, got {event:?}"),
424 }
425 assert!(iter.next().is_none());
426 }
427
428 #[test]
429 fn diagnostic_iterator_preserves_invalid_header_bytes() {
430 let invalid_header = vec![
431 0, 0, 0, 1, 0xff, 0xff, 0, 0, 0, 0, 0, 0, ];
436
437 match BgpkitParser::from_reader(Cursor::new(invalid_header.clone()))
438 .into_diagnostic_iter()
439 .next()
440 .unwrap()
441 {
442 DiagnosticEvent::ParseError {
443 common_header,
444 raw_bytes,
445 ..
446 } => {
447 assert!(common_header.is_none());
448 assert_eq!(raw_bytes.as_deref(), Some(invalid_header.as_slice()));
449 }
450 event => panic!("expected parse error event, got {event:?}"),
451 }
452 }
453
454 #[test]
455 fn diagnostic_iterator_ignores_filters() {
456 let wire = update_record(valid_attributes()).encode().unwrap().to_vec();
457 let parser = BgpkitParser::from_reader(Cursor::new(wire))
458 .add_filter("prefix", "198.51.100.0/24")
459 .unwrap();
460
461 assert!(matches!(
462 parser.into_diagnostic_iter().next(),
463 Some(DiagnosticEvent::Record(_))
464 ));
465 }
466
467 #[test]
468 fn diagnostic_iterator_reports_wire_level_nlri_warnings_and_continues() {
469 let malformed_nlri = [0xc8, 0x01];
470 let invalid_wire = bgp4mp_update_wire(
471 &malformed_nlri,
472 &valid_update_attributes_wire(),
473 &malformed_nlri,
474 );
475 let mut input = invalid_wire.clone();
476 input.extend_from_slice(&update_record(valid_attributes()).encode().unwrap());
477
478 let mut iter = BgpkitParser::from_reader(Cursor::new(input)).into_diagnostic_iter();
479 match iter.next().unwrap() {
480 DiagnosticEvent::Validation {
481 warnings,
482 raw_record,
483 ..
484 } => {
485 assert!(warnings.iter().any(|warning| {
486 matches!(
487 warning,
488 BgpValidationWarning::MalformedNlri {
489 nlri_type: "withdrawn",
490 raw_bytes,
491 ..
492 } if raw_bytes == &malformed_nlri
493 )
494 }));
495 assert!(warnings.iter().any(|warning| {
496 matches!(
497 warning,
498 BgpValidationWarning::MalformedNlri {
499 nlri_type: "announced",
500 raw_bytes,
501 ..
502 } if raw_bytes == &malformed_nlri
503 )
504 }));
505 assert_eq!(raw_record.raw_bytes().as_ref(), invalid_wire.as_slice());
506 }
507 event => panic!("expected validation event, got {event:?}"),
508 }
509 assert!(matches!(iter.next(), Some(DiagnosticEvent::Record(_))));
510 assert!(iter.next().is_none());
511 }
512
513 #[test]
514 fn diagnostic_iterator_reports_wire_level_attribute_warnings() {
515 let announced = [0x18, 10, 0, 0];
516 let mut flags_and_duplicate = valid_update_attributes_wire();
517 flags_and_duplicate[0] = 0x80;
518 flags_and_duplicate.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
519 assert_wire_validation(
520 bgp4mp_update_wire(&[], &flags_and_duplicate, &announced),
521 |warning| {
522 matches!(
523 warning,
524 BgpValidationWarning::AttributeFlagsError {
525 attr_type: AttrType::ORIGIN,
526 ..
527 }
528 )
529 },
530 );
531 assert_wire_validation(
532 bgp4mp_update_wire(&[], &flags_and_duplicate, &announced),
533 |warning| {
534 matches!(
535 warning,
536 BgpValidationWarning::DuplicateAttribute {
537 attr_type: AttrType::ORIGIN
538 }
539 )
540 },
541 );
542
543 let mut invalid_origin = valid_update_attributes_wire();
544 invalid_origin[3] = 3;
545 assert_wire_validation(
546 bgp4mp_update_wire(&[], &invalid_origin, &announced),
547 |warning| matches!(warning, BgpValidationWarning::MalformedAttributeList { .. }),
548 );
549
550 let malformed_as_path = vec![
551 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x02, 0x02, 0x01, 0x40, 0x03, 0x04, 192, 0, 2, 254, ];
555 assert_wire_validation(
556 bgp4mp_update_wire(&[], &malformed_as_path, &announced),
557 |warning| matches!(warning, BgpValidationWarning::MalformedAttributeList { .. }),
558 );
559
560 let invalid_next_hop = vec![
561 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x03, 192, 0, 2, ];
565 assert_wire_validation(
566 bgp4mp_update_wire(&[], &invalid_next_hop, &announced),
567 |warning| {
568 matches!(
569 warning,
570 BgpValidationWarning::AttributeLengthError {
571 attr_type: AttrType::NEXT_HOP,
572 ..
573 }
574 )
575 },
576 );
577 }
578
579 #[test]
580 fn diagnostic_iterator_collects_table_dump_batch_and_rib_warnings() {
581 let table_dump_messages = vec![
582 TableDumpMessage {
583 view_number: 0,
584 sequence_number: 1,
585 prefix: NetworkPrefix::from_str("192.0.2.0/24").unwrap(),
586 status: 1,
587 originated_time: 1,
588 peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
589 peer_asn: Asn::new_16bit(64512),
590 attributes: Attributes::default(),
591 },
592 TableDumpMessage {
593 view_number: 0,
594 sequence_number: 1,
595 prefix: NetworkPrefix::from_str("198.51.100.0/24").unwrap(),
596 status: 1,
597 originated_time: 2,
598 peer_ip: IpAddr::from_str("192.0.2.2").unwrap(),
599 peer_asn: Asn::new_16bit(64513),
600 attributes: Attributes::default(),
601 },
602 ];
603 let table_dump_record = MrtRecord {
604 common_header: CommonHeader {
605 timestamp: 1,
606 microsecond_timestamp: None,
607 entry_type: EntryType::TABLE_DUMP,
608 entry_subtype: 1,
609 length: 0,
610 },
611 message: MrtMessage::TableDumpMessageBatch(table_dump_messages),
612 };
613 let table_dump_wire = table_dump_record.encode().unwrap().to_vec();
614 match BgpkitParser::from_reader(Cursor::new(table_dump_wire.clone()))
615 .into_diagnostic_iter()
616 .next()
617 .unwrap()
618 {
619 DiagnosticEvent::Validation {
620 record,
621 warnings,
622 raw_record,
623 } => {
624 assert!(matches!(
625 record.message,
626 MrtMessage::TableDumpMessageBatch(_)
627 ));
628 assert_eq!(warnings.len(), 6);
629 assert_eq!(raw_record.raw_bytes().as_ref(), table_dump_wire.as_slice());
630 }
631 event => panic!("expected validation event, got {event:?}"),
632 }
633
634 let rib_record = MrtRecord {
635 common_header: CommonHeader {
636 timestamp: 2,
637 microsecond_timestamp: None,
638 entry_type: EntryType::TABLE_DUMP_V2,
639 entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
640 length: 0,
641 },
642 message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
643 rib_type: TableDumpV2Type::RibIpv4Unicast,
644 sequence_number: 1,
645 prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
646 rib_entries: vec![RibEntry {
647 peer_index: 0,
648 originated_time: 1,
649 path_id: None,
650 attributes: Attributes::default(),
651 }],
652 })),
653 };
654 let rib_wire = rib_record.encode().unwrap().to_vec();
655 match BgpkitParser::from_reader(Cursor::new(rib_wire.clone()))
656 .into_diagnostic_iter()
657 .next()
658 .unwrap()
659 {
660 DiagnosticEvent::Validation {
661 record,
662 warnings,
663 raw_record,
664 } => {
665 assert!(matches!(
666 record.message,
667 MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(_))
668 ));
669 assert_eq!(warnings.len(), 3);
670 assert_eq!(raw_record.raw_bytes().as_ref(), rib_wire.as_slice());
671 }
672 event => panic!("expected validation event, got {event:?}"),
673 }
674 }
675}