1 use std::{cmp::Ordering, collections::HashMap, fmt}; 2 3 use crate::commons::{ 4 api::{BgpStats, RoaDefinition, RoaDefinitionUpdates}, 5 bgp::Announcement, 6 }; 7 8 //------------ BgpAnalysisAdvice ------------------------------------------- 9 10 #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] 11 pub struct BgpAnalysisAdvice { 12 effect: BgpAnalysisReport, 13 suggestion: BgpAnalysisSuggestion, 14 } 15 16 impl BgpAnalysisAdvice { new(effect: BgpAnalysisReport, suggestion: BgpAnalysisSuggestion) -> Self17 pub fn new(effect: BgpAnalysisReport, suggestion: BgpAnalysisSuggestion) -> Self { 18 BgpAnalysisAdvice { effect, suggestion } 19 } 20 effect(&self) -> &BgpAnalysisReport21 pub fn effect(&self) -> &BgpAnalysisReport { 22 &self.effect 23 } 24 suggestion(&self) -> &BgpAnalysisSuggestion25 pub fn suggestion(&self) -> &BgpAnalysisSuggestion { 26 &self.suggestion 27 } 28 } 29 30 impl fmt::Display for BgpAnalysisAdvice { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 writeln!(f, "Unsafe update, please review")?; 33 writeln!(f)?; 34 writeln!(f, "Effect would leave the following invalids:")?; 35 36 let invalid_asns = self.effect().matching_defs(BgpAnalysisState::AnnouncementInvalidAsn); 37 if !invalid_asns.is_empty() { 38 writeln!(f)?; 39 writeln!(f, " Announcements from invalid ASNs:")?; 40 for invalid in invalid_asns { 41 writeln!(f, " {}\n", invalid)?; 42 } 43 } 44 45 let invalid_length = self.effect().matching_defs(BgpAnalysisState::AnnouncementInvalidLength); 46 if !invalid_length.is_empty() { 47 writeln!(f)?; 48 writeln!(f, " Announcements too specific for their ASNs:\n")?; 49 for invalid in invalid_length { 50 writeln!(f, " {}", invalid)?; 51 } 52 } 53 54 writeln!(f)?; 55 writeln!(f, "You may want to consider this alternative:")?; 56 writeln!(f, "{}", self.suggestion())?; 57 58 Ok(()) 59 } 60 } 61 62 //------------ BgpAnalysisSuggestion --------------------------------------- 63 64 #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] 65 pub struct BgpAnalysisSuggestion { 66 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 67 stale: Vec<RoaDefinition>, 68 69 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 70 not_found: Vec<Announcement>, 71 72 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 73 invalid_asn: Vec<Announcement>, 74 75 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 76 invalid_length: Vec<Announcement>, 77 78 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 79 too_permissive: Vec<ReplacementRoaSuggestion>, 80 81 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 82 disallowing: Vec<RoaDefinition>, 83 84 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 85 redundant: Vec<RoaDefinition>, 86 87 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 88 not_held: Vec<RoaDefinition>, 89 90 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 91 as0_redundant: Vec<RoaDefinition>, 92 93 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 94 keep: Vec<RoaDefinition>, 95 96 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 97 keep_disallowing: Vec<Announcement>, 98 } 99 100 #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] 101 pub struct ReplacementRoaSuggestion { 102 current: RoaDefinition, 103 new: Vec<RoaDefinition>, 104 } 105 106 impl From<BgpAnalysisSuggestion> for RoaDefinitionUpdates { from(suggestion: BgpAnalysisSuggestion) -> Self107 fn from(suggestion: BgpAnalysisSuggestion) -> Self { 108 let (stale, not_found, invalid_asn, invalid_length, too_permissive, as0_redundant, redundant) = ( 109 suggestion.stale, 110 suggestion.not_found, 111 suggestion.invalid_asn, 112 suggestion.invalid_length, 113 suggestion.too_permissive, 114 suggestion.as0_redundant, 115 suggestion.redundant, 116 ); 117 118 let mut added: Vec<RoaDefinition> = vec![]; 119 let mut removed: Vec<RoaDefinition> = vec![]; 120 121 for auth in not_found 122 .into_iter() 123 .chain(invalid_asn.into_iter()) 124 .chain(invalid_length.into_iter()) 125 { 126 added.push(auth.into()); 127 } 128 129 for auth in stale.into_iter() { 130 removed.push(auth); 131 } 132 133 for suggestion in too_permissive.into_iter() { 134 removed.push(suggestion.current); 135 for auth in suggestion.new.into_iter() { 136 added.push(auth); 137 } 138 } 139 140 for auth in as0_redundant.into_iter() { 141 removed.push(auth); 142 } 143 144 for auth in redundant.into_iter() { 145 removed.push(auth); 146 } 147 148 RoaDefinitionUpdates::new(added, removed) 149 } 150 } 151 152 impl Default for BgpAnalysisSuggestion { default() -> Self153 fn default() -> Self { 154 BgpAnalysisSuggestion { 155 stale: vec![], 156 not_found: vec![], 157 invalid_asn: vec![], 158 invalid_length: vec![], 159 too_permissive: vec![], 160 disallowing: vec![], 161 redundant: vec![], 162 not_held: vec![], 163 keep: vec![], 164 as0_redundant: vec![], 165 keep_disallowing: vec![], 166 } 167 } 168 } 169 170 impl BgpAnalysisSuggestion { add_stale(&mut self, authorization: RoaDefinition)171 pub fn add_stale(&mut self, authorization: RoaDefinition) { 172 self.stale.push(authorization); 173 } 174 add_too_permissive(&mut self, current: RoaDefinition, new: Vec<RoaDefinition>)175 pub fn add_too_permissive(&mut self, current: RoaDefinition, new: Vec<RoaDefinition>) { 176 let replacement = ReplacementRoaSuggestion { current, new }; 177 self.too_permissive.push(replacement); 178 } 179 add_not_found(&mut self, announcement: Announcement)180 pub fn add_not_found(&mut self, announcement: Announcement) { 181 self.not_found.push(announcement); 182 } 183 add_invalid_asn(&mut self, announcement: Announcement)184 pub fn add_invalid_asn(&mut self, announcement: Announcement) { 185 self.invalid_asn.push(announcement); 186 } 187 add_invalid_length(&mut self, announcement: Announcement)188 pub fn add_invalid_length(&mut self, announcement: Announcement) { 189 self.invalid_length.push(announcement); 190 } 191 add_disallowing(&mut self, authorization: RoaDefinition)192 pub fn add_disallowing(&mut self, authorization: RoaDefinition) { 193 self.disallowing.push(authorization); 194 } 195 add_redundant(&mut self, authorization: RoaDefinition)196 pub fn add_redundant(&mut self, authorization: RoaDefinition) { 197 self.redundant.push(authorization); 198 } 199 add_not_held(&mut self, authorization: RoaDefinition)200 pub fn add_not_held(&mut self, authorization: RoaDefinition) { 201 self.not_held.push(authorization); 202 } 203 add_as0_redundant(&mut self, authorization: RoaDefinition)204 pub fn add_as0_redundant(&mut self, authorization: RoaDefinition) { 205 self.as0_redundant.push(authorization); 206 } 207 add_keep(&mut self, authorization: RoaDefinition)208 pub fn add_keep(&mut self, authorization: RoaDefinition) { 209 self.keep.push(authorization); 210 } 211 add_keep_disallowing(&mut self, announcement: Announcement)212 pub fn add_keep_disallowing(&mut self, announcement: Announcement) { 213 self.keep_disallowing.push(announcement); 214 } 215 } 216 217 #[allow(clippy::cognitive_complexity)] 218 impl fmt::Display for BgpAnalysisSuggestion { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result219 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 220 if !self.stale.is_empty() { 221 writeln!(f, "Remove the following stale entries:")?; 222 for auth in &self.stale { 223 writeln!(f, " {}", auth)?; 224 } 225 writeln!(f)?; 226 } 227 228 if !self.too_permissive.is_empty() { 229 writeln!(f, "Replace the following too permissive entries:")?; 230 for entry in &self.too_permissive { 231 writeln!(f, " Remove: {}", entry.current)?; 232 for replace in &entry.new { 233 writeln!(f, " Add: {}", replace)?; 234 } 235 writeln!(f)?; 236 } 237 } 238 239 if !self.as0_redundant.is_empty() { 240 writeln!( 241 f, 242 "Remove the following AS0 ROAs made redundant by ROAs for the same prefix and a real ASN:" 243 )?; 244 for auth in &self.as0_redundant { 245 writeln!(f, " {}", auth)?; 246 } 247 writeln!(f)?; 248 } 249 250 if !self.redundant.is_empty() { 251 writeln!( 252 f, 253 "Remove the following ROAs made redundant by a covering ROA using max length:" 254 )?; 255 for auth in &self.redundant { 256 writeln!(f, " {}", auth)?; 257 } 258 writeln!(f)?; 259 } 260 261 if !self.disallowing.is_empty() { 262 writeln!( 263 f, 264 "Remove the following ROAs which only disallow announcements (did you use the wrong ASN?), if this is intended you may want to use AS0 instead:" 265 )?; 266 for auth in &self.disallowing { 267 writeln!(f, " {}", auth)?; 268 } 269 writeln!(f)?; 270 } 271 272 if !self.keep.is_empty() { 273 writeln!(f, "Keep the following authorizations:")?; 274 for auth in &self.keep { 275 writeln!(f, " {}", auth)?; 276 } 277 writeln!(f)?; 278 } 279 280 if !self.not_found.is_empty() { 281 writeln!(f, "Authorize these announcements which are currently not covered:")?; 282 for auth in &self.not_found { 283 writeln!(f, " {}", auth)?; 284 } 285 writeln!(f)?; 286 } 287 288 if !self.invalid_length.is_empty() { 289 writeln!( 290 f, 291 "Authorize these announcements which are currently invalid because they are too specific:" 292 )?; 293 for auth in &self.invalid_length { 294 writeln!(f, " {}", auth)?; 295 } 296 writeln!(f)?; 297 } 298 299 if !self.invalid_asn.is_empty() { 300 writeln!( 301 f, 302 "Authorize these announcements which are currently invalid because they are not allowed for these ASNs:" 303 )?; 304 for auth in &self.invalid_asn { 305 writeln!(f, " {}", auth)?; 306 } 307 writeln!(f)?; 308 } 309 310 Ok(()) 311 } 312 } 313 314 //------------ BgpAnalysisReport ------------------------------------------- 315 316 #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] 317 pub struct BgpAnalysisReport(Vec<BgpAnalysisEntry>); 318 319 impl BgpAnalysisReport { new(mut roas: Vec<BgpAnalysisEntry>) -> Self320 pub fn new(mut roas: Vec<BgpAnalysisEntry>) -> Self { 321 roas.sort(); 322 BgpAnalysisReport(roas) 323 } 324 entries(&self) -> &Vec<BgpAnalysisEntry>325 pub fn entries(&self) -> &Vec<BgpAnalysisEntry> { 326 &self.0 327 } 328 into_entries(self) -> Vec<BgpAnalysisEntry>329 pub fn into_entries(self) -> Vec<BgpAnalysisEntry> { 330 self.0 331 } 332 matching_defs(&self, state: BgpAnalysisState) -> Vec<&RoaDefinition>333 pub fn matching_defs(&self, state: BgpAnalysisState) -> Vec<&RoaDefinition> { 334 self.matching_entries(state) 335 .into_iter() 336 .map(|e| &e.definition) 337 .collect() 338 } 339 matching_entries(&self, state: BgpAnalysisState) -> Vec<&BgpAnalysisEntry>340 pub fn matching_entries(&self, state: BgpAnalysisState) -> Vec<&BgpAnalysisEntry> { 341 self.0.iter().filter(|e| e.state == state).collect() 342 } 343 contains_invalids(&self) -> bool344 pub fn contains_invalids(&self) -> bool { 345 self.0.iter().any(|el| el.state().is_invalid()) 346 } 347 } 348 349 impl From<BgpAnalysisReport> for BgpStats { from(r: BgpAnalysisReport) -> BgpStats350 fn from(r: BgpAnalysisReport) -> BgpStats { 351 let mut stats = BgpStats::default(); 352 for e in r.0.iter() { 353 match e.state { 354 BgpAnalysisState::AnnouncementValid => stats.increment_valid(), 355 BgpAnalysisState::AnnouncementInvalidAsn => stats.increment_invalid_asn(), 356 BgpAnalysisState::AnnouncementInvalidLength => stats.increment_invalid_length(), 357 BgpAnalysisState::AnnouncementDisallowed => stats.increment_disallowed(), 358 BgpAnalysisState::AnnouncementNotFound => stats.increment_not_found(), 359 BgpAnalysisState::RoaUnseen => { 360 stats.increment_roas_total(); 361 stats.increment_roas_stale(); 362 } 363 BgpAnalysisState::RoaTooPermissive => { 364 stats.increment_roas_total(); 365 stats.increment_roas_too_permissive(); 366 } 367 BgpAnalysisState::RoaRedundant | BgpAnalysisState::RoaAs0Redundant => { 368 stats.increment_roas_total(); 369 stats.increment_roas_redundant(); 370 } 371 BgpAnalysisState::RoaNotHeld => { 372 stats.increment_roas_total(); 373 stats.increment_roas_not_held(); 374 } 375 BgpAnalysisState::RoaDisallowing => { 376 stats.increment_roas_total(); 377 stats.increment_roas_disallowing(); 378 } 379 BgpAnalysisState::RoaAs0 | BgpAnalysisState::RoaNoAnnouncementInfo | BgpAnalysisState::RoaSeen => { 380 stats.increment_roas_total() 381 } 382 } 383 } 384 stats 385 } 386 } 387 388 #[allow(clippy::cognitive_complexity)] 389 impl fmt::Display for BgpAnalysisReport { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result390 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 391 let entries = self.entries(); 392 393 let mut entry_map: HashMap<BgpAnalysisState, Vec<&BgpAnalysisEntry>> = HashMap::new(); 394 for entry in entries.iter() { 395 let state = entry.state(); 396 entry_map.entry(state).or_insert_with(Vec::new); 397 entry_map.get_mut(&state).unwrap().push(entry); 398 } 399 400 if entry_map.contains_key(&BgpAnalysisState::RoaNoAnnouncementInfo) { 401 write!(f, "no BGP announcements known") 402 } else { 403 if let Some(authorizing) = entry_map.get(&BgpAnalysisState::RoaSeen) { 404 writeln!(f, "Authorizations covering announcements seen:")?; 405 for roa in authorizing { 406 writeln!(f)?; 407 writeln!(f, "\tDefinition: {}", roa.definition)?; 408 writeln!(f)?; 409 writeln!(f, "\t\tAuthorizes:")?; 410 for ann in roa.authorizes.iter() { 411 writeln!(f, "\t\t{}", ann)?; 412 } 413 414 if !roa.disallows.is_empty() { 415 writeln!(f)?; 416 writeln!(f, "\t\tDisallows:")?; 417 for ann in roa.disallows.iter() { 418 writeln!(f, "\t\t{}", ann)?; 419 } 420 } 421 } 422 writeln!(f)?; 423 } 424 425 if let Some(redundant) = entry_map.get(&BgpAnalysisState::RoaRedundant) { 426 writeln!( 427 f, 428 "Authorizations which are *redundant* - they are already included in full by other authorizations:" 429 )?; 430 for roa in redundant { 431 writeln!(f)?; 432 writeln!(f, "\tDefinition: {}", roa.definition)?; 433 writeln!(f)?; 434 writeln!(f, "\t\tAuthorizes:")?; 435 for ann in roa.authorizes.iter() { 436 writeln!(f, "\t\t{}", ann)?; 437 } 438 439 if !roa.disallows.is_empty() { 440 writeln!(f)?; 441 writeln!(f, "\t\tDisallows:")?; 442 for ann in roa.disallows.iter() { 443 writeln!(f, "\t\t{}", ann)?; 444 } 445 } 446 447 writeln!(f)?; 448 writeln!(f, "\t\tMade redundant by:")?; 449 for redundant_by in roa.made_redundant_by.iter() { 450 writeln!(f, "\t\t{}", redundant_by)?; 451 } 452 } 453 writeln!(f)?; 454 } 455 456 if let Some(not_seen) = entry_map.get(&BgpAnalysisState::RoaUnseen) { 457 writeln!( 458 f, 459 "Authorizations for which no announcements are seen (you may wish to remove these):" 460 )?; 461 writeln!(f)?; 462 for roa in not_seen { 463 writeln!(f, "\tDefinition: {}", roa.definition)?; 464 } 465 writeln!(f)?; 466 } 467 468 if let Some(not_held) = entry_map.get(&BgpAnalysisState::RoaNotHeld) { 469 writeln!( 470 f, 471 "Authorizations for which no ROAs can be made - you do not have the prefix on your certificate(s):" 472 )?; 473 writeln!(f)?; 474 for roa in not_held { 475 writeln!(f, "\tDefinition: {}", roa.definition)?; 476 } 477 writeln!(f)?; 478 } 479 480 if let Some(disallowing) = entry_map.get(&BgpAnalysisState::RoaDisallowing) { 481 writeln!( 482 f, 483 "Authorizations disallowing announcements seen. You may want to use AS0 ROAs instead:" 484 )?; 485 for roa in disallowing { 486 writeln!(f)?; 487 writeln!(f, "\tDefinition: {}", roa.definition)?; 488 writeln!(f)?; 489 writeln!(f)?; 490 writeln!(f, "\t\tDisallows:")?; 491 for ann in roa.disallows.iter() { 492 writeln!(f, "\t\t{}", ann)?; 493 } 494 } 495 writeln!(f)?; 496 } 497 498 if let Some(too_permissive) = entry_map.get(&BgpAnalysisState::RoaTooPermissive) { 499 writeln!(f, "Authorizations which may be too permissive:")?; 500 501 for roa in too_permissive { 502 writeln!(f)?; 503 writeln!(f, "\tDefinition: {}", roa.definition)?; 504 writeln!(f)?; 505 writeln!(f, "\t\tAuthorizes visible announcements:")?; 506 for ann in roa.authorizes.iter() { 507 writeln!(f, "\t\t{}", ann)?; 508 } 509 510 if !roa.disallows.is_empty() { 511 writeln!(f)?; 512 writeln!(f, "\t\tDisallows:")?; 513 for ann in roa.disallows.iter() { 514 writeln!(f, "\t\t{}", ann)?; 515 } 516 } 517 } 518 writeln!(f)?; 519 } 520 521 if let Some(as0) = entry_map.get(&BgpAnalysisState::RoaAs0) { 522 writeln!(f, "AS0 Authorizations disallowing announcements for prefixes")?; 523 writeln!(f)?; 524 for roa in as0 { 525 writeln!(f, "\tDefinition: {}", roa.definition)?; 526 } 527 writeln!(f)?; 528 } 529 530 if let Some(as0_redundant) = entry_map.get(&BgpAnalysisState::RoaAs0Redundant) { 531 writeln!( 532 f, 533 "AS0 Authorization which are made redundant by authorizations for the prefix from real ASNs" 534 )?; 535 writeln!(f)?; 536 for roa in as0_redundant { 537 writeln!(f, "\tDefinition: {}", roa.definition)?; 538 writeln!(f)?; 539 writeln!(f, "\t\tMade redundant by:")?; 540 for redundant_by in &roa.made_redundant_by { 541 writeln!(f, "\t\t{}", redundant_by)?; 542 } 543 writeln!(f)?; 544 } 545 } 546 547 if let Some(valid) = entry_map.get(&BgpAnalysisState::AnnouncementValid) { 548 writeln!(f, "Announcements which are valid:")?; 549 writeln!(f)?; 550 for ann in valid { 551 writeln!(f, "\tAnnouncement: {}", ann.definition)?; 552 } 553 writeln!(f)?; 554 } 555 556 if let Some(invalid_length) = entry_map.get(&BgpAnalysisState::AnnouncementInvalidLength) { 557 writeln!( 558 f, 559 "Announcements from an authorized ASN, which are too specific (not allowed by max length):" 560 )?; 561 for ann in invalid_length { 562 writeln!(f)?; 563 writeln!(f, "\tAnnouncement: {}", ann.definition)?; 564 writeln!(f)?; 565 writeln!(f, "\t\tDisallowed by authorization(s):")?; 566 for roa in ann.disallowed_by.iter() { 567 writeln!(f, "\t\t{}", roa)?; 568 } 569 } 570 writeln!(f)?; 571 } 572 573 if let Some(invalid_asn) = entry_map.get(&BgpAnalysisState::AnnouncementInvalidAsn) { 574 writeln!(f, "Announcements from an unauthorized ASN:")?; 575 for ann in invalid_asn { 576 writeln!(f)?; 577 writeln!(f, "\tAnnouncement: {}", ann.definition)?; 578 writeln!(f)?; 579 writeln!(f, "\t\tDisallowed by authorization(s):")?; 580 for roa in ann.disallowed_by.iter() { 581 writeln!(f, "\t\t{}", roa)?; 582 } 583 } 584 writeln!(f)?; 585 } 586 587 if let Some(disallowed) = entry_map.get(&BgpAnalysisState::AnnouncementDisallowed) { 588 writeln!(f, "Announcements disallowed by 'AS0' ROAs:")?; 589 writeln!(f)?; 590 for ann in disallowed { 591 writeln!(f, "\tAnnouncement: {}", ann.definition)?; 592 } 593 writeln!(f)?; 594 } 595 596 if let Some(not_found) = entry_map.get(&BgpAnalysisState::AnnouncementNotFound) { 597 writeln!( 598 f, 599 "Announcements which are 'not found' (not covered by any of your authorizations):" 600 )?; 601 writeln!(f)?; 602 for ann in not_found { 603 writeln!(f, "\tAnnouncement: {}", ann.definition)?; 604 } 605 writeln!(f)?; 606 } 607 608 Ok(()) 609 } 610 } 611 } 612 613 //------------ BgpAnalysisEntry -------------------------------------------- 614 615 #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] 616 pub struct BgpAnalysisEntry { 617 #[serde(flatten)] 618 definition: RoaDefinition, 619 state: BgpAnalysisState, 620 #[serde(skip_serializing_if = "Option::is_none")] 621 allowed_by: Option<RoaDefinition>, 622 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 623 disallowed_by: Vec<RoaDefinition>, 624 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 625 made_redundant_by: Vec<RoaDefinition>, 626 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 627 authorizes: Vec<Announcement>, 628 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] 629 disallows: Vec<Announcement>, 630 } 631 632 impl BgpAnalysisEntry { definition(&self) -> &RoaDefinition633 pub fn definition(&self) -> &RoaDefinition { 634 &self.definition 635 } 636 announcement(&self) -> Announcement637 pub fn announcement(&self) -> Announcement { 638 self.definition.into() 639 } 640 state(&self) -> BgpAnalysisState641 pub fn state(&self) -> BgpAnalysisState { 642 self.state 643 } 644 allowed_by(&self) -> Option<&RoaDefinition>645 pub fn allowed_by(&self) -> Option<&RoaDefinition> { 646 self.allowed_by.as_ref() 647 } 648 disallowed_by(&self) -> &Vec<RoaDefinition>649 pub fn disallowed_by(&self) -> &Vec<RoaDefinition> { 650 &self.disallowed_by 651 } 652 made_redundant_by(&self) -> &Vec<RoaDefinition>653 pub fn made_redundant_by(&self) -> &Vec<RoaDefinition> { 654 &self.made_redundant_by 655 } 656 authorizes(&self) -> &Vec<Announcement>657 pub fn authorizes(&self) -> &Vec<Announcement> { 658 &self.authorizes 659 } 660 disallows(&self) -> &Vec<Announcement>661 pub fn disallows(&self) -> &Vec<Announcement> { 662 &self.disallows 663 } 664 roa_seen( definition: RoaDefinition, mut authorizes: Vec<Announcement>, mut disallows: Vec<Announcement>, ) -> Self665 pub fn roa_seen( 666 definition: RoaDefinition, 667 mut authorizes: Vec<Announcement>, 668 mut disallows: Vec<Announcement>, 669 ) -> Self { 670 authorizes.sort(); 671 disallows.sort(); 672 BgpAnalysisEntry { 673 definition, 674 state: BgpAnalysisState::RoaSeen, 675 allowed_by: None, 676 disallowed_by: vec![], 677 made_redundant_by: vec![], 678 authorizes, 679 disallows, 680 } 681 } 682 roa_disallowing(definition: RoaDefinition, mut disallows: Vec<Announcement>) -> Self683 pub fn roa_disallowing(definition: RoaDefinition, mut disallows: Vec<Announcement>) -> Self { 684 disallows.sort(); 685 BgpAnalysisEntry { 686 definition, 687 state: BgpAnalysisState::RoaDisallowing, 688 allowed_by: None, 689 disallowed_by: vec![], 690 made_redundant_by: vec![], 691 authorizes: vec![], 692 disallows, 693 } 694 } 695 roa_as0(definition: RoaDefinition, mut disallows: Vec<Announcement>) -> Self696 pub fn roa_as0(definition: RoaDefinition, mut disallows: Vec<Announcement>) -> Self { 697 disallows.sort(); 698 BgpAnalysisEntry { 699 definition, 700 state: BgpAnalysisState::RoaAs0, 701 allowed_by: None, 702 disallowed_by: vec![], 703 made_redundant_by: vec![], 704 authorizes: vec![], 705 disallows, 706 } 707 } 708 roa_as0_redundant(definition: RoaDefinition, mut made_redundant_by: Vec<RoaDefinition>) -> Self709 pub fn roa_as0_redundant(definition: RoaDefinition, mut made_redundant_by: Vec<RoaDefinition>) -> Self { 710 made_redundant_by.sort(); 711 BgpAnalysisEntry { 712 definition, 713 state: BgpAnalysisState::RoaAs0Redundant, 714 allowed_by: None, 715 disallowed_by: vec![], 716 made_redundant_by, 717 authorizes: vec![], 718 disallows: vec![], 719 } 720 } 721 roa_redundant( definition: RoaDefinition, mut authorizes: Vec<Announcement>, mut disallows: Vec<Announcement>, mut made_redundant_by: Vec<RoaDefinition>, ) -> Self722 pub fn roa_redundant( 723 definition: RoaDefinition, 724 mut authorizes: Vec<Announcement>, 725 mut disallows: Vec<Announcement>, 726 mut made_redundant_by: Vec<RoaDefinition>, 727 ) -> Self { 728 authorizes.sort(); 729 disallows.sort(); 730 made_redundant_by.sort(); 731 BgpAnalysisEntry { 732 definition, 733 state: BgpAnalysisState::RoaRedundant, 734 allowed_by: None, 735 disallowed_by: vec![], 736 made_redundant_by, 737 authorizes, 738 disallows, 739 } 740 } 741 roa_too_permissive( definition: RoaDefinition, mut authorizes: Vec<Announcement>, mut disallows: Vec<Announcement>, ) -> Self742 pub fn roa_too_permissive( 743 definition: RoaDefinition, 744 mut authorizes: Vec<Announcement>, 745 mut disallows: Vec<Announcement>, 746 ) -> Self { 747 authorizes.sort(); 748 disallows.sort(); 749 BgpAnalysisEntry { 750 definition, 751 state: BgpAnalysisState::RoaTooPermissive, 752 allowed_by: None, 753 disallowed_by: vec![], 754 made_redundant_by: vec![], 755 authorizes, 756 disallows, 757 } 758 } 759 roa_unseen(definition: RoaDefinition) -> Self760 pub fn roa_unseen(definition: RoaDefinition) -> Self { 761 BgpAnalysisEntry { 762 definition, 763 state: BgpAnalysisState::RoaUnseen, 764 allowed_by: None, 765 disallowed_by: vec![], 766 made_redundant_by: vec![], 767 authorizes: vec![], 768 disallows: vec![], 769 } 770 } 771 roa_not_held(definition: RoaDefinition) -> Self772 pub fn roa_not_held(definition: RoaDefinition) -> Self { 773 BgpAnalysisEntry { 774 definition, 775 state: BgpAnalysisState::RoaNotHeld, 776 allowed_by: None, 777 disallowed_by: vec![], 778 made_redundant_by: vec![], 779 authorizes: vec![], 780 disallows: vec![], 781 } 782 } 783 roa_no_announcement_info(definition: RoaDefinition) -> Self784 pub fn roa_no_announcement_info(definition: RoaDefinition) -> Self { 785 BgpAnalysisEntry { 786 definition, 787 state: BgpAnalysisState::RoaNoAnnouncementInfo, 788 allowed_by: None, 789 disallowed_by: vec![], 790 made_redundant_by: vec![], 791 authorizes: vec![], 792 disallows: vec![], 793 } 794 } 795 announcement_valid(announcement: Announcement, allowed_by: RoaDefinition) -> Self796 pub fn announcement_valid(announcement: Announcement, allowed_by: RoaDefinition) -> Self { 797 BgpAnalysisEntry { 798 definition: RoaDefinition::from(announcement), 799 state: BgpAnalysisState::AnnouncementValid, 800 allowed_by: Some(allowed_by), 801 disallowed_by: vec![], 802 made_redundant_by: vec![], 803 authorizes: vec![], 804 disallows: vec![], 805 } 806 } 807 announcement_invalid_asn(announcement: Announcement, disallowed_by: Vec<RoaDefinition>) -> Self808 pub fn announcement_invalid_asn(announcement: Announcement, disallowed_by: Vec<RoaDefinition>) -> Self { 809 Self::announcement_invalid(announcement, BgpAnalysisState::AnnouncementInvalidAsn, disallowed_by) 810 } 811 announcement_invalid_length(announcement: Announcement, disallowed_by: Vec<RoaDefinition>) -> Self812 pub fn announcement_invalid_length(announcement: Announcement, disallowed_by: Vec<RoaDefinition>) -> Self { 813 Self::announcement_invalid(announcement, BgpAnalysisState::AnnouncementInvalidLength, disallowed_by) 814 } 815 announcement_disallowed(announcement: Announcement, disallowed_by: Vec<RoaDefinition>) -> Self816 pub fn announcement_disallowed(announcement: Announcement, disallowed_by: Vec<RoaDefinition>) -> Self { 817 Self::announcement_invalid(announcement, BgpAnalysisState::AnnouncementDisallowed, disallowed_by) 818 } 819 announcement_invalid( announcement: Announcement, state: BgpAnalysisState, mut disallowed_by: Vec<RoaDefinition>, ) -> Self820 fn announcement_invalid( 821 announcement: Announcement, 822 state: BgpAnalysisState, 823 mut disallowed_by: Vec<RoaDefinition>, 824 ) -> Self { 825 disallowed_by.sort(); 826 BgpAnalysisEntry { 827 definition: RoaDefinition::from(announcement), 828 state, 829 allowed_by: None, 830 disallowed_by, 831 made_redundant_by: vec![], 832 authorizes: vec![], 833 disallows: vec![], 834 } 835 } 836 announcement_not_found(announcement: Announcement) -> Self837 pub fn announcement_not_found(announcement: Announcement) -> Self { 838 BgpAnalysisEntry { 839 definition: RoaDefinition::from(announcement), 840 state: BgpAnalysisState::AnnouncementNotFound, 841 allowed_by: None, 842 disallowed_by: vec![], 843 made_redundant_by: vec![], 844 authorizes: vec![], 845 disallows: vec![], 846 } 847 } 848 } 849 850 impl Ord for BgpAnalysisEntry { cmp(&self, other: &Self) -> Ordering851 fn cmp(&self, other: &Self) -> Ordering { 852 let mut ordering = self.state.cmp(&other.state); 853 if ordering == Ordering::Equal { 854 ordering = self.definition.cmp(&other.definition); 855 } 856 ordering 857 } 858 } 859 860 impl PartialOrd for BgpAnalysisEntry { partial_cmp(&self, other: &Self) -> Option<Ordering>861 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 862 Some(self.cmp(other)) 863 } 864 } 865 866 //------------ BgpAnalysisState -------------------------------------------- 867 868 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialOrd, PartialEq, Serialize)] 869 #[serde(rename_all = "snake_case")] 870 pub enum BgpAnalysisState { 871 RoaSeen, 872 RoaRedundant, 873 RoaUnseen, 874 RoaDisallowing, 875 RoaTooPermissive, 876 RoaAs0, 877 RoaAs0Redundant, 878 RoaNotHeld, 879 AnnouncementValid, 880 AnnouncementInvalidLength, 881 AnnouncementInvalidAsn, 882 AnnouncementDisallowed, 883 AnnouncementNotFound, 884 RoaNoAnnouncementInfo, 885 } 886 887 impl BgpAnalysisState { is_invalid(self) -> bool888 pub fn is_invalid(self) -> bool { 889 matches!( 890 self, 891 BgpAnalysisState::AnnouncementInvalidAsn | BgpAnalysisState::AnnouncementInvalidLength 892 ) 893 } 894 } 895 896 //------------ Tests -------------------------------------------------------- 897 898 #[cfg(test)] 899 mod tests { 900 use super::*; 901 902 #[test] print_bgp_report_full()903 fn print_bgp_report_full() { 904 let json = include_str!("../../../test-resources/bgp/expected_full_report.json"); 905 let report: BgpAnalysisReport = serde_json::from_str(json).unwrap(); 906 907 let expected = include_str!("../../../test-resources/bgp/expected_full_report.txt"); 908 909 assert_eq!(report.to_string(), expected); 910 } 911 } 912