1use compact_str::CompactString;
12use smallvec::SmallVec;
13
14use super::{FieldValue, ParseContext, ParseResult, Protocol};
15use crate::schema::{DataKind, FieldDescriptor};
16
17pub const BGP_PORT: u16 = 179;
19
20pub mod message_type {
22 pub const OPEN: u8 = 1;
23 pub const UPDATE: u8 = 2;
24 pub const NOTIFICATION: u8 = 3;
25 pub const KEEPALIVE: u8 = 4;
26 pub const ROUTE_REFRESH: u8 = 5;
27}
28
29pub mod path_attr_type {
31 pub const ORIGIN: u8 = 1;
32 pub const AS_PATH: u8 = 2;
33 pub const NEXT_HOP: u8 = 3;
34 pub const MULTI_EXIT_DISC: u8 = 4;
35 pub const LOCAL_PREF: u8 = 5;
36 pub const ATOMIC_AGGREGATE: u8 = 6;
37 pub const AGGREGATOR: u8 = 7;
38 pub const COMMUNITIES: u8 = 8;
39 pub const MP_REACH_NLRI: u8 = 14;
40 pub const MP_UNREACH_NLRI: u8 = 15;
41}
42
43pub mod origin_type {
45 pub const IGP: u8 = 0;
46 pub const EGP: u8 = 1;
47 pub const INCOMPLETE: u8 = 2;
48}
49
50pub mod as_path_segment_type {
52 pub const AS_SET: u8 = 1;
53 pub const AS_SEQUENCE: u8 = 2;
54}
55
56pub mod error_code {
58 pub const MESSAGE_HEADER_ERROR: u8 = 1;
59 pub const OPEN_MESSAGE_ERROR: u8 = 2;
60 pub const UPDATE_MESSAGE_ERROR: u8 = 3;
61 pub const HOLD_TIMER_EXPIRED: u8 = 4;
62 pub const FSM_ERROR: u8 = 5;
63 pub const CEASE: u8 = 6;
64 pub const ROUTE_REFRESH_ERROR: u8 = 7;
65}
66
67pub mod capability_code {
69 pub const MULTIPROTOCOL: u8 = 1;
70 pub const ROUTE_REFRESH: u8 = 2;
71 pub const FOUR_OCTET_AS: u8 = 65;
72 pub const ADD_PATH: u8 = 69;
73 pub const ENHANCED_ROUTE_REFRESH: u8 = 70;
74}
75
76pub const AS_TRANS: u16 = 23456;
78
79const BGP_MARKER: [u8; 16] = [0xFF; 16];
81
82fn message_type_name(msg_type: u8) -> &'static str {
84 match msg_type {
85 message_type::OPEN => "OPEN",
86 message_type::UPDATE => "UPDATE",
87 message_type::NOTIFICATION => "NOTIFICATION",
88 message_type::KEEPALIVE => "KEEPALIVE",
89 message_type::ROUTE_REFRESH => "ROUTE-REFRESH",
90 _ => "UNKNOWN",
91 }
92}
93
94fn origin_name(origin: u8) -> &'static str {
96 match origin {
97 origin_type::IGP => "IGP",
98 origin_type::EGP => "EGP",
99 origin_type::INCOMPLETE => "INCOMPLETE",
100 _ => "UNKNOWN",
101 }
102}
103
104fn error_code_name(code: u8) -> &'static str {
106 match code {
107 error_code::MESSAGE_HEADER_ERROR => "Message Header Error",
108 error_code::OPEN_MESSAGE_ERROR => "OPEN Message Error",
109 error_code::UPDATE_MESSAGE_ERROR => "UPDATE Message Error",
110 error_code::HOLD_TIMER_EXPIRED => "Hold Timer Expired",
111 error_code::FSM_ERROR => "Finite State Machine Error",
112 error_code::CEASE => "Cease",
113 error_code::ROUTE_REFRESH_ERROR => "ROUTE-REFRESH Message Error",
114 _ => "Unknown",
115 }
116}
117
118#[allow(dead_code)]
120fn path_attr_type_name(type_code: u8) -> &'static str {
121 match type_code {
122 path_attr_type::ORIGIN => "ORIGIN",
123 path_attr_type::AS_PATH => "AS_PATH",
124 path_attr_type::NEXT_HOP => "NEXT_HOP",
125 path_attr_type::MULTI_EXIT_DISC => "MULTI_EXIT_DISC",
126 path_attr_type::LOCAL_PREF => "LOCAL_PREF",
127 path_attr_type::ATOMIC_AGGREGATE => "ATOMIC_AGGREGATE",
128 path_attr_type::AGGREGATOR => "AGGREGATOR",
129 path_attr_type::COMMUNITIES => "COMMUNITIES",
130 path_attr_type::MP_REACH_NLRI => "MP_REACH_NLRI",
131 path_attr_type::MP_UNREACH_NLRI => "MP_UNREACH_NLRI",
132 _ => "UNKNOWN",
133 }
134}
135
136#[derive(Debug, Clone, Copy)]
138pub struct BgpProtocol;
139
140impl Protocol for BgpProtocol {
141 fn name(&self) -> &'static str {
142 "bgp"
143 }
144
145 fn display_name(&self) -> &'static str {
146 "BGP"
147 }
148
149 fn can_parse(&self, context: &ParseContext) -> Option<u32> {
150 if let Some(dst_port) = context.hint("dst_port") {
152 if dst_port == BGP_PORT as u64 {
153 return Some(100);
154 }
155 }
156 if let Some(src_port) = context.hint("src_port") {
157 if src_port == BGP_PORT as u64 {
158 return Some(100);
159 }
160 }
161 None
162 }
163
164 fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
165 if data.len() < 19 {
167 return ParseResult::error("BGP header too short".to_string(), data);
168 }
169
170 let mut fields = SmallVec::new();
171
172 if data[0..16] != BGP_MARKER {
174 return ParseResult::error("BGP: invalid marker".to_string(), data);
175 }
176
177 let length = u16::from_be_bytes([data[16], data[17]]);
179 fields.push(("length", FieldValue::UInt16(length)));
180
181 if !(19..=4096).contains(&length) {
182 return ParseResult::error(format!("BGP: invalid length {length}"), data);
183 }
184
185 let msg_type = data[18];
187 fields.push(("message_type", FieldValue::UInt8(msg_type)));
188 fields.push((
189 "message_type_name",
190 FieldValue::Str(message_type_name(msg_type)),
191 ));
192
193 let message_data = if data.len() >= length as usize {
195 &data[19..length as usize]
196 } else {
197 &data[19..]
198 };
199
200 match msg_type {
201 message_type::OPEN => {
202 self.parse_open_message(message_data, &mut fields);
203 }
204 message_type::UPDATE => {
205 self.parse_update_message(message_data, &mut fields);
206 }
207 message_type::NOTIFICATION => {
208 self.parse_notification_message(message_data, &mut fields);
209 }
210 message_type::KEEPALIVE => {
211 }
213 message_type::ROUTE_REFRESH => {
214 self.parse_route_refresh_message(message_data, &mut fields);
215 }
216 _ => {
217 }
219 }
220
221 let consumed = std::cmp::min(length as usize, data.len());
223 ParseResult::success(fields, &data[consumed..], SmallVec::new())
224 }
225
226 fn schema_fields(&self) -> Vec<FieldDescriptor> {
227 vec![
228 FieldDescriptor::new("bgp.message_type", DataKind::UInt8).set_nullable(true),
230 FieldDescriptor::new("bgp.message_type_name", DataKind::String).set_nullable(true),
231 FieldDescriptor::new("bgp.length", DataKind::UInt16).set_nullable(true),
232 FieldDescriptor::new("bgp.version", DataKind::UInt8).set_nullable(true),
234 FieldDescriptor::new("bgp.my_as", DataKind::UInt16).set_nullable(true),
235 FieldDescriptor::new("bgp.my_as_4byte", DataKind::UInt32).set_nullable(true),
236 FieldDescriptor::new("bgp.hold_time", DataKind::UInt16).set_nullable(true),
237 FieldDescriptor::new("bgp.bgp_id", DataKind::String).set_nullable(true),
238 FieldDescriptor::new("bgp.capabilities", DataKind::String).set_nullable(true),
239 FieldDescriptor::new("bgp.withdrawn_routes_len", DataKind::UInt16).set_nullable(true),
241 FieldDescriptor::new("bgp.withdrawn_routes", DataKind::String).set_nullable(true),
242 FieldDescriptor::new("bgp.withdrawn_count", DataKind::UInt16).set_nullable(true),
243 FieldDescriptor::new("bgp.path_attr_len", DataKind::UInt16).set_nullable(true),
244 FieldDescriptor::new("bgp.origin", DataKind::UInt8).set_nullable(true),
245 FieldDescriptor::new("bgp.origin_name", DataKind::String).set_nullable(true),
246 FieldDescriptor::new("bgp.as_path", DataKind::String).set_nullable(true),
247 FieldDescriptor::new("bgp.as_path_length", DataKind::UInt16).set_nullable(true),
248 FieldDescriptor::new("bgp.next_hop", DataKind::String).set_nullable(true),
249 FieldDescriptor::new("bgp.med", DataKind::UInt32).set_nullable(true),
250 FieldDescriptor::new("bgp.local_pref", DataKind::UInt32).set_nullable(true),
251 FieldDescriptor::new("bgp.atomic_aggregate", DataKind::Bool).set_nullable(true),
252 FieldDescriptor::new("bgp.aggregator_as", DataKind::UInt32).set_nullable(true),
253 FieldDescriptor::new("bgp.aggregator_ip", DataKind::String).set_nullable(true),
254 FieldDescriptor::new("bgp.nlri", DataKind::String).set_nullable(true),
255 FieldDescriptor::new("bgp.nlri_count", DataKind::UInt16).set_nullable(true),
256 FieldDescriptor::new("bgp.error_code", DataKind::UInt8).set_nullable(true),
258 FieldDescriptor::new("bgp.error_code_name", DataKind::String).set_nullable(true),
259 FieldDescriptor::new("bgp.error_subcode", DataKind::UInt8).set_nullable(true),
260 FieldDescriptor::new("bgp.afi", DataKind::UInt16).set_nullable(true),
262 FieldDescriptor::new("bgp.safi", DataKind::UInt8).set_nullable(true),
263 ]
264 }
265
266 fn child_protocols(&self) -> &[&'static str] {
267 &[]
268 }
269
270 fn dependencies(&self) -> &'static [&'static str] {
271 &["tcp"]
272 }
273}
274
275impl BgpProtocol {
276 fn parse_open_message(
297 &self,
298 data: &[u8],
299 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
300 ) {
301 if data.len() < 10 {
302 return;
303 }
304
305 let version = data[0];
307 fields.push(("version", FieldValue::UInt8(version)));
308
309 let my_as = u16::from_be_bytes([data[1], data[2]]);
311 fields.push(("my_as", FieldValue::UInt16(my_as)));
312
313 let hold_time = u16::from_be_bytes([data[3], data[4]]);
315 fields.push(("hold_time", FieldValue::UInt16(hold_time)));
316
317 let bgp_id = format!("{}.{}.{}.{}", data[5], data[6], data[7], data[8]);
319 fields.push((
320 "bgp_id",
321 FieldValue::OwnedString(CompactString::new(bgp_id)),
322 ));
323
324 let opt_params_len = data[9] as usize;
326 if data.len() < 10 + opt_params_len {
327 return;
328 }
329
330 let opt_params = &data[10..10 + opt_params_len];
332 self.parse_open_optional_params(opt_params, fields, my_as);
333 }
334
335 fn parse_open_optional_params(
337 &self,
338 data: &[u8],
339 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
340 my_as_2byte: u16,
341 ) {
342 let mut offset = 0;
343 let mut capabilities = Vec::with_capacity(4);
345 let mut four_byte_asn: Option<u32> = None;
346
347 while offset + 2 <= data.len() {
348 let param_type = data[offset];
349 let param_len = data[offset + 1] as usize;
350 offset += 2;
351
352 if offset + param_len > data.len() {
353 break;
354 }
355
356 if param_type == 2 {
358 let cap_data = &data[offset..offset + param_len];
360 let mut cap_offset = 0;
361
362 while cap_offset + 2 <= cap_data.len() {
363 let cap_code = cap_data[cap_offset];
364 let cap_len = cap_data[cap_offset + 1] as usize;
365 cap_offset += 2;
366
367 if cap_offset + cap_len > cap_data.len() {
368 break;
369 }
370
371 let cap_name = match cap_code {
373 capability_code::MULTIPROTOCOL => "MULTIPROTOCOL",
374 capability_code::ROUTE_REFRESH => "ROUTE_REFRESH",
375 capability_code::FOUR_OCTET_AS => "4-BYTE-AS",
376 capability_code::ADD_PATH => "ADD_PATH",
377 capability_code::ENHANCED_ROUTE_REFRESH => "ENHANCED_ROUTE_REFRESH",
378 _ => "UNKNOWN",
379 };
380 capabilities.push(cap_name);
381
382 if cap_code == capability_code::FOUR_OCTET_AS && cap_len == 4 {
384 let asn = u32::from_be_bytes([
385 cap_data[cap_offset],
386 cap_data[cap_offset + 1],
387 cap_data[cap_offset + 2],
388 cap_data[cap_offset + 3],
389 ]);
390 four_byte_asn = Some(asn);
391 }
392
393 cap_offset += cap_len;
394 }
395 }
396
397 offset += param_len;
398 }
399
400 if !capabilities.is_empty() {
401 fields.push((
402 "capabilities",
403 FieldValue::OwnedString(CompactString::new(capabilities.join(","))),
404 ));
405 }
406
407 if let Some(asn) = four_byte_asn {
410 fields.push(("my_as_4byte", FieldValue::UInt32(asn)));
411 } else if my_as_2byte != AS_TRANS {
412 fields.push(("my_as_4byte", FieldValue::UInt32(my_as_2byte as u32)));
414 }
415 }
416
417 fn parse_update_message(
436 &self,
437 data: &[u8],
438 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
439 ) {
440 if data.len() < 4 {
441 return;
442 }
443
444 let mut offset = 0;
445
446 let withdrawn_routes_len = u16::from_be_bytes([data[0], data[1]]) as usize;
448 fields.push((
449 "withdrawn_routes_len",
450 FieldValue::UInt16(withdrawn_routes_len as u16),
451 ));
452 offset += 2;
453
454 if data.len() < offset + withdrawn_routes_len {
456 return;
457 }
458 let withdrawn_data = &data[offset..offset + withdrawn_routes_len];
459 let withdrawn_prefixes = self.parse_nlri_prefixes(withdrawn_data);
460 if !withdrawn_prefixes.is_empty() {
461 fields.push((
462 "withdrawn_routes",
463 FieldValue::OwnedString(CompactString::new(withdrawn_prefixes.join(","))),
464 ));
465 fields.push((
466 "withdrawn_count",
467 FieldValue::UInt16(withdrawn_prefixes.len() as u16),
468 ));
469 }
470 offset += withdrawn_routes_len;
471
472 if data.len() < offset + 2 {
474 return;
475 }
476
477 let path_attr_len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
479 fields.push(("path_attr_len", FieldValue::UInt16(path_attr_len as u16)));
480 offset += 2;
481
482 if data.len() < offset + path_attr_len {
484 return;
485 }
486 let path_attr_data = &data[offset..offset + path_attr_len];
487 self.parse_path_attributes(path_attr_data, fields);
488 offset += path_attr_len;
489
490 if offset < data.len() {
492 let nlri_data = &data[offset..];
493 let nlri_prefixes = self.parse_nlri_prefixes(nlri_data);
494 if !nlri_prefixes.is_empty() {
495 fields.push((
496 "nlri",
497 FieldValue::OwnedString(CompactString::new(nlri_prefixes.join(","))),
498 ));
499 fields.push(("nlri_count", FieldValue::UInt16(nlri_prefixes.len() as u16)));
500 }
501 }
502 }
503
504 fn parse_nlri_prefixes(&self, data: &[u8]) -> Vec<String> {
507 let mut prefixes = Vec::with_capacity(8);
509 let mut offset = 0;
510
511 while offset < data.len() {
512 let prefix_len_bits = data[offset] as usize;
513 offset += 1;
514
515 let prefix_bytes = prefix_len_bits.div_ceil(8);
517 if offset + prefix_bytes > data.len() {
518 break;
519 }
520
521 let mut prefix = [0u8; 4];
523 let copy_len = prefix_bytes.min(4);
524 prefix[..copy_len].copy_from_slice(&data[offset..offset + copy_len]);
525
526 let prefix_str = format!(
527 "{}.{}.{}.{}/{}",
528 prefix[0], prefix[1], prefix[2], prefix[3], prefix_len_bits
529 );
530 prefixes.push(prefix_str);
531
532 offset += prefix_bytes;
533 }
534
535 prefixes
536 }
537
538 fn parse_path_attributes(
540 &self,
541 data: &[u8],
542 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
543 ) {
544 let mut offset = 0;
545
546 while offset + 3 <= data.len() {
547 let flags = data[offset];
549 let optional = (flags & 0x80) != 0;
550 let transitive = (flags & 0x40) != 0;
551 let partial = (flags & 0x20) != 0;
552 let extended_length = (flags & 0x10) != 0;
553 let _ = (optional, transitive, partial); let type_code = data[offset + 1];
557 offset += 2;
558
559 let attr_len = if extended_length {
561 if offset + 2 > data.len() {
562 break;
563 }
564 let len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
565 offset += 2;
566 len
567 } else {
568 if offset >= data.len() {
569 break;
570 }
571 let len = data[offset] as usize;
572 offset += 1;
573 len
574 };
575
576 if offset + attr_len > data.len() {
577 break;
578 }
579
580 let attr_data = &data[offset..offset + attr_len];
581
582 match type_code {
584 path_attr_type::ORIGIN => {
585 if !attr_data.is_empty() {
586 let origin = attr_data[0];
587 fields.push(("origin", FieldValue::UInt8(origin)));
588 fields.push(("origin_name", FieldValue::Str(origin_name(origin))));
589 }
590 }
591 path_attr_type::AS_PATH => {
592 let (as_path_str, path_length) = self.parse_as_path(attr_data);
593 fields.push((
594 "as_path",
595 FieldValue::OwnedString(CompactString::new(as_path_str)),
596 ));
597 fields.push(("as_path_length", FieldValue::UInt16(path_length)));
598 }
599 path_attr_type::NEXT_HOP => {
600 if attr_data.len() >= 4 {
601 let next_hop = format!(
602 "{}.{}.{}.{}",
603 attr_data[0], attr_data[1], attr_data[2], attr_data[3]
604 );
605 fields.push((
606 "next_hop",
607 FieldValue::OwnedString(CompactString::new(next_hop)),
608 ));
609 }
610 }
611 path_attr_type::MULTI_EXIT_DISC => {
612 if attr_data.len() >= 4 {
613 let med = u32::from_be_bytes([
614 attr_data[0],
615 attr_data[1],
616 attr_data[2],
617 attr_data[3],
618 ]);
619 fields.push(("med", FieldValue::UInt32(med)));
620 }
621 }
622 path_attr_type::LOCAL_PREF => {
623 if attr_data.len() >= 4 {
624 let local_pref = u32::from_be_bytes([
625 attr_data[0],
626 attr_data[1],
627 attr_data[2],
628 attr_data[3],
629 ]);
630 fields.push(("local_pref", FieldValue::UInt32(local_pref)));
631 }
632 }
633 path_attr_type::ATOMIC_AGGREGATE => {
634 fields.push(("atomic_aggregate", FieldValue::Bool(true)));
636 }
637 path_attr_type::AGGREGATOR => {
638 if attr_data.len() >= 6 {
640 let (asn, ip_offset) = if attr_data.len() >= 8 {
641 (
643 u32::from_be_bytes([
644 attr_data[0],
645 attr_data[1],
646 attr_data[2],
647 attr_data[3],
648 ]),
649 4,
650 )
651 } else {
652 (u16::from_be_bytes([attr_data[0], attr_data[1]]) as u32, 2)
654 };
655 fields.push(("aggregator_as", FieldValue::UInt32(asn)));
656
657 if attr_data.len() >= ip_offset + 4 {
658 let ip = format!(
659 "{}.{}.{}.{}",
660 attr_data[ip_offset],
661 attr_data[ip_offset + 1],
662 attr_data[ip_offset + 2],
663 attr_data[ip_offset + 3]
664 );
665 fields.push((
666 "aggregator_ip",
667 FieldValue::OwnedString(CompactString::new(ip)),
668 ));
669 }
670 }
671 }
672 _ => {
673 }
675 }
676
677 offset += attr_len;
678 }
679 }
680
681 fn parse_as_path(&self, data: &[u8]) -> (String, u16) {
684 let mut segments = Vec::with_capacity(4);
686 let mut total_length = 0u16;
687 let mut offset = 0;
688
689 while offset + 2 <= data.len() {
690 let segment_type = data[offset];
691 let segment_len = data[offset + 1] as usize;
692 offset += 2;
693
694 let remaining = data.len() - offset;
697 let as_size = if segment_len > 0 && remaining >= segment_len * 4 {
698 4 } else {
700 2 };
702
703 let needed_bytes = segment_len * as_size;
704 if offset + needed_bytes > data.len() {
705 break;
706 }
707
708 let mut asns = Vec::with_capacity(segment_len);
710 for i in 0..segment_len {
711 let asn = if as_size == 4 {
712 u32::from_be_bytes([
713 data[offset + i * 4],
714 data[offset + i * 4 + 1],
715 data[offset + i * 4 + 2],
716 data[offset + i * 4 + 3],
717 ])
718 } else {
719 u16::from_be_bytes([data[offset + i * 2], data[offset + i * 2 + 1]]) as u32
720 };
721 asns.push(asn.to_string());
722 }
723
724 let segment_str = match segment_type {
725 as_path_segment_type::AS_SET => format!("{{{}}}", asns.join(",")),
726 as_path_segment_type::AS_SEQUENCE => asns.join(" "),
727 _ => asns.join(" "),
728 };
729 segments.push(segment_str);
730
731 if segment_type == as_path_segment_type::AS_SEQUENCE {
733 total_length += segment_len as u16;
734 } else if segment_type == as_path_segment_type::AS_SET {
735 total_length += 1;
736 }
737
738 offset += needed_bytes;
739 }
740
741 (segments.join(" "), total_length)
742 }
743
744 fn parse_notification_message(
746 &self,
747 data: &[u8],
748 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
749 ) {
750 if data.len() < 2 {
751 return;
752 }
753
754 let error_code = data[0];
756 fields.push(("error_code", FieldValue::UInt8(error_code)));
757 fields.push((
758 "error_code_name",
759 FieldValue::Str(error_code_name(error_code)),
760 ));
761
762 let error_subcode = data[1];
764 fields.push(("error_subcode", FieldValue::UInt8(error_subcode)));
765
766 }
768
769 fn parse_route_refresh_message(
771 &self,
772 data: &[u8],
773 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
774 ) {
775 if data.len() < 4 {
776 return;
777 }
778
779 let afi = u16::from_be_bytes([data[0], data[1]]);
781 fields.push(("afi", FieldValue::UInt16(afi)));
782
783 let safi = data[3];
786 fields.push(("safi", FieldValue::UInt8(safi)));
787 }
788}
789
790#[cfg(test)]
791mod tests {
792 use super::*;
793
794 fn create_bgp_header(msg_type: u8, length: u16) -> Vec<u8> {
796 let mut header = Vec::new();
797
798 header.extend_from_slice(&BGP_MARKER);
800
801 header.extend_from_slice(&length.to_be_bytes());
803
804 header.push(msg_type);
806
807 header
808 }
809
810 fn create_bgp_open(version: u8, my_as: u16, hold_time: u16, bgp_id: [u8; 4]) -> Vec<u8> {
812 let mut msg = create_bgp_header(message_type::OPEN, 29); msg.push(version);
816
817 msg.extend_from_slice(&my_as.to_be_bytes());
819
820 msg.extend_from_slice(&hold_time.to_be_bytes());
822
823 msg.extend_from_slice(&bgp_id);
825
826 msg.push(0);
828
829 msg
830 }
831
832 fn create_bgp_update(withdrawn_len: u16, path_attr_len: u16) -> Vec<u8> {
834 let total_len = 19 + 2 + withdrawn_len + 2 + path_attr_len;
835 let mut msg = create_bgp_header(message_type::UPDATE, total_len);
836
837 msg.extend_from_slice(&withdrawn_len.to_be_bytes());
839
840 msg.extend(vec![0u8; withdrawn_len as usize]);
842
843 msg.extend_from_slice(&path_attr_len.to_be_bytes());
845
846 msg.extend(vec![0u8; path_attr_len as usize]);
848
849 msg
850 }
851
852 #[test]
854 fn test_can_parse_with_tcp_port_179() {
855 let parser = BgpProtocol;
856
857 let ctx1 = ParseContext::new(1);
859 assert!(parser.can_parse(&ctx1).is_none());
860
861 let mut ctx2 = ParseContext::new(1);
863 ctx2.insert_hint("dst_port", 80);
864 assert!(parser.can_parse(&ctx2).is_none());
865
866 let mut ctx3 = ParseContext::new(1);
868 ctx3.insert_hint("dst_port", 179);
869 assert!(parser.can_parse(&ctx3).is_some());
870
871 let mut ctx4 = ParseContext::new(1);
873 ctx4.insert_hint("src_port", 179);
874 assert!(parser.can_parse(&ctx4).is_some());
875 }
876
877 #[test]
879 fn test_marker_validation() {
880 let parser = BgpProtocol;
881 let mut context = ParseContext::new(1);
882 context.insert_hint("dst_port", 179);
883
884 let valid_msg = create_bgp_header(message_type::KEEPALIVE, 19);
886 let result = parser.parse(&valid_msg, &context);
887 assert!(result.is_ok());
888
889 let mut invalid_msg = create_bgp_header(message_type::KEEPALIVE, 19);
891 invalid_msg[0] = 0xFE; let result = parser.parse(&invalid_msg, &context);
893 assert!(!result.is_ok());
894 assert!(result.error.unwrap().contains("invalid marker"));
895 }
896
897 #[test]
899 fn test_message_type_parsing() {
900 let parser = BgpProtocol;
901 let mut context = ParseContext::new(1);
902 context.insert_hint("dst_port", 179);
903
904 let test_types = [
905 (message_type::OPEN, "OPEN"),
906 (message_type::UPDATE, "UPDATE"),
907 (message_type::NOTIFICATION, "NOTIFICATION"),
908 (message_type::KEEPALIVE, "KEEPALIVE"),
909 (message_type::ROUTE_REFRESH, "ROUTE-REFRESH"),
910 ];
911
912 for (msg_type, name) in test_types {
913 let length = if msg_type == message_type::OPEN {
915 29
916 } else {
917 19
918 };
919 let mut msg = create_bgp_header(msg_type, length);
920 if msg_type == message_type::OPEN {
922 msg.extend(vec![0u8; 10]);
923 }
924
925 let result = parser.parse(&msg, &context);
926 assert!(result.is_ok());
927 assert_eq!(
928 result.get("message_type"),
929 Some(&FieldValue::UInt8(msg_type))
930 );
931 assert_eq!(
932 result.get("message_type_name"),
933 Some(&FieldValue::Str(name))
934 );
935 }
936 }
937
938 #[test]
940 fn test_open_message_parsing() {
941 let parser = BgpProtocol;
942 let mut context = ParseContext::new(1);
943 context.insert_hint("dst_port", 179);
944
945 let msg = create_bgp_open(4, 65001, 180, [192, 168, 1, 1]);
946 let result = parser.parse(&msg, &context);
947
948 assert!(result.is_ok());
949 assert_eq!(result.get("version"), Some(&FieldValue::UInt8(4)));
950 assert_eq!(result.get("my_as"), Some(&FieldValue::UInt16(65001)));
951 assert_eq!(result.get("hold_time"), Some(&FieldValue::UInt16(180)));
952 assert_eq!(
953 result.get("bgp_id"),
954 Some(&FieldValue::OwnedString(CompactString::new("192.168.1.1")))
955 );
956 }
957
958 #[test]
960 fn test_keepalive_message() {
961 let parser = BgpProtocol;
962 let mut context = ParseContext::new(1);
963 context.insert_hint("dst_port", 179);
964
965 let msg = create_bgp_header(message_type::KEEPALIVE, 19);
966 let result = parser.parse(&msg, &context);
967
968 assert!(result.is_ok());
969 assert_eq!(
970 result.get("message_type"),
971 Some(&FieldValue::UInt8(message_type::KEEPALIVE))
972 );
973 assert_eq!(result.get("length"), Some(&FieldValue::UInt16(19)));
974 }
975
976 #[test]
978 fn test_update_message_basic_parsing() {
979 let parser = BgpProtocol;
980 let mut context = ParseContext::new(1);
981 context.insert_hint("dst_port", 179);
982
983 let msg = create_bgp_update(5, 10);
984 let result = parser.parse(&msg, &context);
985
986 assert!(result.is_ok());
987 assert_eq!(
988 result.get("message_type"),
989 Some(&FieldValue::UInt8(message_type::UPDATE))
990 );
991 assert_eq!(
992 result.get("withdrawn_routes_len"),
993 Some(&FieldValue::UInt16(5))
994 );
995 assert_eq!(result.get("path_attr_len"), Some(&FieldValue::UInt16(10)));
996 }
997
998 #[test]
1000 fn test_notification_message() {
1001 let parser = BgpProtocol;
1002 let mut context = ParseContext::new(1);
1003 context.insert_hint("dst_port", 179);
1004
1005 let mut msg = create_bgp_header(message_type::NOTIFICATION, 21);
1006 msg.push(6); msg.push(4); let result = parser.parse(&msg, &context);
1010
1011 assert!(result.is_ok());
1012 assert_eq!(
1013 result.get("message_type"),
1014 Some(&FieldValue::UInt8(message_type::NOTIFICATION))
1015 );
1016 assert_eq!(
1017 result.get("message_type_name"),
1018 Some(&FieldValue::Str("NOTIFICATION"))
1019 );
1020 }
1021
1022 #[test]
1024 fn test_invalid_marker_rejection() {
1025 let parser = BgpProtocol;
1026 let mut context = ParseContext::new(1);
1027 context.insert_hint("dst_port", 179);
1028
1029 let mut msg = vec![0u8; 16];
1031 msg.extend_from_slice(&19u16.to_be_bytes());
1032 msg.push(message_type::KEEPALIVE);
1033
1034 let result = parser.parse(&msg, &context);
1035 assert!(!result.is_ok());
1036 assert!(result.error.unwrap().contains("invalid marker"));
1037 }
1038
1039 #[test]
1041 fn test_bgp_too_short() {
1042 let parser = BgpProtocol;
1043 let mut context = ParseContext::new(1);
1044 context.insert_hint("dst_port", 179);
1045
1046 let short_msg = [0xFF; 18]; let result = parser.parse(&short_msg, &context);
1048
1049 assert!(!result.is_ok());
1050 assert!(result.error.is_some());
1051 }
1052
1053 #[test]
1055 fn test_bgp_schema_fields() {
1056 let parser = BgpProtocol;
1057 let fields = parser.schema_fields();
1058
1059 assert!(!fields.is_empty());
1060 let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
1061 assert!(field_names.contains(&"bgp.message_type"));
1062 assert!(field_names.contains(&"bgp.message_type_name"));
1063 assert!(field_names.contains(&"bgp.length"));
1064 assert!(field_names.contains(&"bgp.version"));
1065 assert!(field_names.contains(&"bgp.my_as"));
1066 assert!(field_names.contains(&"bgp.hold_time"));
1067 assert!(field_names.contains(&"bgp.bgp_id"));
1068 assert!(field_names.contains(&"bgp.withdrawn_routes_len"));
1069 assert!(field_names.contains(&"bgp.path_attr_len"));
1070 }
1071
1072 #[test]
1074 fn test_multiple_messages() {
1075 let parser = BgpProtocol;
1076 let mut context = ParseContext::new(1);
1077 context.insert_hint("dst_port", 179);
1078
1079 let mut data = create_bgp_header(message_type::KEEPALIVE, 19);
1081 data.extend(create_bgp_header(message_type::KEEPALIVE, 19));
1082
1083 let result = parser.parse(&data, &context);
1084
1085 assert!(result.is_ok());
1086 assert_eq!(result.remaining.len(), 19); }
1088
1089 #[test]
1091 fn test_open_with_4byte_asn_capability() {
1092 let parser = BgpProtocol;
1093 let mut context = ParseContext::new(1);
1094 context.insert_hint("dst_port", 179);
1095
1096 let mut msg = Vec::new();
1098 msg.extend_from_slice(&BGP_MARKER);
1099
1100 let four_byte_asn: u32 = 4200000001;
1102 let cap_data = [
1103 2,
1104 6, capability_code::FOUR_OCTET_AS,
1106 4, ((four_byte_asn >> 24) & 0xFF) as u8,
1108 ((four_byte_asn >> 16) & 0xFF) as u8,
1109 ((four_byte_asn >> 8) & 0xFF) as u8,
1110 (four_byte_asn & 0xFF) as u8,
1111 ];
1112
1113 let total_len = 19 + 10 + cap_data.len();
1114 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1115 msg.push(message_type::OPEN);
1116
1117 msg.push(4); msg.extend_from_slice(&AS_TRANS.to_be_bytes()); msg.extend_from_slice(&180u16.to_be_bytes()); msg.extend_from_slice(&[10, 0, 0, 1]); msg.push(cap_data.len() as u8); msg.extend_from_slice(&cap_data);
1124
1125 let result = parser.parse(&msg, &context);
1126
1127 assert!(result.is_ok());
1128 assert_eq!(result.get("my_as"), Some(&FieldValue::UInt16(AS_TRANS)));
1129 assert_eq!(
1130 result.get("my_as_4byte"),
1131 Some(&FieldValue::UInt32(four_byte_asn))
1132 );
1133
1134 if let Some(FieldValue::OwnedString(caps)) = result.get("capabilities") {
1136 assert!(caps.contains("4-BYTE-AS"));
1137 } else {
1138 panic!("Expected capabilities field");
1139 }
1140 }
1141
1142 #[test]
1144 fn test_update_with_withdrawn_routes() {
1145 let parser = BgpProtocol;
1146 let mut context = ParseContext::new(1);
1147 context.insert_hint("dst_port", 179);
1148
1149 let mut msg = Vec::new();
1151 msg.extend_from_slice(&BGP_MARKER);
1152
1153 let withdrawn = [
1156 8, 10, 16, 192, 168, ];
1159
1160 let total_len = 19 + 2 + withdrawn.len() + 2; msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1162 msg.push(message_type::UPDATE);
1163
1164 msg.extend_from_slice(&(withdrawn.len() as u16).to_be_bytes());
1166 msg.extend_from_slice(&withdrawn);
1167
1168 msg.extend_from_slice(&0u16.to_be_bytes());
1170
1171 let result = parser.parse(&msg, &context);
1172
1173 assert!(result.is_ok());
1174 assert_eq!(
1175 result.get("withdrawn_routes_len"),
1176 Some(&FieldValue::UInt16(5))
1177 );
1178 assert_eq!(result.get("withdrawn_count"), Some(&FieldValue::UInt16(2)));
1179
1180 if let Some(FieldValue::OwnedString(routes)) = result.get("withdrawn_routes") {
1181 assert!(routes.contains("10.0.0.0/8"));
1182 assert!(routes.contains("192.168.0.0/16"));
1183 } else {
1184 panic!("Expected withdrawn_routes field");
1185 }
1186 }
1187
1188 #[test]
1190 fn test_update_with_path_attributes() {
1191 let parser = BgpProtocol;
1192 let mut context = ParseContext::new(1);
1193 context.insert_hint("dst_port", 179);
1194
1195 let mut path_attrs = Vec::new();
1197
1198 path_attrs.extend_from_slice(&[0x40, path_attr_type::ORIGIN, 1, origin_type::IGP]);
1200
1201 path_attrs.extend_from_slice(&[
1203 0x40,
1204 path_attr_type::AS_PATH,
1205 6,
1206 as_path_segment_type::AS_SEQUENCE,
1207 2, 0xFD,
1209 0xE9, 0xFD,
1211 0xEA, ]);
1213
1214 path_attrs.extend_from_slice(&[0x40, path_attr_type::NEXT_HOP, 4, 192, 168, 1, 1]);
1216
1217 path_attrs.extend_from_slice(&[0x80, path_attr_type::MULTI_EXIT_DISC, 4, 0, 0, 0, 100]);
1219
1220 path_attrs.extend_from_slice(&[0x40, path_attr_type::LOCAL_PREF, 4, 0, 0, 0, 200]);
1222
1223 let mut msg = Vec::new();
1225 msg.extend_from_slice(&BGP_MARKER);
1226
1227 let total_len = 19 + 2 + 2 + path_attrs.len();
1228 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1229 msg.push(message_type::UPDATE);
1230
1231 msg.extend_from_slice(&0u16.to_be_bytes());
1233
1234 msg.extend_from_slice(&(path_attrs.len() as u16).to_be_bytes());
1236 msg.extend_from_slice(&path_attrs);
1237
1238 let result = parser.parse(&msg, &context);
1239
1240 assert!(result.is_ok());
1241 assert_eq!(
1242 result.get("origin"),
1243 Some(&FieldValue::UInt8(origin_type::IGP))
1244 );
1245 assert_eq!(result.get("origin_name"), Some(&FieldValue::Str("IGP")));
1246 assert_eq!(
1247 result.get("next_hop"),
1248 Some(&FieldValue::OwnedString(CompactString::new("192.168.1.1")))
1249 );
1250 assert_eq!(result.get("med"), Some(&FieldValue::UInt32(100)));
1251 assert_eq!(result.get("local_pref"), Some(&FieldValue::UInt32(200)));
1252 assert_eq!(result.get("as_path_length"), Some(&FieldValue::UInt16(2)));
1253
1254 if let Some(FieldValue::OwnedString(as_path)) = result.get("as_path") {
1255 assert!(as_path.contains("65001"));
1256 assert!(as_path.contains("65002"));
1257 } else {
1258 panic!("Expected as_path field");
1259 }
1260 }
1261
1262 #[test]
1264 fn test_update_with_nlri() {
1265 let parser = BgpProtocol;
1266 let mut context = ParseContext::new(1);
1267 context.insert_hint("dst_port", 179);
1268
1269 let path_attrs = [
1271 0x40,
1272 path_attr_type::ORIGIN,
1273 1,
1274 origin_type::IGP,
1275 0x40,
1276 path_attr_type::AS_PATH,
1277 0, 0x40,
1279 path_attr_type::NEXT_HOP,
1280 4,
1281 10,
1282 0,
1283 0,
1284 1,
1285 ];
1286
1287 let nlri = [12, 172, 16]; let mut msg = Vec::new();
1291 msg.extend_from_slice(&BGP_MARKER);
1292
1293 let total_len = 19 + 2 + 2 + path_attrs.len() + nlri.len();
1294 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1295 msg.push(message_type::UPDATE);
1296
1297 msg.extend_from_slice(&0u16.to_be_bytes()); msg.extend_from_slice(&(path_attrs.len() as u16).to_be_bytes());
1299 msg.extend_from_slice(&path_attrs);
1300 msg.extend_from_slice(&nlri);
1301
1302 let result = parser.parse(&msg, &context);
1303
1304 assert!(result.is_ok());
1305 assert_eq!(result.get("nlri_count"), Some(&FieldValue::UInt16(1)));
1306
1307 if let Some(FieldValue::OwnedString(nlri_str)) = result.get("nlri") {
1308 assert!(nlri_str.contains("172.16.0.0/12"));
1309 } else {
1310 panic!("Expected nlri field");
1311 }
1312 }
1313
1314 #[test]
1316 fn test_route_refresh_message() {
1317 let parser = BgpProtocol;
1318 let mut context = ParseContext::new(1);
1319 context.insert_hint("dst_port", 179);
1320
1321 let mut msg = create_bgp_header(message_type::ROUTE_REFRESH, 23); msg.extend_from_slice(&1u16.to_be_bytes()); msg.push(0); msg.push(1); let result = parser.parse(&msg, &context);
1329
1330 assert!(result.is_ok());
1331 assert_eq!(
1332 result.get("message_type"),
1333 Some(&FieldValue::UInt8(message_type::ROUTE_REFRESH))
1334 );
1335 assert_eq!(
1336 result.get("message_type_name"),
1337 Some(&FieldValue::Str("ROUTE-REFRESH"))
1338 );
1339 assert_eq!(result.get("afi"), Some(&FieldValue::UInt16(1)));
1340 assert_eq!(result.get("safi"), Some(&FieldValue::UInt8(1)));
1341 }
1342
1343 #[test]
1345 fn test_route_refresh_ipv6_multicast() {
1346 let parser = BgpProtocol;
1347 let mut context = ParseContext::new(1);
1348 context.insert_hint("dst_port", 179);
1349
1350 let mut msg = create_bgp_header(message_type::ROUTE_REFRESH, 23);
1351
1352 msg.extend_from_slice(&2u16.to_be_bytes()); msg.push(0); msg.push(2); let result = parser.parse(&msg, &context);
1358
1359 assert!(result.is_ok());
1360 assert_eq!(result.get("afi"), Some(&FieldValue::UInt16(2)));
1361 assert_eq!(result.get("safi"), Some(&FieldValue::UInt8(2)));
1362 }
1363
1364 #[test]
1366 fn test_as_path_with_as_set() {
1367 let parser = BgpProtocol;
1368 let mut context = ParseContext::new(1);
1369 context.insert_hint("dst_port", 179);
1370
1371 let path_attrs = [
1373 0x40,
1374 path_attr_type::ORIGIN,
1375 1,
1376 origin_type::IGP,
1377 0x40,
1378 path_attr_type::AS_PATH,
1379 8,
1380 as_path_segment_type::AS_SET,
1381 3, 0xFD,
1383 0xE9, 0xFD,
1385 0xEA, 0xFD,
1387 0xEB, 0x40,
1389 path_attr_type::NEXT_HOP,
1390 4,
1391 10,
1392 0,
1393 0,
1394 1,
1395 ];
1396
1397 let mut msg = Vec::new();
1398 msg.extend_from_slice(&BGP_MARKER);
1399
1400 let total_len = 19 + 2 + 2 + path_attrs.len();
1401 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1402 msg.push(message_type::UPDATE);
1403
1404 msg.extend_from_slice(&0u16.to_be_bytes());
1405 msg.extend_from_slice(&(path_attrs.len() as u16).to_be_bytes());
1406 msg.extend_from_slice(&path_attrs);
1407
1408 let result = parser.parse(&msg, &context);
1409
1410 assert!(result.is_ok());
1411 assert_eq!(result.get("as_path_length"), Some(&FieldValue::UInt16(1)));
1413
1414 if let Some(FieldValue::OwnedString(as_path)) = result.get("as_path") {
1415 assert!(as_path.contains("{"));
1417 assert!(as_path.contains("}"));
1418 } else {
1419 panic!("Expected as_path field");
1420 }
1421 }
1422
1423 #[test]
1425 fn test_notification_error_codes() {
1426 let parser = BgpProtocol;
1427 let mut context = ParseContext::new(1);
1428 context.insert_hint("dst_port", 179);
1429
1430 let test_cases = [
1431 (error_code::MESSAGE_HEADER_ERROR, 1, "Message Header Error"),
1432 (error_code::OPEN_MESSAGE_ERROR, 2, "OPEN Message Error"),
1433 (error_code::UPDATE_MESSAGE_ERROR, 1, "UPDATE Message Error"),
1434 (error_code::HOLD_TIMER_EXPIRED, 0, "Hold Timer Expired"),
1435 (error_code::FSM_ERROR, 0, "Finite State Machine Error"),
1436 (error_code::CEASE, 4, "Cease"), ];
1438
1439 for (err_code, err_subcode, expected_name) in test_cases {
1440 let mut msg = create_bgp_header(message_type::NOTIFICATION, 21);
1441 msg.push(err_code);
1442 msg.push(err_subcode);
1443
1444 let result = parser.parse(&msg, &context);
1445
1446 assert!(result.is_ok());
1447 assert_eq!(result.get("error_code"), Some(&FieldValue::UInt8(err_code)));
1448 assert_eq!(
1449 result.get("error_subcode"),
1450 Some(&FieldValue::UInt8(err_subcode))
1451 );
1452 assert_eq!(
1453 result.get("error_code_name"),
1454 Some(&FieldValue::Str(expected_name))
1455 );
1456 }
1457 }
1458
1459 #[test]
1461 fn test_atomic_aggregate_attribute() {
1462 let parser = BgpProtocol;
1463 let mut context = ParseContext::new(1);
1464 context.insert_hint("dst_port", 179);
1465
1466 let path_attrs = [
1467 0x40,
1468 path_attr_type::ORIGIN,
1469 1,
1470 origin_type::IGP,
1471 0x40,
1472 path_attr_type::AS_PATH,
1473 0,
1474 0x40,
1475 path_attr_type::NEXT_HOP,
1476 4,
1477 10,
1478 0,
1479 0,
1480 1,
1481 0x40,
1482 path_attr_type::ATOMIC_AGGREGATE,
1483 0, ];
1485
1486 let mut msg = Vec::new();
1487 msg.extend_from_slice(&BGP_MARKER);
1488
1489 let total_len = 19 + 2 + 2 + path_attrs.len();
1490 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1491 msg.push(message_type::UPDATE);
1492
1493 msg.extend_from_slice(&0u16.to_be_bytes());
1494 msg.extend_from_slice(&(path_attrs.len() as u16).to_be_bytes());
1495 msg.extend_from_slice(&path_attrs);
1496
1497 let result = parser.parse(&msg, &context);
1498
1499 assert!(result.is_ok());
1500 assert_eq!(
1501 result.get("atomic_aggregate"),
1502 Some(&FieldValue::Bool(true))
1503 );
1504 }
1505
1506 #[test]
1508 fn test_aggregator_attribute() {
1509 let parser = BgpProtocol;
1510 let mut context = ParseContext::new(1);
1511 context.insert_hint("dst_port", 179);
1512
1513 let path_attrs = [
1515 0x40,
1516 path_attr_type::ORIGIN,
1517 1,
1518 origin_type::IGP,
1519 0x40,
1520 path_attr_type::AS_PATH,
1521 0,
1522 0x40,
1523 path_attr_type::NEXT_HOP,
1524 4,
1525 10,
1526 0,
1527 0,
1528 1,
1529 0xC0,
1530 path_attr_type::AGGREGATOR,
1531 6, 0xFD,
1533 0xE9, 192,
1535 168,
1536 1,
1537 1, ];
1539
1540 let mut msg = Vec::new();
1541 msg.extend_from_slice(&BGP_MARKER);
1542
1543 let total_len = 19 + 2 + 2 + path_attrs.len();
1544 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1545 msg.push(message_type::UPDATE);
1546
1547 msg.extend_from_slice(&0u16.to_be_bytes());
1548 msg.extend_from_slice(&(path_attrs.len() as u16).to_be_bytes());
1549 msg.extend_from_slice(&path_attrs);
1550
1551 let result = parser.parse(&msg, &context);
1552
1553 assert!(result.is_ok());
1554 assert_eq!(
1555 result.get("aggregator_as"),
1556 Some(&FieldValue::UInt32(65001))
1557 );
1558 assert_eq!(
1559 result.get("aggregator_ip"),
1560 Some(&FieldValue::OwnedString(CompactString::new("192.168.1.1")))
1561 );
1562 }
1563
1564 #[test]
1566 fn test_extended_length_attribute() {
1567 let parser = BgpProtocol;
1568 let mut context = ParseContext::new(1);
1569 context.insert_hint("dst_port", 179);
1570
1571 let mut path_attrs = Vec::new();
1573
1574 path_attrs.extend_from_slice(&[0x40, path_attr_type::ORIGIN, 1, origin_type::EGP]);
1576
1577 path_attrs.push(0x50); path_attrs.push(path_attr_type::AS_PATH);
1581 path_attrs.extend_from_slice(&14u16.to_be_bytes()); path_attrs.push(as_path_segment_type::AS_SEQUENCE);
1583 path_attrs.push(6); for asn in [65001u16, 65002, 65003, 65004, 65005, 65006] {
1585 path_attrs.extend_from_slice(&asn.to_be_bytes());
1586 }
1587
1588 path_attrs.extend_from_slice(&[0x40, path_attr_type::NEXT_HOP, 4, 10, 0, 0, 1]);
1590
1591 let mut msg = Vec::new();
1592 msg.extend_from_slice(&BGP_MARKER);
1593
1594 let total_len = 19 + 2 + 2 + path_attrs.len();
1595 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1596 msg.push(message_type::UPDATE);
1597
1598 msg.extend_from_slice(&0u16.to_be_bytes());
1599 msg.extend_from_slice(&(path_attrs.len() as u16).to_be_bytes());
1600 msg.extend_from_slice(&path_attrs);
1601
1602 let result = parser.parse(&msg, &context);
1603
1604 assert!(result.is_ok());
1605 assert_eq!(
1606 result.get("origin"),
1607 Some(&FieldValue::UInt8(origin_type::EGP))
1608 );
1609 assert_eq!(result.get("origin_name"), Some(&FieldValue::Str("EGP")));
1610 assert_eq!(result.get("as_path_length"), Some(&FieldValue::UInt16(6)));
1611 }
1612
1613 #[test]
1615 fn test_multiple_nlri_prefixes() {
1616 let parser = BgpProtocol;
1617 let mut context = ParseContext::new(1);
1618 context.insert_hint("dst_port", 179);
1619
1620 let path_attrs = [
1621 0x40,
1622 path_attr_type::ORIGIN,
1623 1,
1624 origin_type::IGP,
1625 0x40,
1626 path_attr_type::AS_PATH,
1627 0,
1628 0x40,
1629 path_attr_type::NEXT_HOP,
1630 4,
1631 10,
1632 0,
1633 0,
1634 1,
1635 ];
1636
1637 let nlri = [
1639 8, 10, 16, 172, 16, 24, 192, 168, 1, ];
1643
1644 let mut msg = Vec::new();
1645 msg.extend_from_slice(&BGP_MARKER);
1646
1647 let total_len = 19 + 2 + 2 + path_attrs.len() + nlri.len();
1648 msg.extend_from_slice(&(total_len as u16).to_be_bytes());
1649 msg.push(message_type::UPDATE);
1650
1651 msg.extend_from_slice(&0u16.to_be_bytes());
1652 msg.extend_from_slice(&(path_attrs.len() as u16).to_be_bytes());
1653 msg.extend_from_slice(&path_attrs);
1654 msg.extend_from_slice(&nlri);
1655
1656 let result = parser.parse(&msg, &context);
1657
1658 assert!(result.is_ok());
1659 assert_eq!(result.get("nlri_count"), Some(&FieldValue::UInt16(3)));
1660
1661 if let Some(FieldValue::OwnedString(nlri_str)) = result.get("nlri") {
1662 assert!(nlri_str.contains("10.0.0.0/8"));
1663 assert!(nlri_str.contains("172.16.0.0/16"));
1664 assert!(nlri_str.contains("192.168.1.0/24"));
1665 } else {
1666 panic!("Expected nlri field");
1667 }
1668 }
1669}