//! Choosing the best route for a prefix (RFC 4271 §9.1.2.2). //! //! The steps, in order. A route wins at the first step where it differs: //! //! 1. highest LOCAL_PREF; //! 2. shortest AS path; //! 3. lowest ORIGIN; //! 4. lowest MULTI_EXIT_DISC, among routes from the same neighbouring AS; //! 5. a route from an external peer over one from an internal peer; //! 6. the peer with the lowest BGP Identifier; //! 7. the peer with the lowest address. //! //! (The RFC's step between 5 and 6, the interior cost to the next hop, has //! nothing to compare until there is an IGP to ask.) //! //! Steps 1 to 3 are put into one number per route when it is stored, //! [`preference`]. Most of the time that number alone settles it: a new //! route with a larger one than the best is the new best, with a smaller //! one changes nothing, and neither needs a look at any other route. Only a //! tie goes on to [`choose`], which looks at the whole row, and that begins //! with a pass over the numbers to find the routes still in the running. //! //! [`after_add`], [`after_replace`] and [`after_remove`] are what the table //! calls when a row has changed. Each says whether the best route is now //! another, and takes the short way where there is one. They rely on, and //! keep, one fact about a row: its best route has the highest preference //! of all its routes. use super::attrs::SelectionKey; use super::{DEFAULT_LOCAL_PREF, Path, PeerInfo, Row}; /// Steps 1 to 3 as one number of 48 bits: the larger, the better. /// /// LOCAL_PREF fills the high 32 bits. Below it come the AS path length in /// 12 bits and the ORIGIN in 2, each turned upside down so that shorter and /// lower compare as larger. Twelve bits are enough for any path: a BGP /// message of 4096 octets has room for about a thousand AS numbers. pub(super) fn preference(key: &SelectionKey) -> u64 { // LOCAL_PREF is a matter between routers of one AS. What an external // peer sent as one never gets as far as the table (the decoder drops // it, RFC 7606 §7.5); a route from such a peer that has one was given // it by our own import policy, which is who is to say. let local_pref = key.local_pref.unwrap_or(DEFAULT_LOCAL_PREF); const LONGEST: u16 = (1 << 12) - 1; (u64::from(local_pref) << 16) | (u64::from(LONGEST - key.as_path_len.min(LONGEST)) << 4) | (u64::from(3 - key.origin.min(3)) << 2) } /// Buffers that [`choose`] reuses. #[derive(Default)] pub(super) struct Scratch { /// Routes still in the running: (neighbouring AS, MED, index in row). candidates: Vec<(u32, u32, u32)>, } /// The best route changed after a route was added at the end of `row`? pub(super) fn after_add(row: &mut Row, peers: &[Option], scratch: &mut Scratch) -> bool { let added = row.paths.len() - 1; let preference = row.paths[added].preference(); let best = row.paths[row.best()].preference(); // Better than the best in the first steps of selection is better than // everything; worse than it changes nothing. A tie has to be settled by // the later steps. if preference > best { row.set_best(added); true } else if preference < best { false } else if let Some(wins) = settles_tie(row, added, peers) { if wins { row.set_best(added); } wins } else { choose(row, peers, scratch) } } /// The best route changed after the route at `at` was replaced by another /// from the same peer? `before` is the preference of the route it replaced. /// The two are known to differ. pub(super) fn after_replace( row: &mut Row, at: usize, before: u64, peers: &[Option], scratch: &mut Scratch, ) -> bool { let preference = row.paths[at].preference(); if at == row.best() { // The best route itself changed. If it got better in the first // steps it is still the best, but it is a different route now. if preference <= before { choose(row, peers, scratch); } return true; } let best = row.paths[row.best()].preference(); if preference > best { row.set_best(at); true } else if preference < best && before < best { // It was not in the running and still is not. false } else { choose(row, peers, scratch) } } /// Takes the route at `at` out of `row`, which has others, and returns /// whether the best route changed. pub(super) fn after_remove( row: &mut Row, at: usize, peers: &[Option], scratch: &mut Scratch, ) -> bool { let best = row.best(); let best_preference = row.paths[best].preference(); let last = row.paths.len() - 1; let removed = row.swap_remove(at); if at == best { choose(row, peers, scratch); return true; } // The last route moved into the gap. if best == last { row.set_best(at); } // A route that tied with the best in the first steps may have been // what kept another from being best, by its MED. removed.preference() == best_preference && choose(row, peers, scratch) } /// Chooses the best route of `row` from all its routes, records it in the /// row, and returns whether it is another route than before. /// /// The row must have at least one route. fn choose(row: &mut Row, peers: &[Option], scratch: &mut Scratch) -> bool { let before = row.best; let top = row .paths .iter() .map(|path| path.preference()) .max() .unwrap_or(0); let candidates = &mut scratch.candidates; candidates.clear(); for (index, path) in row.paths.iter().enumerate() { if path.preference() == top { let key = path.attrs.key(); // A route without a MED is taken to have the lowest (RFC 4271 // §9.1.2.2 c). candidates.push((key.neighbor_as, key.med.unwrap_or(0), index as u32)); } } // Step 4. MEDs are compared only between routes from the same // neighbouring AS, so "lower MED" is not an order over all the routes, // and comparing them pair by pair can go round in circles. Instead: // within each neighbouring AS only the routes with its lowest MED stay // in, and the steps after are an order over what is left. candidates.sort_unstable(); let mut best: Option<(TieBreak, u32)> = None; let mut group = None; for &(neighbor_as, med, index) in candidates.iter() { // Sorted by AS and then MED: the first of each AS has its lowest. let lowest = match group { Some((group_as, lowest)) if group_as == neighbor_as => lowest, _ => { group = Some((neighbor_as, med)); med } }; if med != lowest { continue; } let tie_break = TieBreak::of(row, index as usize, peers); if best.as_ref().is_none_or(|(least, _)| tie_break < *least) { best = Some((tie_break, index)); } } row.best = best.map_or(0, |(_, index)| index); row.best != before } /// The routes of `row` from the best down, as indices into its routes: /// after the best, the one that would be chosen were the best gone, and /// so on. What an operator asks when looking at a prefix: which route /// would take over. pub(super) fn ranked(row: &Row, peers: &[Option]) -> Vec { // A copy to take the winners out of one by one, and where each of its // routes is in the row itself. let mut rest = Row { prefix: row.prefix, best: 0, seqs: row.seqs, paths: row .paths .iter() .map(|path| Path { word: path.word, attrs: std::sync::Arc::clone(&path.attrs), }) .collect(), path_ids: row.path_ids.clone(), }; let mut places: Vec = (0..row.paths.len()).collect(); let mut scratch = Scratch::default(); let mut order = Vec::with_capacity(row.paths.len()); while !rest.paths.is_empty() { choose(&mut rest, peers, &mut scratch); let at = rest.best(); order.push(places[at]); rest.swap_remove(at); places.swap_remove(at); } order } /// For a route just added at `added` that ties with the best in steps 1 to /// 3: whether it is the new best, if that can be said from the two routes /// alone. `None` if the whole row has to be looked at. /// /// It can be said when MED cannot knock either of them out. The new route /// has the lowest MED there is, so nothing knocks it out. It knocks out /// only routes of its own neighbouring AS with a higher MED, and if the /// best is not one of those, the best is still the best of all the others. /// So the better of the two is the best of all. This is the usual case: /// most routes have no MED. fn settles_tie(row: &Row, added: usize, peers: &[Option]) -> Option { let new = row.paths[added].attrs.key(); let best = row.paths[row.best()].attrs.key(); if new.med.unwrap_or(0) != 0 { return None; } if best.neighbor_as == new.neighbor_as && best.med.unwrap_or(0) != 0 { return None; } Some(TieBreak::of(row, added, peers) < TieBreak::of(row, row.best(), peers)) } /// Steps 5 to 7, then the peer's number, and then the path identifier for /// a peer that sent several routes: so that the choice never depends on the /// order routes arrived in. The least wins. #[derive(PartialEq, Eq, PartialOrd, Ord)] struct TieBreak { internal: bool, router_id: u32, address: std::net::IpAddr, peer: u16, path_id: u32, } impl TieBreak { fn of(row: &Row, index: usize, peers: &[Option]) -> Self { let peer = row.paths[index].peer(); let path_id = row.path_id(index); match peers.get(peer.as_usize()).copied().flatten() { Some(info) => Self { internal: info.ibgp, router_id: info.router_id.into(), address: info.address, peer: peer.0, path_id, }, // Not expected: a peer is forgotten only once its routes are // gone. If it happened, anything known would be preferred. None => Self { internal: true, router_id: u32::MAX, address: std::net::Ipv6Addr::from(u128::MAX).into(), peer: peer.0, path_id, }, } } }