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