Skip to content

Commit 2d279a2

Browse files
committed
WIP path selection fixes, fuzzing and documentation
This commit changes the bounds on the path selection helper methods `best` and `best_backup` to ensure we have access to the actual OrdRoute contents. Those are necessary for comparison of the actual route contents (path attributes, additional tiebreaker info), specifically in `best_backup`. Before, those methods would take the more generic `T: Ord`, but we need to make sure any returned backup route is actually different from the returned best route, hence the need to access the inner values of OrdRoute.
1 parent 44d8b36 commit 2d279a2

File tree

3 files changed

+166
-42
lines changed

3 files changed

+166
-42
lines changed

fuzz/Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fuzz/fuzz_targets/path_selection.rs

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
#![no_main]
22

33
use std::cmp::Ordering::*;
4+
use std::fmt::Debug;
45
use libfuzzer_sys::fuzz_target;
5-
use routecore::bgp::{path_attributes::PaMap, path_selection::{OrdRoute, Rfc4271, SkipMed, TiebreakerInfo}};
6+
use routecore::bgp::{
7+
path_attributes::PaMap,
8+
path_selection::{OrdStrat, OrdRoute, Rfc4271, SkipMed, TiebreakerInfo,
9+
best_backup, best
10+
}
11+
};
612

713
fn verify_ord<T>(a: &T, b: &T, c: &T)
814
where T: Ord
@@ -36,6 +42,39 @@ fn verify_ord<T>(a: &T, b: &T, c: &T)
3642
}
3743
}
3844

