1use crate::models::{AsnLength, DissectionNode};
12use std::net::{Ipv4Addr, Ipv6Addr};
13
14fn attr_name(code: u8) -> &'static str {
16 match code {
17 1 => "ORIGIN",
18 2 => "AS_PATH",
19 3 => "NEXT_HOP",
20 4 => "MULTI_EXIT_DISC",
21 5 => "LOCAL_PREF",
22 6 => "ATOMIC_AGGREGATE",
23 7 => "AGGREGATOR",
24 8 => "COMMUNITIES",
25 9 => "ORIGINATOR_ID",
26 10 => "CLUSTER_LIST",
27 14 => "MP_REACH_NLRI",
28 15 => "MP_UNREACH_NLRI",
29 16 => "EXTENDED_COMMUNITIES",
30 17 => "AS4_PATH",
31 18 => "AS4_AGGREGATOR",
32 22 => "PMSI_TUNNEL",
33 23 => "TUNNEL_ENCAPSULATION",
34 24 => "TRAFFIC_ENGINEERING",
35 25 => "IPV6_EXTENDED_COMMUNITIES",
36 26 => "AIGP",
37 27 => "PE_DISTINGUISHER_LABELS",
38 29 => "BGP-LS",
39 32 => "LARGE_COMMUNITY",
40 33 => "BGPSEC_PATH",
41 35 => "ONLY_TO_CUSTOMER",
42 36 => "BGP_DOMAIN_PATH",
43 37 => "SFP",
44 38 => "BFD_DISCRIMINATOR",
45 40 => "BGP_PREFIX_SID",
46 41 => "BIER",
47 _ => "ATTRIBUTE",
48 }
49}
50
51pub(crate) fn read_u16(data: &[u8], pos: usize) -> Option<u16> {
52 let hi = *data.get(pos)?;
53 let lo = *data.get(pos + 1)?;
54 Some(u16::from_be_bytes([hi, lo]))
55}
56
57fn read_u32(data: &[u8], pos: usize) -> Option<u32> {
58 let b: [u8; 4] = data.get(pos..pos + 4)?.try_into().ok()?;
59 Some(u32::from_be_bytes(b))
60}
61
62fn render_prefix(afi_v4: bool, plen: u8, octets: &[u8]) -> String {
63 if afi_v4 {
64 let mut octets = octets.to_vec();
65 octets.resize(4, 0);
66 format!(
67 "{}.{}.{}.{}/{}",
68 octets[0], octets[1], octets[2], octets[3], plen
69 )
70 } else {
71 let mut octets = octets.to_vec();
72 octets.resize(16, 0);
73 let segs: Vec<u16> = octets
74 .chunks(2)
75 .map(|c| u16::from_be_bytes([c[0], c[1]]))
76 .collect();
77 let s = &segs;
78 format!(
79 "{}/{}",
80 Ipv6Addr::new(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]),
81 plen
82 )
83 }
84}
85
86pub fn dissect_bgp_message(data: &[u8], asn_len: &AsnLength, add_path: bool) -> DissectionNode {
92 dissect_bgp_message_base(data, 0, asn_len, add_path)
93}
94
95pub(crate) fn dissect_bgp_message_base(
99 data: &[u8],
100 base: u32,
101 asn_len: &AsnLength,
102 add_path: bool,
103) -> DissectionNode {
104 if data.len() < 19 {
105 let mut root = DissectionNode::new(
106 "bgp",
107 format!("BGP message (truncated, {} of 19 header bytes)", data.len()),
108 base,
109 data.len() as u32,
110 );
111 if !data.is_empty() {
112 root.children.push(DissectionNode::new(
113 "bgp.header",
114 format!("Header (truncated, {} of 19 bytes)", data.len()),
115 base,
116 data.len() as u32,
117 ));
118 }
119 return root;
120 }
121
122 let length = u16::from_be_bytes([data[16], data[17]]);
123 let msg_type = data[18];
124
125 let declared_body = (length as usize).saturating_sub(19);
130 let available_body = data.len() - 19;
131 let body_len = declared_body.min(available_body);
132 let message_len = 19 + body_len;
133
134 let mut root = DissectionNode::new(
135 "bgp",
136 format!("BGP message ({message_len} bytes)"),
137 base,
138 message_len as u32,
139 );
140
141 let mut header = DissectionNode::new("bgp.header", "Header", base, 19);
142 let all_ones = data[..16].iter().all(|b| *b == 0xFF);
143 header.children.push(DissectionNode::new(
144 "bgp.header.marker",
145 if all_ones {
146 "Marker (all ones)".to_string()
147 } else {
148 "Marker (NOT all ones — invalid per RFC 4271)".to_string()
149 },
150 base,
151 16,
152 ));
153 header.children.push(DissectionNode::new(
154 "bgp.header.length",
155 format!("Length: {length}"),
156 base + 16,
157 2,
158 ));
159 let type_name = match msg_type {
160 1 => "OPEN",
161 2 => "UPDATE",
162 3 => "NOTIFICATION",
163 4 => "KEEPALIVE",
164 5 => "ROUTE-REFRESH",
165 _ => "UNKNOWN",
166 };
167 header.children.push(DissectionNode::new(
168 "bgp.header.type",
169 format!("Type: {type_name} ({msg_type})"),
170 base + 18,
171 1,
172 ));
173 root.children.push(header);
174
175 if declared_body > available_body {
176 root.children.push(DissectionNode::new(
177 "bgp.truncated",
178 format!(
179 "Declared length {length} exceeds the {} available bytes",
180 data.len()
181 ),
182 base + message_len as u32,
183 0,
184 ));
185 }
186
187 let body = &data[19..19 + body_len];
188 let body_base = base + 19;
189 match msg_type {
190 1 => root.children.push(dissect_open(body, body_base)),
191 2 => root
192 .children
193 .push(dissect_update(body, body_base, asn_len, add_path)),
194 3 => root.children.push(dissect_notification(body, body_base)),
195 4 => {}
196 5 => root.children.push(dissect_route_refresh(body, body_base)),
197 _ => root.children.push(DissectionNode::new(
198 "bgp.body",
199 format!("Unknown message type ({} bytes)", body.len()),
200 body_base,
201 body.len() as u32,
202 )),
203 }
204
205 if available_body > body_len {
206 root.children.push(DissectionNode::new(
207 "bgp.trailing",
208 format!(
209 "Trailing bytes beyond the declared message ({} bytes)",
210 available_body - body_len
211 ),
212 base + message_len as u32,
213 (available_body - body_len) as u32,
214 ));
215 }
216 root
217}
218
219fn dissect_update(data: &[u8], base: u32, asn_len: &AsnLength, add_path: bool) -> DissectionNode {
220 let mut update = DissectionNode::new("bgp.update", "UPDATE", base, data.len() as u32);
221
222 if data.len() < 2 {
223 if !data.is_empty() {
224 update.children.push(DissectionNode::new(
225 "bgp.update.withdrawn_routes.length",
226 "Withdrawn routes length (truncated)",
227 base,
228 data.len() as u32,
229 ));
230 }
231 return update;
232 }
233
234 let withdrawn_len = u16::from_be_bytes([data[0], data[1]]) as usize;
235 update.children.push(DissectionNode::new(
236 "bgp.update.withdrawn_routes.length",
237 format!("Withdrawn routes length: {withdrawn_len}"),
238 base,
239 2,
240 ));
241
242 let mut pos = 2usize;
243 if pos + withdrawn_len > data.len() {
244 update.children.push(DissectionNode::new(
245 "bgp.update.withdrawn_routes",
246 format!(
247 "Withdrawn routes (truncated: {} of {} bytes)",
248 data.len() - pos,
249 withdrawn_len
250 ),
251 base + pos as u32,
252 (data.len() - pos) as u32,
253 ));
254 return update;
255 }
256 if withdrawn_len > 0 {
257 let mut section = DissectionNode::new(
258 "bgp.update.withdrawn_routes",
259 format!("Withdrawn routes ({withdrawn_len} bytes)"),
260 base + 2,
261 withdrawn_len as u32,
262 );
263 section.children = dissect_nlri(&data[2..2 + withdrawn_len], base + 2, true, add_path);
264 update.children.push(section);
265 }
266 pos = 2 + withdrawn_len;
267
268 if pos + 2 > data.len() {
269 if pos < data.len() {
270 update.children.push(DissectionNode::new(
271 "bgp.update.path_attributes.length",
272 "Total path attribute length (truncated)",
273 base + pos as u32,
274 (data.len() - pos) as u32,
275 ));
276 }
277 return update;
278 }
279 let attr_len = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
280 update.children.push(DissectionNode::new(
281 "bgp.update.path_attributes.length",
282 format!("Total path attribute length: {attr_len}"),
283 base + pos as u32,
284 2,
285 ));
286 pos += 2;
287
288 let attr_end = (pos + attr_len).min(data.len());
289 if attr_end > pos {
290 let mut section = DissectionNode::new(
291 "bgp.update.path_attributes",
292 format!("Path attributes ({attr_len} bytes)"),
293 base + pos as u32,
294 (attr_end - pos) as u32,
295 );
296 section.children =
297 dissect_attributes(&data[pos..attr_end], base + pos as u32, asn_len, add_path);
298 update.children.push(section);
299 } else if attr_len > 0 {
300 update.children.push(DissectionNode::new(
301 "bgp.update.path_attributes",
302 format!("Path attributes (truncated: declared {attr_len} bytes)"),
303 base + pos as u32,
304 0,
305 ));
306 }
307 pos = attr_end;
308
309 if pos < data.len() {
310 let mut section = DissectionNode::new(
311 "bgp.update.nlri",
312 format!(
313 "Network Layer Reachability Information ({} bytes)",
314 data.len() - pos
315 ),
316 base + pos as u32,
317 (data.len() - pos) as u32,
318 );
319 section.children = dissect_nlri(&data[pos..], base + pos as u32, true, add_path);
320 update.children.push(section);
321 }
322
323 update
324}
325
326fn dissect_attributes(
328 data: &[u8],
329 base: u32,
330 asn_len: &AsnLength,
331 add_path: bool,
332) -> Vec<DissectionNode> {
333 let mut nodes = Vec::new();
334 let mut pos = 0usize;
335 let asn_size = match asn_len {
336 AsnLength::Bits16 => 2,
337 AsnLength::Bits32 => 4,
338 };
339
340 while pos + 3 <= data.len() {
341 let flags = data[pos];
342 let code = data[pos + 1];
343 let extended = flags & 0x10 != 0;
344 let header_len = if extended { 4 } else { 3 };
345 if pos + header_len > data.len() {
346 break;
347 }
348 let value_len = if extended {
349 match read_u16(data, pos + 2) {
350 Some(v) => v as usize,
351 None => break,
352 }
353 } else {
354 data[pos + 2] as usize
355 };
356 let value_start = pos + header_len;
357 let value_end = value_start + value_len;
358 if value_end > data.len() {
359 let attr_end = data.len();
363 let mut node = DissectionNode::new(
364 format!("bgp.attr.{code}"),
365 format!(
366 "{} (type {code}) — truncated ({} of {} value bytes)",
367 attr_name(code),
368 attr_end - value_start,
369 value_len
370 ),
371 base + pos as u32,
372 (attr_end - pos) as u32,
373 );
374 node.children.push(DissectionNode::new(
375 "bgp.attr.flags",
376 format!("Flags: 0x{flags:02X}"),
377 base + pos as u32,
378 1,
379 ));
380 nodes.push(node);
381 break;
382 }
383
384 let total = header_len + value_len;
385 let mut node = DissectionNode::new(
386 format!("bgp.attr.{code}"),
387 format!("{} (type {code}), {value_len} bytes", attr_name(code)),
388 base + pos as u32,
389 total as u32,
390 );
391 node.children.push(DissectionNode::new(
392 "bgp.attr.flags",
393 format!("Flags: 0x{flags:02X}"),
394 base + pos as u32,
395 1,
396 ));
397 node.children.push(DissectionNode::new(
398 "bgp.attr.type",
399 format!("Type: {code} ({})", attr_name(code)),
400 base + pos as u32 + 1,
401 1,
402 ));
403 node.children.push(DissectionNode::new(
404 "bgp.attr.length",
405 format!("Length: {value_len}"),
406 base + pos as u32 + 2,
407 (header_len - 2) as u32,
408 ));
409 let value_base = base + value_start as u32;
410 let mut value_node = DissectionNode::new(
411 "bgp.attr.value",
412 format!("Value ({value_len} bytes)"),
413 value_base,
414 value_len as u32,
415 );
416 value_node.children = dissect_attr_value(
417 code,
418 &data[value_start..value_end],
419 value_base,
420 asn_size,
421 add_path,
422 );
423 node.children.push(value_node);
424
425 nodes.push(node);
426 pos = value_end;
427 }
428
429 nodes
430}
431
432fn dissect_attr_value(
436 code: u8,
437 value: &[u8],
438 base: u32,
439 asn_size: usize,
440 add_path: bool,
441) -> Vec<DissectionNode> {
442 let mut nodes = Vec::new();
443 match code {
444 1 => {
445 if let Some(v) = value.first() {
447 let name = match v {
448 0 => "IGP",
449 1 => "EGP",
450 2 => "INCOMPLETE",
451 _ => "INVALID",
452 };
453 nodes.push(DissectionNode::new(
454 "bgp.attr.origin",
455 format!("Origin: {name} ({v})"),
456 base,
457 1,
458 ));
459 }
460 }
461 2 | 17 => {
462 let asn_size = if code == 17 { 4 } else { asn_size };
466 let mut pos = 0usize;
467 while pos + 2 <= value.len() {
468 let seg_type = value[pos];
469 let count = value[pos + 1] as usize;
470 let seg_len = 2 + count * asn_size;
471 if pos + seg_len > value.len() {
472 break;
473 }
474 let name = match seg_type {
475 1 => "AS_SET",
476 2 => "AS_SEQUENCE",
477 3 => "AS_CONFED_SEQUENCE",
478 4 => "AS_CONFED_SET",
479 _ => "UNKNOWN",
480 };
481 let mut asns = Vec::with_capacity(count);
482 for i in 0..count {
483 let at = pos + 2 + i * asn_size;
484 let asn = if asn_size == 2 {
485 read_u16(value, at).unwrap_or(0) as u32
486 } else {
487 read_u32(value, at).unwrap_or(0)
488 };
489 asns.push(asn.to_string());
490 }
491 nodes.push(DissectionNode::new(
492 "bgp.attr.as_path.segment",
493 format!("{name}: {}", asns.join(" ")),
494 base + pos as u32,
495 seg_len as u32,
496 ));
497 pos += seg_len;
498 }
499 }
500 3 => {
501 if let Some(ip) = value.get(0..4) {
502 let ipv4 = Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]);
503 nodes.push(DissectionNode::new(
504 "bgp.attr.next_hop",
505 format!("Next hop: {ipv4}"),
506 base,
507 value.len() as u32,
508 ));
509 }
510 }
511 4 | 5 | 35 => {
512 if let Some(v) = read_u32(value, 0) {
513 let name = match code {
514 4 => "Multi-exit discriminator",
515 5 => "Local preference",
516 _ => "Only to customer",
517 };
518 nodes.push(DissectionNode::new(
519 "bgp.attr.u32",
520 format!("{name}: {v}"),
521 base,
522 value.len() as u32,
523 ));
524 }
525 }
526 7 | 18 => {
527 if value.len() > 4 {
529 let asn_len_field = value.len() - 4;
530 let asn = if asn_len_field == 4 {
531 read_u32(value, 0).unwrap_or(0)
532 } else {
533 read_u16(value, 0).unwrap_or(0) as u32
534 };
535 nodes.push(DissectionNode::new(
536 "bgp.attr.aggregator.asn",
537 format!("Aggregator ASN: {asn}"),
538 base,
539 asn_len_field as u32,
540 ));
541 let id = Ipv4Addr::new(
542 value[asn_len_field],
543 value[asn_len_field + 1],
544 value[asn_len_field + 2],
545 value[asn_len_field + 3],
546 );
547 nodes.push(DissectionNode::new(
548 "bgp.attr.aggregator.id",
549 format!("Aggregator router ID: {id}"),
550 base + asn_len_field as u32,
551 4,
552 ));
553 }
554 }
555 8 => {
556 let mut pos = 0usize;
558 let mut idx = 1;
559 while pos + 4 <= value.len() {
560 let asn = read_u16(value, pos).unwrap_or(0);
561 let val = read_u16(value, pos + 2).unwrap_or(0);
562 nodes.push(DissectionNode::new(
563 "bgp.attr.communities.entry",
564 format!("Community [{idx}]: {asn}:{val}"),
565 base + pos as u32,
566 4,
567 ));
568 pos += 4;
569 idx += 1;
570 }
571 }
572 9 => {
573 if let Some(ip) = value.get(0..4) {
574 nodes.push(DissectionNode::new(
575 "bgp.attr.originator_id",
576 format!("Originator ID: {}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]),
577 base,
578 value.len() as u32,
579 ));
580 }
581 }
582 10 => {
583 let mut pos = 0usize;
585 let mut idx = 1;
586 while pos + 4 <= value.len() {
587 let id = read_u32(value, pos).unwrap_or(0);
588 nodes.push(DissectionNode::new(
589 "bgp.attr.cluster_list.entry",
590 format!("Cluster ID [{idx}]: {id}"),
591 base + pos as u32,
592 4,
593 ));
594 pos += 4;
595 idx += 1;
596 }
597 }
598 14 => {
599 if value.len() >= 4 {
601 let afi = read_u16(value, 0).unwrap_or(0);
602 let afi_name = match afi {
603 1 => "IPv4",
604 2 => "IPv6",
605 25 => "L2VPN",
606 _ => "unknown",
607 };
608 nodes.push(DissectionNode::new(
609 "bgp.attr.mp_reach.afi",
610 format!("AFI: {afi} ({afi_name})"),
611 base,
612 2,
613 ));
614 nodes.push(DissectionNode::new(
615 "bgp.attr.mp_reach.safi",
616 format!("SAFI: {}", value[2]),
617 base + 2,
618 1,
619 ));
620 let nh_len = value[3] as usize;
621 nodes.push(DissectionNode::new(
622 "bgp.attr.mp_reach.next_hop_length",
623 format!("Next hop length: {nh_len}"),
624 base + 3,
625 1,
626 ));
627 let mut pos = 4usize;
628 if nh_len > 0 && pos + nh_len > value.len() {
629 nodes.push(DissectionNode::new(
632 "bgp.attr.mp_reach.next_hop",
633 format!(
634 "Next hop (truncated: {} of {} bytes)",
635 value.len() - pos,
636 nh_len
637 ),
638 base + pos as u32,
639 (value.len() - pos) as u32,
640 ));
641 return nodes;
642 }
643 if nh_len > 0 {
644 nodes.push(DissectionNode::new(
645 "bgp.attr.mp_reach.next_hop",
646 format!("Next hop ({} bytes)", nh_len),
647 base + pos as u32,
648 nh_len as u32,
649 ));
650 pos += nh_len;
651 }
652 if pos < value.len() {
653 nodes.push(DissectionNode::new(
654 "bgp.attr.mp_reach.reserved",
655 "Reserved",
656 base + pos as u32,
657 1,
658 ));
659 pos += 1;
660 }
661 if pos < value.len() {
662 nodes.extend(dissect_nlri(
663 &value[pos..],
664 base + pos as u32,
665 afi == 1,
666 add_path,
667 ));
668 }
669 }
670 }
671 15 => {
672 if value.len() >= 3 {
674 let afi = read_u16(value, 0).unwrap_or(0);
675 nodes.push(DissectionNode::new(
676 "bgp.attr.mp_unreach.afi",
677 format!("AFI: {afi}"),
678 base,
679 2,
680 ));
681 nodes.push(DissectionNode::new(
682 "bgp.attr.mp_unreach.safi",
683 format!("SAFI: {}", value[2]),
684 base + 2,
685 1,
686 ));
687 nodes.extend(dissect_nlri(&value[3..], base + 3, afi == 1, add_path));
688 }
689 }
690 16 => {
691 let mut pos = 0usize;
693 let mut idx = 1;
694 while pos + 8 <= value.len() {
695 nodes.push(DissectionNode::new(
696 "bgp.attr.ext_communities.entry",
697 format!(
698 "Extended community [{idx}]: {}",
699 render_ext_community(&value[pos..pos + 8])
700 ),
701 base + pos as u32,
702 8,
703 ));
704 pos += 8;
705 idx += 1;
706 }
707 }
708 25 => {
709 let mut pos = 0usize;
711 let mut idx = 1;
712 while pos + 20 <= value.len() {
713 nodes.push(DissectionNode::new(
714 "bgp.attr.ipv6_ext_communities.entry",
715 format!("IPv6 extended community [{idx}]"),
716 base + pos as u32,
717 20,
718 ));
719 pos += 20;
720 idx += 1;
721 }
722 }
723 26 => {
724 let mut pos = 0usize;
726 while pos + 3 <= value.len() {
727 let tlv_type = value[pos];
728 let tlv_len = read_u16(value, pos + 1).unwrap_or(0) as usize;
730 if tlv_len < 3 || pos + tlv_len > value.len() {
731 break;
732 }
733 nodes.push(DissectionNode::new(
734 "bgp.attr.aigp.entry",
735 format!("AIGP TLV: type {tlv_type}, {tlv_len} bytes"),
736 base + pos as u32,
737 tlv_len as u32,
738 ));
739 pos += tlv_len;
740 }
741 }
742 32 => {
743 let mut pos = 0usize;
745 let mut idx = 1;
746 while pos + 12 <= value.len() {
747 let ga = read_u32(value, pos).unwrap_or(0);
748 let l1 = read_u32(value, pos + 4).unwrap_or(0);
749 let l2 = read_u32(value, pos + 8).unwrap_or(0);
750 nodes.push(DissectionNode::new(
751 "bgp.attr.large_communities.entry",
752 format!("Large community [{idx}]: {ga}:{l1}:{l2}"),
753 base + pos as u32,
754 12,
755 ));
756 pos += 12;
757 idx += 1;
758 }
759 }
760 _ => {}
761 }
762 nodes
763}
764
765fn render_ext_community(entry: &[u8]) -> String {
766 let type_high = entry[0];
767 let subtype = entry[1];
768 let value = &entry[2..8];
769 match type_high & 0x0F {
770 0x00 => {
771 let asn = u16::from_be_bytes([value[0], value[1]]);
772 let val = u32::from_be_bytes([value[2], value[3], value[4], value[5]]);
773 format!("type 0x{type_high:02X} subtype 0x{subtype:02X}: {asn}:{val}")
774 }
775 0x01 => {
776 let ip = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
777 let val = u16::from_be_bytes([value[4], value[5]]);
778 format!("type 0x{type_high:02X} subtype 0x{subtype:02X}: {ip}:{val}")
779 }
780 0x02 => {
781 let asn = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
782 let val = u16::from_be_bytes([value[4], value[5]]);
783 format!("type 0x{type_high:02X} subtype 0x{subtype:02X}: {asn}:{val}")
784 }
785 _ => {
786 let hex: Vec<String> = value.iter().map(|b| format!("{b:02X}")).collect();
787 format!(
788 "type 0x{type_high:02X} subtype 0x{subtype:02X}: {}",
789 hex.join(" ")
790 )
791 }
792 }
793}
794
795fn dissect_nlri(data: &[u8], base: u32, afi_v4: bool, add_path: bool) -> Vec<DissectionNode> {
798 let mut nodes = Vec::new();
799 let mut pos = 0usize;
800 while pos < data.len() {
801 let entry_start = pos;
802 if add_path {
803 if pos + 5 > data.len() {
804 break;
805 }
806 pos += 4; }
808 let plen = match data.get(pos) {
809 Some(v) => *v,
810 None => break,
811 };
812 let octets = (plen as usize).div_ceil(8);
813 pos += 1;
814 if pos + octets > data.len() {
815 break;
816 }
817 let label = render_prefix(afi_v4, plen, &data[pos..pos + octets]);
818 nodes.push(DissectionNode::new(
819 "bgp.nlri.prefix",
820 label,
821 base + entry_start as u32,
822 (pos + octets - entry_start) as u32,
823 ));
824 pos += octets;
825 }
826 nodes
827}
828
829fn dissect_open(data: &[u8], base: u32) -> DissectionNode {
830 let mut open = DissectionNode::new("bgp.open", "OPEN", base, data.len() as u32);
831 if data.len() < 10 {
832 if !data.is_empty() {
833 open.children.push(DissectionNode::new(
834 "bgp.open.truncated",
835 format!("Truncated OPEN ({} of 10 fixed bytes)", data.len()),
836 base,
837 data.len() as u32,
838 ));
839 }
840 return open;
841 }
842
843 open.children.push(DissectionNode::new(
844 "bgp.open.version",
845 format!("Version: {}", data[0]),
846 base,
847 1,
848 ));
849 let asn = u16::from_be_bytes([data[1], data[2]]);
850 open.children.push(DissectionNode::new(
851 "bgp.open.asn",
852 format!("My AS: {asn}"),
853 base + 1,
854 2,
855 ));
856 let hold = u16::from_be_bytes([data[3], data[4]]);
857 open.children.push(DissectionNode::new(
858 "bgp.open.hold_time",
859 format!("Hold time: {hold}s"),
860 base + 3,
861 2,
862 ));
863 open.children.push(DissectionNode::new(
864 "bgp.open.bgp_identifier",
865 format!(
866 "BGP identifier: {}.{}.{}.{}",
867 data[5], data[6], data[7], data[8]
868 ),
869 base + 5,
870 4,
871 ));
872 let opt_len = data[9];
873 open.children.push(DissectionNode::new(
874 "bgp.open.opt_params_len",
875 format!("Optional parameters length: {opt_len}"),
876 base + 9,
877 1,
878 ));
879
880 let mut pos = 10usize;
883 let mut extended = false;
884 let mut first = true;
885 while pos + 2 <= data.len() {
886 let param_type = data[pos];
887 if first && param_type == 255 && opt_len != 0 {
888 extended = true;
889 if pos + 3 > data.len() {
890 break;
891 }
892 let ext_len = read_u16(data, pos + 1).unwrap_or(0);
893 open.children.push(DissectionNode::new(
894 "bgp.open.ext_params_len",
895 format!("Extended optional parameters length: {ext_len} (RFC 9072)"),
896 base + pos as u32,
897 3,
898 ));
899 pos += 3;
900 first = false;
901 if ext_len == 0 {
902 break;
903 }
904 continue;
905 }
906 first = false;
907
908 let len_size = if extended { 2 } else { 1 };
909 let value_len = if extended {
910 match read_u16(data, pos + 1) {
911 Some(v) => v as usize,
912 None => break,
913 }
914 } else {
915 *data.get(pos + 1).unwrap_or(&0) as usize
916 };
917 let header_len = 1 + len_size;
918 if pos + header_len + value_len > data.len() {
919 break;
920 }
921
922 let mut param = DissectionNode::new(
923 "bgp.open.param",
924 if param_type == 2 {
925 "Capabilities (RFC 3392)".to_string()
926 } else {
927 format!("Optional parameter (type {param_type})")
928 },
929 base + pos as u32,
930 (header_len + value_len) as u32,
931 );
932 param.children.push(DissectionNode::new(
933 "bgp.open.param.type",
934 format!("Type: {param_type}"),
935 base + pos as u32,
936 1,
937 ));
938 param.children.push(DissectionNode::new(
939 "bgp.open.param.length",
940 format!("Length: {value_len}"),
941 base + pos as u32 + 1,
942 len_size as u32,
943 ));
944
945 let value = &data[pos + header_len..pos + header_len + value_len];
946 let value_base = base + (pos + header_len) as u32;
947 if param_type == 2 {
948 let mut cpos = 0usize;
950 while cpos + 2 <= value.len() {
951 let cap_code = value[cpos];
952 let cap_len = value[cpos + 1] as usize;
953 if cpos + 2 + cap_len > value.len() {
954 break;
955 }
956 let mut cap = DissectionNode::new(
957 "bgp.open.capability",
958 format!("Capability: {} ({cap_code})", capability_name(cap_code)),
959 value_base + cpos as u32,
960 (2 + cap_len) as u32,
961 );
962 cap.children.push(DissectionNode::new(
963 "bgp.open.capability.code",
964 format!("Code: {cap_code} ({})", capability_name(cap_code)),
965 value_base + cpos as u32,
966 1,
967 ));
968 cap.children.push(DissectionNode::new(
969 "bgp.open.capability.length",
970 format!("Length: {cap_len}"),
971 value_base + cpos as u32 + 1,
972 1,
973 ));
974 param.children.push(cap);
975 cpos += 2 + cap_len;
976 }
977 } else {
978 param.children.push(DissectionNode::new(
979 "bgp.open.param.value",
980 format!("Value ({value_len} bytes)"),
981 value_base,
982 value_len as u32,
983 ));
984 }
985
986 open.children.push(param);
987 pos += header_len + value_len;
988 }
989
990 open
991}
992
993fn capability_name(code: u8) -> &'static str {
994 match code {
995 1 => "Multiprotocol Extensions",
996 2 => "Route Refresh",
997 4 => "Multiple Routes to a Destination",
998 5 => "Extended Next Hop Encoding",
999 6 => "BGP Extended Message",
1000 7 => "BGP Role",
1001 64 => "Graceful Restart",
1002 65 => "Support for 4-octet AS Number",
1003 66 => "Support for Add-Path",
1004 67 => "Enhanced Route Refresh",
1005 68 => "Long-Lived Graceful Restart",
1006 _ => "Unknown",
1007 }
1008}
1009
1010fn dissect_notification(data: &[u8], base: u32) -> DissectionNode {
1011 let mut notification =
1012 DissectionNode::new("bgp.notification", "NOTIFICATION", base, data.len() as u32);
1013 if let Some(code) = data.first() {
1014 notification.children.push(DissectionNode::new(
1015 "bgp.notification.code",
1016 format!("Error code: {code}"),
1017 base,
1018 1,
1019 ));
1020 }
1021 if data.len() >= 2 {
1022 notification.children.push(DissectionNode::new(
1023 "bgp.notification.subcode",
1024 format!("Error subcode: {}", data[1]),
1025 base + 1,
1026 1,
1027 ));
1028 }
1029 if data.len() > 2 {
1030 notification.children.push(DissectionNode::new(
1031 "bgp.notification.data",
1032 format!("Data ({} bytes)", data.len() - 2),
1033 base + 2,
1034 (data.len() - 2) as u32,
1035 ));
1036 }
1037 notification
1038}
1039
1040fn dissect_route_refresh(data: &[u8], base: u32) -> DissectionNode {
1041 let mut refresh = DissectionNode::new(
1042 "bgp.route_refresh",
1043 "ROUTE-REFRESH",
1044 base,
1045 data.len() as u32,
1046 );
1047 if data.len() >= 4 {
1048 let afi = read_u16(data, 0).unwrap_or(0);
1049 refresh.children.push(DissectionNode::new(
1050 "bgp.route_refresh.afi",
1051 format!("AFI: {afi}"),
1052 base,
1053 2,
1054 ));
1055 refresh.children.push(DissectionNode::new(
1056 "bgp.route_refresh.subtype",
1057 format!("Subtype: {}", data[2]),
1058 base + 2,
1059 1,
1060 ));
1061 refresh.children.push(DissectionNode::new(
1062 "bgp.route_refresh.safi",
1063 format!("SAFI: {}", data[3]),
1064 base + 3,
1065 1,
1066 ));
1067 }
1068 if data.len() > 4 {
1069 refresh.children.push(DissectionNode::new(
1070 "bgp.route_refresh.data",
1071 format!("ORF data ({} bytes)", data.len() - 4),
1072 base + 4,
1073 (data.len() - 4) as u32,
1074 ));
1075 }
1076 refresh
1077}
1078
1079#[cfg(test)]
1080mod tests {
1081 use super::*;
1082
1083 fn bgp_wire(msg_type: u8, body: &[u8]) -> Vec<u8> {
1085 let mut wire = vec![0xFF; 16];
1086 let total = (19 + body.len()) as u16;
1087 wire.extend_from_slice(&total.to_be_bytes());
1088 wire.push(msg_type);
1089 wire.extend_from_slice(body);
1090 wire
1091 }
1092
1093 fn sample_update_body() -> Vec<u8> {
1096 let mut body = Vec::new();
1097 let withdrawn: [u8; 4] = [24, 192, 0, 2];
1099 body.extend_from_slice(&(withdrawn.len() as u16).to_be_bytes());
1100 body.extend_from_slice(&withdrawn);
1101
1102 let mut attrs = Vec::new();
1103 attrs.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]); attrs.extend_from_slice(&[0x40, 0x02, 0x0A, 0x02, 0x02]);
1106 attrs.extend_from_slice(&65001u32.to_be_bytes());
1107 attrs.extend_from_slice(&65002u32.to_be_bytes());
1108 attrs.extend_from_slice(&[0x40, 0x03, 0x04, 192, 0, 2, 254]);
1110 let mut comms = Vec::new();
1112 comms.extend_from_slice(&64512u16.to_be_bytes());
1113 comms.extend_from_slice(&100u16.to_be_bytes());
1114 comms.extend_from_slice(&64512u16.to_be_bytes());
1115 comms.extend_from_slice(&200u16.to_be_bytes());
1116 attrs.push(0xC0);
1117 attrs.push(0x08);
1118 attrs.push(comms.len() as u8);
1119 attrs.extend_from_slice(&comms);
1120 attrs.push(0xC0);
1122 attrs.push(0x20);
1123 attrs.push(12);
1124 attrs.extend_from_slice(&64496u32.to_be_bytes());
1125 attrs.extend_from_slice(&1u32.to_be_bytes());
1126 attrs.extend_from_slice(&2u32.to_be_bytes());
1127
1128 body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1129 body.extend_from_slice(&attrs);
1130 body.extend_from_slice(&[24, 203, 0, 113]);
1132 body
1133 }
1134
1135 fn find_node<'a>(node: &'a DissectionNode, field: &str) -> &'a DissectionNode {
1136 node.find(field)
1137 .unwrap_or_else(|| panic!("node {field} not found in tree"))
1138 }
1139
1140 #[test]
1141 fn dissect_update_field_offsets() {
1142 let wire = bgp_wire(2, &sample_update_body());
1143 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1144
1145 assert_eq!(tree.field, "bgp");
1146 assert_eq!(tree.offset, 0);
1147 assert_eq!(tree.length, wire.len() as u32);
1148
1149 let marker = find_node(&tree, "bgp.header.marker");
1150 assert_eq!((marker.offset, marker.length), (0, 16));
1151 let length = find_node(&tree, "bgp.header.length");
1152 assert_eq!((length.offset, length.length), (16, 2));
1153 assert_eq!(length.label, format!("Length: {}", wire.len()));
1154 let msg_type = find_node(&tree, "bgp.header.type");
1155 assert_eq!((msg_type.offset, msg_type.length), (18, 1));
1156
1157 let wlen = find_node(&tree, "bgp.update.withdrawn_routes.length");
1159 assert_eq!((wlen.offset, wlen.length), (19, 2));
1160 assert_eq!(wlen.label, "Withdrawn routes length: 4");
1161 let wr = find_node(&tree, "bgp.update.withdrawn_routes");
1162 assert_eq!((wr.offset, wr.length), (21, 4));
1163 let wprefix = find_node(&tree, "bgp.nlri.prefix");
1164 assert_eq!((wprefix.offset, wprefix.length), (21, 4));
1165 assert_eq!(wprefix.label, "192.0.2.0/24");
1166
1167 let alen = find_node(&tree, "bgp.update.path_attributes.length");
1169 assert_eq!((alen.offset, alen.length), (25, 2));
1170 let attrs = find_node(&tree, "bgp.update.path_attributes");
1171 assert_eq!(attrs.offset, 27);
1172
1173 let as_path = find_node(&tree, "bgp.attr.2");
1175 assert_eq!((as_path.offset, as_path.length), (31, 13));
1176 let segment = find_node(&tree, "bgp.attr.as_path.segment");
1177 assert_eq!((segment.offset, segment.length), (34, 10));
1178 assert_eq!(segment.label, "AS_SEQUENCE: 65001 65002");
1179
1180 let comms = find_node(&tree, "bgp.attr.8");
1182 assert_eq!((comms.offset, comms.length), (51, 11));
1183 let mut entries = Vec::new();
1184 tree.find_all("bgp.attr.communities.entry", &mut entries);
1185 assert_eq!(entries.len(), 2);
1186 assert_eq!((entries[0].offset, entries[0].length), (54, 4));
1187 assert_eq!(entries[0].label, "Community [1]: 64512:100");
1188 assert_eq!(entries[1].label, "Community [2]: 64512:200");
1189
1190 let large = find_node(&tree, "bgp.attr.32");
1192 assert_eq!((large.offset, large.length), (62, 15));
1193 let entry = find_node(&tree, "bgp.attr.large_communities.entry");
1194 assert_eq!(entry.label, "Large community [1]: 64496:1:2");
1195
1196 let nlri = find_node(&tree, "bgp.update.nlri");
1198 assert_eq!(nlri.offset, 77);
1199 assert_eq!(nlri.length, 4);
1200 }
1201
1202 #[test]
1203 fn dissect_update_mp_reach() {
1204 let mut value = Vec::new();
1206 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.push(16); value.extend_from_slice(&[
1210 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01,
1211 ]);
1212 value.push(0); value.extend_from_slice(&[32, 0x20, 0x01, 0x0d, 0xb8]); let mut attrs = Vec::new();
1216 attrs.push(0x80);
1217 attrs.push(14);
1218 attrs.push(value.len() as u8);
1219 attrs.extend_from_slice(&value);
1220
1221 let mut body = Vec::new();
1222 body.extend_from_slice(&0u16.to_be_bytes()); body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1224 body.extend_from_slice(&attrs);
1225
1226 let wire = bgp_wire(2, &body);
1227 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1228
1229 let mp_reach = find_node(&tree, "bgp.attr.14");
1230 let afi = find_node(&tree, "bgp.attr.mp_reach.afi");
1231 assert_eq!(afi.label, "AFI: 2 (IPv6)");
1232 let nh = find_node(&tree, "bgp.attr.mp_reach.next_hop");
1233 assert_eq!(nh.length, 16);
1234 let prefix = tree.find("bgp.update.nlri");
1235 assert!(prefix.is_none(), "IPv6 NLRI lives inside MP_REACH");
1236 let mut prefixes = Vec::new();
1237 tree.find_all("bgp.nlri.prefix", &mut prefixes);
1238 assert_eq!(prefixes.len(), 1);
1239 assert_eq!(prefixes[0].label, "2001:db8::/32");
1240 assert_eq!(mp_reach.length, 3 + value.len() as u32);
1241 }
1242
1243 #[test]
1244 fn dissect_update_truncated_attribute_is_partial() {
1245 let mut body = Vec::new();
1247 body.extend_from_slice(&0u16.to_be_bytes());
1248 let attrs = [0x40u8, 0x02, 0x0A, 0x02, 0x02, 0x0F, 0x9A]; body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1250 body.extend_from_slice(&attrs);
1251
1252 let wire = bgp_wire(2, &body);
1253 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1254
1255 let as_path = find_node(&tree, "bgp.attr.2");
1256 assert!(as_path.label.contains("truncated"));
1257 assert_eq!(as_path.length, attrs.len() as u32);
1258 }
1259
1260 #[test]
1261 fn dissect_open_capabilities() {
1262 let mut caps = Vec::new();
1265 caps.extend_from_slice(&[1, 4, 0, 1, 0, 1]); caps.extend_from_slice(&[65, 4, 0, 0, 0xFD, 0xE9]); let mut body = Vec::new();
1269 body.push(4);
1270 body.extend_from_slice(&65001u16.to_be_bytes());
1271 body.extend_from_slice(&180u16.to_be_bytes());
1272 body.extend_from_slice(&[1, 2, 3, 4]);
1273 body.push(2 + caps.len() as u8); body.push(2); body.push(caps.len() as u8);
1276 body.extend_from_slice(&caps);
1277
1278 let wire = bgp_wire(1, &body);
1279 let tree = dissect_bgp_message(&wire, &AsnLength::Bits16, false);
1280
1281 let version = find_node(&tree, "bgp.open.version");
1282 assert_eq!((version.offset, version.length), (19, 1));
1283 let asn = find_node(&tree, "bgp.open.asn");
1284 assert_eq!(asn.label, "My AS: 65001");
1285 let mut capabilities = Vec::new();
1286 tree.find_all("bgp.open.capability", &mut capabilities);
1287 assert_eq!(capabilities.len(), 2);
1288 assert_eq!(
1289 capabilities[0].label,
1290 "Capability: Multiprotocol Extensions (1)"
1291 );
1292 assert_eq!(
1293 capabilities[1].label,
1294 "Capability: Support for 4-octet AS Number (65)"
1295 );
1296 assert_eq!(capabilities[0].offset, 19 + 10 + 2);
1297 }
1298
1299 #[test]
1300 fn dissect_notification_and_route_refresh() {
1301 let notification = bgp_wire(3, &[6, 5, 0xDE, 0xAD]);
1302 let tree = dissect_bgp_message(¬ification, &AsnLength::Bits16, false);
1303 let code = find_node(&tree, "bgp.notification.code");
1304 assert_eq!((code.offset, code.length), (19, 1));
1305 let data = find_node(&tree, "bgp.notification.data");
1306 assert_eq!(data.length, 2);
1307
1308 let refresh = bgp_wire(5, &[0, 1, 0, 1, 0xDE, 0xAD, 0xBE]);
1309 let tree = dissect_bgp_message(&refresh, &AsnLength::Bits16, false);
1310 let subtype = find_node(&tree, "bgp.route_refresh.subtype");
1311 assert_eq!(subtype.label, "Subtype: 0");
1312 let orf = find_node(&tree, "bgp.route_refresh.data");
1313 assert_eq!(orf.length, 3);
1314 }
1315
1316 #[test]
1317 fn dissect_keepalive_and_truncated_header() {
1318 let keepalive = bgp_wire(4, &[]);
1319 let tree = dissect_bgp_message(&keepalive, &AsnLength::Bits16, false);
1320 assert_eq!(tree.children.len(), 1, "header only, no body node");
1321 assert!(tree.find("bgp.header.type").is_some());
1322
1323 let truncated = dissect_bgp_message(&[0xFF; 7], &AsnLength::Bits16, false);
1324 let header = find_node(&truncated, "bgp.header");
1325 assert!(header.label.contains("truncated"));
1326 }
1327
1328 #[test]
1329 fn dissect_attribute_value_zoo() {
1330 let mut attrs = Vec::new();
1335 let push_attr = |attrs: &mut Vec<u8>, flags: u8, code: u8, value: &[u8]| {
1336 attrs.push(flags);
1337 attrs.push(code);
1338 attrs.push(value.len() as u8);
1339 attrs.extend_from_slice(value);
1340 };
1341
1342 push_attr(&mut attrs, 0x40, 1, &[0x01]);
1344 push_attr(&mut attrs, 0x40, 3, &[10, 0, 0, 1]);
1346 push_attr(&mut attrs, 0x80, 4, &100u32.to_be_bytes());
1348 push_attr(&mut attrs, 0x40, 5, &200u32.to_be_bytes());
1350 push_attr(&mut attrs, 0x40, 6, &[]);
1352 push_attr(&mut attrs, 0xC0, 7, &[0xFC, 0x80, 1, 2, 3, 4]);
1354 push_attr(&mut attrs, 0x80, 9, &[10, 0, 0, 9]);
1356 push_attr(&mut attrs, 0x80, 10, &0xAABBCCDDu32.to_be_bytes());
1358 push_attr(
1360 &mut attrs,
1361 0xC0,
1362 16,
1363 &[0x00, 0x02, 0xFC, 0x80, 0, 0, 0, 100],
1364 );
1365 push_attr(&mut attrs, 0xC0, 16, &[0x03, 0x04, 0, 0, 0, 0, 0, 1]);
1366 let as4_aggregator: Vec<u8> =
1368 [4200000000u32.to_be_bytes().to_vec(), vec![1, 2, 3, 4]].concat();
1369 push_attr(&mut attrs, 0xC0, 18, &as4_aggregator);
1370 push_attr(&mut attrs, 0xC0, 25, &[0x00; 20]);
1372 push_attr(
1374 &mut attrs,
1375 0x80,
1376 26,
1377 &[0x01, 0x00, 0x0B, 0, 0, 0, 0, 0, 0, 0, 100],
1378 );
1379 push_attr(&mut attrs, 0xC0, 35, &64512u32.to_be_bytes());
1381 push_attr(&mut attrs, 0x80, 99, &[0xAB, 0xCD]);
1383
1384 let mut body = Vec::new();
1385 body.extend_from_slice(&0u16.to_be_bytes());
1386 body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1387 body.extend_from_slice(&attrs);
1388 let wire = bgp_wire(2, &body);
1389 let tree = dissect_bgp_message(&wire, &AsnLength::Bits16, false);
1390
1391 assert_eq!(find_node(&tree, "bgp.attr.origin").label, "Origin: EGP (1)");
1392 assert_eq!(
1393 find_node(&tree, "bgp.attr.next_hop").label,
1394 "Next hop: 10.0.0.1"
1395 );
1396 assert_eq!(
1397 find_node(&tree, "bgp.attr.u32").label,
1398 "Multi-exit discriminator: 100"
1399 );
1400 assert_eq!(
1401 find_node(&tree, "bgp.attr.aggregator.asn").label,
1402 "Aggregator ASN: 64640"
1403 );
1404 assert_eq!(
1405 find_node(&tree, "bgp.attr.aggregator.id").label,
1406 "Aggregator router ID: 1.2.3.4"
1407 );
1408 assert_eq!(
1409 find_node(&tree, "bgp.attr.originator_id").label,
1410 "Originator ID: 10.0.0.9"
1411 );
1412 let cluster = find_node(&tree, "bgp.attr.cluster_list.entry");
1413 assert!(cluster.label.contains("2864434397"));
1414 let mut ext = Vec::new();
1415 tree.find_all("bgp.attr.ext_communities.entry", &mut ext);
1416 assert_eq!(ext.len(), 2);
1417 assert_eq!(
1418 ext[0].label,
1419 "Extended community [1]: type 0x00 subtype 0x02: 64640:100"
1420 );
1421 assert!(ext[1].label.contains("type 0x03 subtype 0x04"));
1422 assert_eq!(
1423 find_node(&tree, "bgp.attr.ipv6_ext_communities.entry").label,
1424 "IPv6 extended community [1]"
1425 );
1426 let aigp = find_node(&tree, "bgp.attr.aigp.entry");
1429 assert_eq!(aigp.label, "AIGP TLV: type 1, 11 bytes");
1430 assert_eq!(
1431 (aigp.offset, aigp.length),
1432 (find_node(&tree, "bgp.attr.26").offset + 3, 11)
1433 );
1434 let mut u32s = Vec::new();
1436 tree.find_all("bgp.attr.u32", &mut u32s);
1437 assert!(u32s.iter().any(|n| n.label == "Only to customer: 64512"));
1438 assert!(find_node(&tree, "bgp.attr.99")
1440 .label
1441 .starts_with("ATTRIBUTE (type 99)"));
1442 }
1443
1444 #[test]
1445 fn dissect_as4_path_uses_four_octet_asns_even_on_16bit_sessions() {
1446 let value: Vec<u8> = [
1449 &[0x02, 0x02][..],
1450 &65001u32.to_be_bytes(),
1451 &65002u32.to_be_bytes(),
1452 ]
1453 .concat();
1454 let mut attrs = Vec::new();
1455 attrs.extend_from_slice(&[0x40, 17, value.len() as u8]);
1456 attrs.extend_from_slice(&value);
1457
1458 let mut body = Vec::new();
1459 body.extend_from_slice(&0u16.to_be_bytes());
1460 body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1461 body.extend_from_slice(&attrs);
1462 let wire = bgp_wire(2, &body);
1463 let tree = dissect_bgp_message(&wire, &AsnLength::Bits16, false);
1464
1465 let segment = find_node(&tree, "bgp.attr.as_path.segment");
1466 assert_eq!((segment.offset, segment.length), (23 + 3, 10));
1467 assert_eq!(segment.label, "AS_SEQUENCE: 65001 65002");
1468 }
1469
1470 #[test]
1471 fn dissect_extended_length_attribute() {
1472 let mut value = vec![0u8; 300];
1474 value[0] = 1; let mut attrs = Vec::new();
1476 attrs.push(0x50); attrs.push(99);
1478 attrs.extend_from_slice(&(value.len() as u16).to_be_bytes());
1479 attrs.extend_from_slice(&value);
1480
1481 let mut body = Vec::new();
1482 body.extend_from_slice(&0u16.to_be_bytes());
1483 body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1484 body.extend_from_slice(&attrs);
1485 let wire = bgp_wire(2, &body);
1486 let tree = dissect_bgp_message(&wire, &AsnLength::Bits16, false);
1487
1488 let attr = find_node(&tree, "bgp.attr.99");
1489 assert_eq!(attr.length, 4 + 300);
1490 let length = find_node(&tree, "bgp.attr.length");
1491 assert_eq!(length.label, "Length: 300");
1492 assert_eq!(length.length, 2);
1493 }
1494
1495 #[test]
1496 fn dissect_declared_length_bounds_body() {
1497 let mut wire = bgp_wire(4, &[]);
1500 wire.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x42]);
1501 let tree = dissect_bgp_message(&wire, &AsnLength::Bits16, false);
1502
1503 assert_eq!(tree.length, 19, "root bounded to the declared message");
1504 let trailing = find_node(&tree, "bgp.trailing");
1505 assert_eq!((trailing.offset, trailing.length), (19, 5));
1506
1507 let body = sample_update_body();
1510 let mut wire = bgp_wire(2, &body);
1511 let full_len = u16::from_be_bytes([wire[16], wire[17]]);
1513 let shorter = full_len - 4;
1514 wire[16] = (shorter >> 8) as u8;
1515 wire[17] = (shorter & 0xFF) as u8;
1516 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1517 assert!(tree.find("bgp.update.nlri").is_none());
1518 assert_eq!(find_node(&tree, "bgp.trailing").length, 4);
1519
1520 let mut wire = bgp_wire(2, &body);
1522 wire.truncate(wire.len() - 10);
1523 let inflated = wire.len() as u16 + 20;
1524 wire[16] = (inflated >> 8) as u8;
1525 wire[17] = (inflated & 0xFF) as u8;
1526 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1527 let truncated = find_node(&tree, "bgp.truncated");
1528 assert!(truncated.label.contains("exceeds"));
1529 }
1530
1531 #[test]
1532 fn dissect_mp_unreach_and_truncated_next_hop() {
1533 let mut value = Vec::new();
1535 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&[32, 0x20, 0x01, 0x0d, 0xb8]); let mut attrs = Vec::new();
1539 attrs.extend_from_slice(&[0x80, 15, value.len() as u8]);
1540 attrs.extend_from_slice(&value);
1541 let mut body = Vec::new();
1542 body.extend_from_slice(&0u16.to_be_bytes());
1543 body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1544 body.extend_from_slice(&attrs);
1545 let wire = bgp_wire(2, &body);
1546 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1547
1548 assert!(tree.find("bgp.attr.mp_unreach.afi").is_some());
1549 let mut prefixes = Vec::new();
1550 tree.find_all("bgp.nlri.prefix", &mut prefixes);
1551 assert_eq!(prefixes[0].label, "2001:db8::/32");
1552
1553 let mut value = Vec::new();
1556 value.extend_from_slice(&2u16.to_be_bytes());
1557 value.push(1);
1558 value.push(16); value.extend_from_slice(&[0x20, 0x01]); let mut attrs = Vec::new();
1561 attrs.extend_from_slice(&[0x80, 14, value.len() as u8]);
1562 attrs.extend_from_slice(&value);
1563 let mut body = Vec::new();
1564 body.extend_from_slice(&0u16.to_be_bytes());
1565 body.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
1566 body.extend_from_slice(&attrs);
1567 let wire = bgp_wire(2, &body);
1568 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1569
1570 let next_hop = find_node(&tree, "bgp.attr.mp_reach.next_hop");
1571 assert!(next_hop.label.contains("truncated: 2 of 16"));
1572 assert!(tree.find("bgp.attr.mp_reach.reserved").is_none());
1573 assert!(tree.find("bgp.nlri.prefix").is_none());
1574 }
1575
1576 #[test]
1577 fn dissect_open_extended_params_and_raw_param() {
1578 let mut caps = Vec::new();
1580 caps.extend_from_slice(&[65, 4]);
1581 caps.extend_from_slice(&65001u32.to_be_bytes()); let caps_param = [
1584 vec![2u8],
1585 (caps.len() as u16).to_be_bytes().to_vec(),
1586 caps.clone(),
1587 ]
1588 .concat();
1589 let raw_param = [
1590 vec![254u8],
1591 3u16.to_be_bytes().to_vec(),
1592 vec![0xAA, 0xBB, 0xCC],
1593 ]
1594 .concat();
1595 let mut params = Vec::new();
1596 params.push(255);
1597 let params_len = 3 + caps_param.len() + raw_param.len();
1598 params.extend_from_slice(&(params_len as u16).to_be_bytes());
1599 params.extend_from_slice(&caps_param);
1600 params.extend_from_slice(&raw_param);
1601
1602 let mut body = Vec::new();
1603 body.push(4);
1604 body.extend_from_slice(&65001u16.to_be_bytes());
1605 body.extend_from_slice(&180u16.to_be_bytes());
1606 body.extend_from_slice(&[1, 2, 3, 4]);
1607 body.push(0xFF); body.extend_from_slice(¶ms);
1609
1610 let wire = bgp_wire(1, &body);
1611 let tree = dissect_bgp_message(&wire, &AsnLength::Bits16, false);
1612
1613 let ext = find_node(&tree, "bgp.open.ext_params_len");
1614 assert!(ext.label.contains("RFC 9072"));
1615 assert!(tree.find("bgp.open.capability").is_some());
1616 let raw = find_node(&tree, "bgp.open.param.value");
1617 assert_eq!(raw.length, 3);
1618 }
1619
1620 #[test]
1621 fn dissect_truncated_update_sections() {
1622 let mut body = Vec::new();
1624 body.extend_from_slice(&8u16.to_be_bytes()); body.extend_from_slice(&[24, 192, 0, 2]); let wire = bgp_wire(2, &body);
1627 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1628 let wr = find_node(&tree, "bgp.update.withdrawn_routes");
1629 assert!(wr.label.contains("truncated: 4 of 8"));
1630
1631 let mut body = Vec::new();
1633 body.extend_from_slice(&0u16.to_be_bytes());
1634 body.push(0x40); let wire = bgp_wire(2, &body);
1636 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1637 let alen = find_node(&tree, "bgp.update.path_attributes.length");
1638 assert_eq!(alen.label, "Total path attribute length (truncated)");
1639
1640 let mut body = Vec::new();
1642 body.extend_from_slice(&0u16.to_be_bytes());
1643 body.extend_from_slice(&0u16.to_be_bytes());
1644 body.extend_from_slice(&[24, 203, 0]); let wire = bgp_wire(2, &body);
1646 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, false);
1647 assert!(tree.find("bgp.nlri.prefix").is_none());
1648 }
1649
1650 #[test]
1651 fn dissect_addpath_nlri() {
1652 let nlri = [0, 0, 0, 7, 24, 203, 0, 113];
1654 let mut body = Vec::new();
1655 body.extend_from_slice(&0u16.to_be_bytes());
1656 body.extend_from_slice(&0u16.to_be_bytes());
1657 body.extend_from_slice(&nlri);
1658
1659 let wire = bgp_wire(2, &body);
1660 let tree = dissect_bgp_message(&wire, &AsnLength::Bits32, true);
1661 let mut prefixes = Vec::new();
1662 tree.find_all("bgp.nlri.prefix", &mut prefixes);
1663 assert_eq!(prefixes.len(), 1);
1664 assert_eq!((prefixes[0].offset, prefixes[0].length), (23, 8));
1665 assert_eq!(prefixes[0].label, "203.0.113.0/24");
1666 }
1667}