45+
fn verify_path_selection<'a, T, OS, I>(candidates: I)
46+
where
47+
I: Clone + Iterator<Item = T>,
48+
T: Debug + Ord + core::borrow::Borrow<OrdRoute<'a, OS>>,
49+
OS: Debug + OrdStrat,
50+
{
51+
let best1 = best(candidates.clone()).unwrap();
52+
let (best2, backup2) = best_backup(candidates.clone());
53+
let best2 = best2.unwrap();
54+
55+
assert_eq!(best1.borrow(), best2.borrow());
56+
assert_eq!(best1.borrow().pa_map(), best2.borrow().pa_map());
57+
assert_eq!(best1.borrow().tiebreakers(), best2.borrow().tiebreakers());
58+
59+
let backup2 = match backup2 {
60+
Some(route) => route,
61+
None => {
62+
let mut iter = candidates.clone();
63+
let mut cur = iter.next().unwrap();
64+
for c in iter {
65+
assert_eq!(cur.borrow().inner(), c.borrow().inner());
66+
cur = c;
67+
}
68+
return;
69+
}
70+
};
71+
72+
assert_ne!(
73+
(best2.borrow().tiebreakers(), best2.borrow().pa_map()),
74+
(backup2.borrow().tiebreakers(), backup2.borrow().pa_map()),
75+
);
76+
}
77+
3978
// XXX while doing fuzz_target!(|data: &[u8]| ... and then creating an
4079
// Unstructured from `data` can be useful, the Debug output becomes quite
4180
// useless. It'll be just a bunch of bytes without any structure.
@@ -67,6 +106,7 @@ fuzz_target!(|data: (
67106

68107
//dbg!("skipmed");
69108
verify_ord(&a, &b, &c);
109+
verify_path_selection([&a, &b, &c, &b, &c, &a].into_iter());
70110

71111
//dbg!("rfc4271");
72112
/*

src/bgp/path_selection.rs

+124-40
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ use super::{
1212
};
1313

1414
/// Wrapper type used for comparison and path selection.
15-
#[derive(Copy, Clone, Debug)]
15+
///
16+
/// Be aware that the `PartialEq` implementation of `OrdRoute` is specifically
17+
/// tailored towards its `Ord` implementation, to do the BGP Decision Process.
18+
/// Comparing two `OrdRoute`s, wrapping two routes with different sets of path
19+
/// attributes, might still yield 'equal'. Instead, consider comparing on the
20+
/// `PaMap` and/or `Tiebreakerinfo` directly (or [`fn inner`]).
21+
#[derive(Copy, Clone, Debug, Hash)]
1622
pub struct OrdRoute<'a, OS> {
1723
pa_map: &'a PaMap,
1824
tiebreakers: TiebreakerInfo,
@@ -53,6 +59,22 @@ impl<'a, OS> OrdRoute<'a, OS> {
5359

5460
Ok(self)
5561
}
62+
63+
64+
/// Returns the `TiebreakerInfo`.
65+
pub fn tiebreakers(&self) -> TiebreakerInfo {
66+
self.tiebreakers
67+
}
68+
69+
/// Returns a reference to the Path Attributes.
70+
pub fn pa_map(&self) -> &PaMap {
71+
self.pa_map
72+
}
73+
74+
/// Returns a tuple of the actual content of this OrdRoute.
75+
pub fn inner(&self) -> (TiebreakerInfo, &PaMap) {
76+
(self.tiebreakers, self.pa_map())
77+
}
5678
}
5779

5880
impl<'a, OS: OrdStrat> OrdRoute<'a, OS> {
@@ -476,22 +498,28 @@ pub fn preferred<'a, OS: OrdStrat>(
476498
cmp::min(a,b)
477499
}
478500

479-
pub fn best<'a, OS: 'a + OrdStrat, I>(it: I) -> Option<I::Item>
501+
/// Selects the preferred ('best') route.
502+
pub fn best<'a, T, I, OS>(it: I) -> Option<T>
480503
where
481-
I: Iterator<Item = OrdRoute<'a, OS>>
504+
T: Ord,
505+
I: Iterator<Item = T>,
506+
T: core::borrow::Borrow<OrdRoute<'a, OS>>
482507
{
483508
it.min()
484509
}
485510

486-
pub fn best_with_strat<'a, OS, AltOS, I>(it: I)
511+
/*
512+
pub fn best_with_strat<'a, OS, AltOS, I, T1>(it: I)
487513
-> Option<OrdRoute<'a, AltOS>>
488514
where
489515
OS: 'a + OrdStrat,
490516
AltOS: 'a + OrdStrat,
491-
I: Iterator<Item = OrdRoute<'a, OS>>,
517+
I: Iterator<Item = T1>,
518+
T1: core::borrow::Borrow<OrdRoute<'a, OS>>
492519
{
493520
it.map(|r| r.into_strat()).min()
494521
}
522+
*/
495523

496524

497525
// XXX would this be convenient?
@@ -502,23 +530,23 @@ pub fn best_alt<'a, OS: OrdStrat>(
502530
unimplemented!()
503531
}
504532

505-
pub fn best_backup_vec<'a, OS: OrdStrat>(
506-
it: impl Iterator<Item = OrdRoute<'a, OS>>
507-
) -> (Option<OrdRoute<'a, OS>>, Option<OrdRoute<'a, OS>>)
508-
{
509-
let mut sorted = it.collect::<Vec<_>>();
510-
sorted.sort();
511-
let mut iter = sorted.into_iter();
512-
513-
let best = iter.next();
514-
let backup = iter.next();
515-
516-
(best, backup)
517-
}
518-
519-
pub fn best_backup<T: Ord>(
533+
/// Returns the 'best' and second-best path.
534+
///
535+
/// If the iterator passed in contains no paths, `(None, None)` is returned.
536+
/// If the iterator yields only a single item, that will be the 'best' path,
537+
/// and the 'backup' will be None, i.e. `(Some(best), None)`.
538+
///
539+
/// In all other cases (an iterator yielding two or more non-identical
540+
/// values), both members of the tuple should be `Some(..)`.
541+
///
542+
/// The returned 'best' path is the same as the path returned by [`fn best`].
543+
pub fn best_backup<'a, OS, T>(
520544
it: impl Iterator<Item = T>
521545
) -> (Option<T>, Option<T>)
546+
where
547+
OS: OrdStrat,
548+
T: Ord,
549+
T: core::borrow::Borrow<OrdRoute<'a, OS>>
522550
{
523551
let mut best = None;
524552
let mut backup = None;
@@ -528,7 +556,7 @@ pub fn best_backup<T: Ord>(
528556
None => { best = Some(c); continue }
529557
Some(cur_best) => {
530558
if c < cur_best {
531-
// c is preferred over current
559+
// c is preferred over current best
532560
best = Some(c);
533561
backup = Some(cur_best);
534562
continue;
@@ -538,12 +566,35 @@ pub fn best_backup<T: Ord>(
538566

539567
// c is not better than best, now check backup
540568
match backup.take() {
541-
None => { backup = Some(c); }
569+
None => {
570+
// Before we set the backup route, ensure it is not
571+
// the same route as best.
572+
// We compare the actual contents of the OrdRoute
573+
// here, i.e. the TiebreakerInfo and PaMap. If we'd
574+
// simply compare the OrdRoutes themselves, we'd be
575+
// testing whether or not they are considered equal in
576+
// terms of path preference, NOT in terms of content.
577+
// If for example we have a candidate backup path with
578+
// a different AS_PATH but of equal length as the best
579+
// path, the OrdRoutes are considered equal even
580+
// though the actual path attributes differ.
581+
//
582+
583+
// Best is always Some at this point, unwrap is safe.
584+
if best.as_ref().unwrap().borrow().inner() != c.borrow().inner() {
585+
backup = Some(c);
586+
} }
542587
Some(cur_backup) => {
543588
if c < cur_backup {
544-
// c is preferred over backup
545-
backup = Some(c);
589+
// c is preferred over current backup
590+
// check if it is not the same route as 'best'
591+
if best.as_ref() != Some(&c) {
592+
backup = Some(c);
593+
} else {
594+
backup = Some(cur_backup);
595+
}
546596
} else {
597+
// put it back in
547598
backup = Some(cur_backup);
548599
}
549600

@@ -556,7 +607,7 @@ pub fn best_backup<T: Ord>(
556607
(best, backup)
557608
}
558609

559-
610+
/*
560611
pub fn best_multistrat<'a, OS1: 'a + OrdStrat, OS2: 'a + OrdStrat, I>(it: I)
561612
-> Option<(OrdRoute<'a, OS1>, OrdRoute<'a, OS2>)>
562613
where
@@ -575,6 +626,33 @@ where
575626
576627
Some((res1, res2))
577628
}
629+
*/
630+
631+
/*
632+
pub fn best_multistrat<'a, I, T1, T2, OS1, OS2>(it: I) -> Option<(T1, T2)>
633+
where
634+
T1: Ord + core::borrow::Borrow<OrdRoute<'a, OS1>>,
635+
T2: Ord,
636+
OS1: OrdStrat,
637+
OS2: OrdStrat,
638+
I: Clone + Iterator<Item = T1>,
639+
{
640+
let res1 = best(it.clone());
641+
let res1 = match res1 {
642+
Some(r) => r,
643+
None => return None
644+
};
645+
646+
// Given that res1 is not None, `it` is non-empty.
647+
// For a non-empty collection of OrdRoutes, there is always a best, so we
648+
// can unwrap().
649+
let res2 = best_with_strat::<'_, OS1, OS2, _>(it).unwrap();
650+
651+
Some((res1, res2))
652+
}
653+
*/
654+
655+
578656

579657

580658
//------------ Tests ---------------------------------------------------------
@@ -656,33 +734,38 @@ mod tests {
656734

657735
let mut b_pamap = PaMap::empty();
658736
b_pamap.set(Origin(OriginType::Egp));
659-
b_pamap.set(HopPath::from([10, 25, 30]));
737+
b_pamap.set(HopPath::from([10, 25, 30, 40]));
660738
b_pamap.set(MultiExitDisc(50));
661739

662740
let mut c_pamap = PaMap::empty();
663741
c_pamap.set(Origin(OriginType::Egp));
664-
c_pamap.set(HopPath::from([80, 90, 100]));
742+
c_pamap.set(HopPath::from([80, 90, 100, 200, 300]));
665743

666744
let candidates = [
667-
OrdRoute::rfc4271(&a_pamap, tiebreakers).unwrap(),
668-
OrdRoute::rfc4271(&b_pamap, tiebreakers).unwrap(),
669-
OrdRoute::rfc4271(&c_pamap, tiebreakers).unwrap(),
745+
OrdRoute::skip_med(&a_pamap, tiebreakers).unwrap(),
746+
OrdRoute::skip_med(&b_pamap, tiebreakers).unwrap(),
747+
OrdRoute::skip_med(&a_pamap, tiebreakers).unwrap(),
748+
//OrdRoute::skip_med(&c_pamap, tiebreakers).unwrap(),
670749
];
671750

672-
let best1 = best(candidates.into_iter());
673-
let (best2, backup2) = best_backup_vec(candidates.into_iter());
674-
let (best3, backup3) = best_backup(candidates.into_iter());
751+
let best1 = best(candidates.iter().cloned()).unwrap();
752+
let (best2, backup2) = best_backup(candidates.iter());
753+
let (best2, backup2) = (best2.unwrap(), backup2.unwrap());
754+
755+
assert_eq!(best1.pa_map(), best2.pa_map());
756+
assert_eq!(best1.tiebreakers(), best2.tiebreakers());
757+
758+
dbg!(&best2);
759+
dbg!(&backup2);
675760

676-
assert_eq!(best1, best2);
677-
assert_eq!(best2, best3);
678-
assert_eq!(backup2, backup3);
679-
assert_ne!(best2, backup2);
680-
assert_ne!(best3, backup3);
761+
assert_ne!(
762+
(best2.pa_map(), best2.tiebreakers()),
763+
(backup2.pa_map(), backup2.tiebreakers())
764+
);
681765

682-
//dbg!(&best1);
683-
//dbg!(&backup2);
684766
}
685767

768+
/*
686769
#[test]
687770
fn helpers_generic() {
688771
let (a,b,c) = ("2".to_string(), "1".to_string(), "3".to_string());
@@ -692,4 +775,5 @@ mod tests {
692775
assert_eq!(best, Some(&b));
693776
assert_eq!(backup, Some(&a));
694777
}
778+
*/
695779
}

0 commit comments

Comments
 (0)