Import rules: preference and communities for some routes only
The import policy did the same to every route of a session. Now `rules` can do more to some of them: those with certain communities, those for certain prefixes, or both. Each rule can set the local preference and put communities on; each route takes the first rule that holds for it. Rules see a route as the session's own statements leave it, with our communities taken off and the session's marks put on. So a router can act on where another router of ours learned a route, by the mark that router put on it, and a customer's single prefix can be marked for a prepend. Routes of one UPDATE that different rules hold for go into the table with attribute sets of their own. An UPDATE that no rule touches is taken as before, without its attributes being written out again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d13336874
commit
4e945b278f
9 files changed
+481
-70
No files matched your search
@@ -16,8 +16,8 @@ use bgp_packet::message::{
|
||||
use bgp_primitives::ip_prefix::IpPrefix;
|
||||
use bgp_server::config::PeerConfig;
|
||||
use bgp_server::family::FamilySet;
|
||||
use bgp_server::marks::Scrub;
|
||||
use bgp_server::policy::{ImportPolicy, PrefixSet};
|
||||
use bgp_server::marks::{Marks, Patterns, Scrub};
|
||||
use bgp_server::policy::{ImportPolicy, ImportRule, PrefixSet};
|
||||
use bgp_server::session::DialState;
|
||||
use eyre::Result;
|
||||
use tokio::net::TcpListener;
|
||||
@@ -434,3 +434,79 @@ async fn announce_with(
|
||||
}))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Rules do to some routes what the rest of the policy does to all: by
|
||||
/// their communities, as the session has marked them, or by their prefix,
|
||||
/// even when they came in one UPDATE with others. The first rule that
|
||||
/// holds for a route is the one applied.
|
||||
#[tokio::test]
|
||||
async fn test_rules_apply_to_the_routes_they_hold_for() -> Result<()> {
|
||||
let mut within = PrefixSet::default();
|
||||
within.allow(prefix("193.0.0.0/21"), 24);
|
||||
let rule = |communities: &[&str], prefixes: Option<PrefixSet>, local_pref, add: &[&str]| {
|
||||
let strings = |texts: &[&str]| texts.iter().map(|t| t.to_string()).collect::<Vec<_>>();
|
||||
ImportRule {
|
||||
communities: Patterns::parse(&strings(communities)).unwrap(),
|
||||
prefixes,
|
||||
local_pref,
|
||||
marks: Marks::parse(&[], &[], &strings(add)).unwrap(),
|
||||
}
|
||||
};
|
||||
let policy = ImportPolicy {
|
||||
// The session marks where its routes were learned.
|
||||
marks: Marks::parse(&[], &[], &["1299:101:1".to_string()]).unwrap(),
|
||||
rules: vec![
|
||||
rule(&["3356:3"], None, Some(50), &[]),
|
||||
rule(&[], Some(within), None, &["1299:31:10"]),
|
||||
rule(&["1299:101:*"], None, Some(120), &[]),
|
||||
],
|
||||
..default_policy()
|
||||
};
|
||||
let (server, _listener) = server_with(policy).await?;
|
||||
let mut peer = established(&server).await?;
|
||||
announce_with(
|
||||
&mut peer,
|
||||
&["194.0.0.0/16"],
|
||||
vec![PathAttribute::Communities(CommunitiesPathAttribute(vec![
|
||||
(3356, 3),
|
||||
]))],
|
||||
)
|
||||
.await?;
|
||||
announce_with(&mut peer, &["193.0.0.0/22", "195.0.0.0/16"], vec![]).await?;
|
||||
table_after(&server, 3, 0).await?;
|
||||
|
||||
let kept = |wanted: &str| {
|
||||
let routes = server.received_routes();
|
||||
let route = routes
|
||||
.iter()
|
||||
.find(|route| route.prefix.to_string() == wanted)
|
||||
.unwrap_or_else(|| panic!("{wanted} is in the table"));
|
||||
let local_pref = route
|
||||
.attributes
|
||||
.iter()
|
||||
.find_map(|attribute| match attribute {
|
||||
PathAttribute::LocalPref(local_pref) => Some(local_pref.0),
|
||||
_ => None,
|
||||
});
|
||||
let large = route
|
||||
.attributes
|
||||
.iter()
|
||||
.find_map(|attribute| match attribute {
|
||||
PathAttribute::LargeCommunities(large) => Some(large.communities.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
(local_pref, large)
|
||||
};
|
||||
let learned = large(1299, 101, 1);
|
||||
// By its community: the first rule, and only that one.
|
||||
assert_eq!(kept("194.0.0.0/16"), (Some(50), vec![learned.clone()]));
|
||||
// By its prefix, apart from the route it came with.
|
||||
assert_eq!(
|
||||
kept("193.0.0.0/22"),
|
||||
(None, vec![learned.clone(), large(1299, 31, 10)])
|
||||
);
|
||||
// By the mark the session put on.
|
||||
assert_eq!(kept("195.0.0.0/16"), (Some(120), vec![learned]));
|
||||
Ok(())
|
||||
}
|
||||
@@ -459,6 +459,34 @@ message ImportPolicy {
|
||||
// a route server's client. Not for a peer in our own AS, whose routes
|
||||
// keep them in any case.
|
||||
bool accept_control_communities = 19;
|
||||
|
||||
// What is done to some of the routes, beyond what is done to all of
|
||||
// them above. Each route that is taken is looked at by the rules in
|
||||
// turn, and the first that holds for it is applied; the rest are not
|
||||
// looked at. A rule sees the route as the statements above leave it:
|
||||
// with our communities taken off and ours put on.
|
||||
repeated ImportRule rules = 20;
|
||||
}
|
||||
|
||||
// What is done to the routes with some communities, or for some prefixes:
|
||||
// those learned at one exchange, say, by the mark another router of ours
|
||||
// put on them, to be preferred less; or one prefix of a customer's, to be
|
||||
// marked for a prepend.
|
||||
message ImportRule {
|
||||
// The rule holds for routes with one of these communities: one
|
||||
// ("64601:100"), all of an AS ("64601:*"), or large ones with three
|
||||
// parts ("64601:101:*"). Left out, for routes with any.
|
||||
repeated string match_communities = 1;
|
||||
|
||||
// And for routes for a prefix in these. Left out, for any prefix.
|
||||
repeated PrefixRange match_prefixes = 2;
|
||||
|
||||
// The LOCAL_PREF of the routes, in place of import.local_pref.
|
||||
optional uint32 local_pref = 3;
|
||||
|
||||
// Communities put on the routes.
|
||||
repeated string add_communities = 4;
|
||||
repeated string add_large_communities = 5;
|
||||
}
|
||||
|
||||
// A prefix, and optionally the more specific prefixes inside it.
|
||||
|
||||
+79
-23
@@ -15,8 +15,8 @@ use bgp_rpki::RpkiConfig;
|
||||
use crate::address::{Endpoint, PeerAddress};
|
||||
use crate::error::{ConfigError, ExportProblem, ImportProblem, OriginateProblem, PeerProblem};
|
||||
use crate::family::FamilySet;
|
||||
use crate::marks::{Control, Marks, Scrub};
|
||||
use crate::policy::{ExportPolicy, ImportPolicy, PeerRole, PrefixSet};
|
||||
use crate::marks::{Control, Marks, Patterns, Scrub};
|
||||
use crate::policy::{ExportPolicy, ImportPolicy, ImportRule, PeerRole, PrefixSet};
|
||||
use crate::proto::config::{self as proto, BgpConfig, OriginatedRoute, Peer};
|
||||
use crate::restart::GracefulRestartConfig;
|
||||
|
||||
@@ -415,27 +415,18 @@ fn parse_import(import: &proto::ImportPolicy) -> Result<ImportPolicy, ImportProb
|
||||
}),
|
||||
};
|
||||
|
||||
let mut prefixes = PrefixSet::default();
|
||||
for range in &import.prefixes {
|
||||
let prefix =
|
||||
IpPrefix::try_from(range.prefix.as_str()).map_err(|problem| ImportProblem::Prefix {
|
||||
prefix: range.prefix.clone(),
|
||||
problem,
|
||||
})?;
|
||||
let longest = prefix.address_family().bits();
|
||||
let max_length = match range.max_length {
|
||||
0 => prefix.length(),
|
||||
max_length => u8::try_from(max_length)
|
||||
.ok()
|
||||
.filter(|max| (prefix.length()..=longest).contains(max))
|
||||
.ok_or(ImportProblem::MaxLength {
|
||||
prefix: range.prefix.clone(),
|
||||
max_length,
|
||||
longest,
|
||||
})?,
|
||||
};
|
||||
prefixes.allow(prefix, max_length);
|
||||
}
|
||||
let prefixes = parse_prefixes(&import.prefixes)?;
|
||||
let rules = import
|
||||
.rules
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(at, rule)| {
|
||||
parse_rule(rule).map_err(|problem| ImportProblem::Rule {
|
||||
number: at + 1,
|
||||
problem: Box::new(problem),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(ImportPolicy {
|
||||
max_prefixes: Some(import.max_prefixes).filter(|limit| *limit > 0),
|
||||
@@ -472,6 +463,7 @@ fn parse_import(import: &proto::ImportPolicy) -> Result<ImportPolicy, ImportProb
|
||||
&import.add_large_communities,
|
||||
)?,
|
||||
local_pref: import.local_pref,
|
||||
rules,
|
||||
max_as_path_length: match import.max_as_path_length {
|
||||
0 => defaults.max_as_path_length,
|
||||
length => u16::try_from(length).map_err(|_| ImportProblem::PathLength(length))?,
|
||||
@@ -482,6 +474,46 @@ fn parse_import(import: &proto::ImportPolicy) -> Result<ImportPolicy, ImportProb
|
||||
})
|
||||
}
|
||||
|
||||
/// A list of prefixes, each with how much longer a prefix inside it may
|
||||
/// be.
|
||||
fn parse_prefixes(ranges: &[proto::PrefixRange]) -> Result<PrefixSet, ImportProblem> {
|
||||
let mut prefixes = PrefixSet::default();
|
||||
for range in ranges {
|
||||
let prefix =
|
||||
IpPrefix::try_from(range.prefix.as_str()).map_err(|problem| ImportProblem::Prefix {
|
||||
prefix: range.prefix.clone(),
|
||||
problem,
|
||||
})?;
|
||||
let longest = prefix.address_family().bits();
|
||||
let max_length = match range.max_length {
|
||||
0 => prefix.length(),
|
||||
max_length => u8::try_from(max_length)
|
||||
.ok()
|
||||
.filter(|max| (prefix.length()..=longest).contains(max))
|
||||
.ok_or(ImportProblem::MaxLength {
|
||||
prefix: range.prefix.clone(),
|
||||
max_length,
|
||||
longest,
|
||||
})?,
|
||||
};
|
||||
prefixes.allow(prefix, max_length);
|
||||
}
|
||||
Ok(prefixes)
|
||||
}
|
||||
|
||||
fn parse_rule(rule: &proto::ImportRule) -> Result<ImportRule, ImportProblem> {
|
||||
Ok(ImportRule {
|
||||
communities: Patterns::parse(&rule.match_communities)?,
|
||||
prefixes: if rule.match_prefixes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(parse_prefixes(&rule.match_prefixes)?)
|
||||
},
|
||||
local_pref: rule.local_pref,
|
||||
marks: Marks::parse(&[], &rule.add_communities, &rule.add_large_communities)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_port(port: u32) -> Result<u16, ConfigError> {
|
||||
match port {
|
||||
0 => Ok(DEFAULT_BGP_PORT),
|
||||
@@ -615,6 +647,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_rules_are_parsed_in_order() {
|
||||
let policy = import_of(
|
||||
"import {
|
||||
rules { match_communities: \"65000:101:2\" local_pref: 130 }
|
||||
rules { match_prefixes { prefix: \"203.0.113.0/24\" }
|
||||
add_large_communities: \"65000:31:10\" }
|
||||
}",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(policy.rules.len(), 2);
|
||||
assert_eq!(policy.rules[0].local_pref, Some(130));
|
||||
assert!(policy.rules[0].prefixes.is_none());
|
||||
let prefix = IpPrefix::try_from("203.0.113.0/24").unwrap();
|
||||
assert!(policy.rules[1].holds_for_prefix(&prefix));
|
||||
assert!(!policy.rules[1].holds_for_prefix(&IpPrefix::try_from("198.51.100.0/24").unwrap()));
|
||||
assert!(policy.rules[1].holds_for(&[]), "no communities asked for");
|
||||
|
||||
// A rule that is wrong is named by its place in the list.
|
||||
let wrong =
|
||||
import_of("import { rules {} rules { match_communities: \"65000\" } }").unwrap_err();
|
||||
assert!(wrong.to_string().contains("rule 2: "), "{wrong}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sessions_are_put_in_groups() {
|
||||
let config = parse(&format!(
|
||||
|
||||
@@ -263,6 +263,12 @@ pub enum ImportProblem {
|
||||
PathLength(u32),
|
||||
#[error(transparent)]
|
||||
Marks(#[from] crate::marks::MarksProblem),
|
||||
#[error("rule {number}: {problem}")]
|
||||
Rule {
|
||||
/// From 1, as an operator counts.
|
||||
number: usize,
|
||||
problem: Box<ImportProblem>,
|
||||
},
|
||||
}
|
||||
|
||||
/// What is wrong with an `export` policy.
|
||||
|
||||
+62
-21
@@ -68,12 +68,64 @@ fn part<T: std::str::FromStr>(text: &str, whole: &str) -> Result<Option<T>, Mark
|
||||
}
|
||||
}
|
||||
|
||||
/// Communities, standard and large, by patterns of them: `64601:100`,
|
||||
/// `64601:*`, `64601:2:*`.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct Patterns {
|
||||
standard: Vec<Pattern>,
|
||||
large: Vec<LargePattern>,
|
||||
}
|
||||
|
||||
impl Patterns {
|
||||
/// From the configuration: patterns of standard communities (two
|
||||
/// parts) and of large ones (three).
|
||||
pub fn parse(texts: &[String]) -> Result<Self, MarksProblem> {
|
||||
let mut patterns = Self::default();
|
||||
for text in texts {
|
||||
let parts: Vec<&str> = text.split(':').collect();
|
||||
match parts.as_slice() {
|
||||
[high, low] => patterns
|
||||
.standard
|
||||
.push(Pattern(part(high, text)?, part(low, text)?)),
|
||||
[admin, first, second] => patterns.large.push(LargePattern(
|
||||
part(admin, text)?,
|
||||
part(first, text)?,
|
||||
part(second, text)?,
|
||||
)),
|
||||
_ => return Err(MarksProblem::Pattern(text.clone())),
|
||||
}
|
||||
}
|
||||
Ok(patterns)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.standard.is_empty() && self.large.is_empty()
|
||||
}
|
||||
|
||||
fn matches(&self, community: (u16, u16)) -> bool {
|
||||
self.standard
|
||||
.iter()
|
||||
.any(|pattern| pattern.matches(community))
|
||||
}
|
||||
|
||||
fn matches_large(&self, community: &LargeCommunity) -> bool {
|
||||
self.large.iter().any(|pattern| pattern.matches(community))
|
||||
}
|
||||
|
||||
/// Whether `attributes` have a community that one of the patterns
|
||||
/// matches.
|
||||
pub fn match_any(&self, attributes: &[PathAttribute]) -> bool {
|
||||
let (standard, large) = communities_of(attributes);
|
||||
standard.iter().any(|community| self.matches(*community))
|
||||
|| large.iter().any(|community| self.matches_large(community))
|
||||
}
|
||||
}
|
||||
|
||||
/// What is done to the communities of every route that goes one way on
|
||||
/// one session: some are taken off, then some are put on.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct Marks {
|
||||
remove: Vec<Pattern>,
|
||||
remove_large: Vec<LargePattern>,
|
||||
remove: Patterns,
|
||||
add: Vec<(u16, u16)>,
|
||||
add_large: Vec<LargeCommunity>,
|
||||
}
|
||||
@@ -86,21 +138,10 @@ impl Marks {
|
||||
add: &[String],
|
||||
add_large: &[String],
|
||||
) -> Result<Self, MarksProblem> {
|
||||
let mut marks = Self::default();
|
||||
for text in remove {
|
||||
let parts: Vec<&str> = text.split(':').collect();
|
||||
match parts.as_slice() {
|
||||
[high, low] => marks
|
||||
.remove
|
||||
.push(Pattern(part(high, text)?, part(low, text)?)),
|
||||
[admin, first, second] => marks.remove_large.push(LargePattern(
|
||||
part(admin, text)?,
|
||||
part(first, text)?,
|
||||
part(second, text)?,
|
||||
)),
|
||||
_ => return Err(MarksProblem::Pattern(text.clone())),
|
||||
}
|
||||
}
|
||||
let mut marks = Self {
|
||||
remove: Patterns::parse(remove)?,
|
||||
..Self::default()
|
||||
};
|
||||
for text in add {
|
||||
let community = parse_standard(text)?;
|
||||
if !marks.add.contains(&community) {
|
||||
@@ -122,9 +163,9 @@ impl Marks {
|
||||
|
||||
/// Does it to `attributes`.
|
||||
pub fn apply(&self, attributes: &mut Vec<PathAttribute>) {
|
||||
if !self.remove.is_empty() || !self.add.is_empty() {
|
||||
if !self.remove.standard.is_empty() || !self.add.is_empty() {
|
||||
let mut communities = take_communities(attributes);
|
||||
communities.retain(|community| !self.remove.iter().any(|p| p.matches(*community)));
|
||||
communities.retain(|community| !self.remove.matches(*community));
|
||||
for community in &self.add {
|
||||
if !communities.contains(community) {
|
||||
communities.push(*community);
|
||||
@@ -132,9 +173,9 @@ impl Marks {
|
||||
}
|
||||
put_communities(attributes, communities);
|
||||
}
|
||||
if !self.remove_large.is_empty() || !self.add_large.is_empty() {
|
||||
if !self.remove.large.is_empty() || !self.add_large.is_empty() {
|
||||
let mut large = take_large_communities(attributes);
|
||||
large.retain(|community| !self.remove_large.iter().any(|p| p.matches(community)));
|
||||
large.retain(|community| !self.remove.matches_large(community));
|
||||
for community in &self.add_large {
|
||||
if !large.contains(community) {
|
||||
large.push(community.clone());
|
||||
|
||||
@@ -37,7 +37,7 @@ use bgp_primitives::constants::IpVersion;
|
||||
use bgp_primitives::ip_prefix::IpPrefix;
|
||||
|
||||
use crate::address::PeerAddress;
|
||||
use crate::marks::{self, Marks, Scrub};
|
||||
use crate::marks::{self, Marks, Patterns, Scrub};
|
||||
use crate::sync::IgnorePoison;
|
||||
|
||||
/// The longest prefixes routed between networks: anything more specific is
|
||||
@@ -119,6 +119,50 @@ pub struct ImportPolicy {
|
||||
/// The LOCAL_PREF every route that is taken is given; `None` leaves
|
||||
/// it at the default.
|
||||
pub local_pref: Option<u32>,
|
||||
/// What is done to some of the routes that are taken, beyond the
|
||||
/// above: each route by the first rule that holds for it.
|
||||
pub rules: Vec<ImportRule>,
|
||||
}
|
||||
|
||||
/// What is done to the routes with some communities, or for some
|
||||
/// prefixes, as they are taken.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ImportRule {
|
||||
/// The rule is for routes with one of these communities, as the
|
||||
/// route has them once the import policy has marked it; for any if
|
||||
/// empty.
|
||||
pub communities: Patterns,
|
||||
/// And for routes whose prefix is in these; for any if `None`.
|
||||
pub prefixes: Option<PrefixSet>,
|
||||
/// The LOCAL_PREF it gives them, in place of the policy's.
|
||||
pub local_pref: Option<u32>,
|
||||
/// Communities it puts on them.
|
||||
pub marks: Marks,
|
||||
}
|
||||
|
||||
impl ImportRule {
|
||||
/// Whether the rule is for routes with `attributes`, as far as their
|
||||
/// communities go: the same for all of an UPDATE's routes.
|
||||
pub fn holds_for(&self, attributes: &[PathAttribute]) -> bool {
|
||||
self.communities.is_empty() || self.communities.match_any(attributes)
|
||||
}
|
||||
|
||||
/// Whether it is for routes for `prefix`, as far as that goes.
|
||||
pub fn holds_for_prefix(&self, prefix: &IpPrefix) -> bool {
|
||||
self.prefixes
|
||||
.as_ref()
|
||||
.is_none_or(|prefixes| prefixes.allows(prefix))
|
||||
}
|
||||
|
||||
pub fn apply(&self, attributes: &mut Vec<PathAttribute>) {
|
||||
self.marks.apply(attributes);
|
||||
if let Some(local_pref) = self.local_pref {
|
||||
marks::put(
|
||||
attributes,
|
||||
PathAttribute::LocalPref(LocalPrefPathAttribute(local_pref)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ImportPolicy {
|
||||
@@ -146,6 +190,7 @@ impl Default for ImportPolicy {
|
||||
scrub: None,
|
||||
marks: Marks::default(),
|
||||
local_pref: None,
|
||||
rules: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+91
-23
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
|
||||
use bgp_packet::message::{NlriNextHop, PathAttribute, UpdateMessage, only_to_customer_wire};
|
||||
use bgp_primitives::constants::IpVersion;
|
||||
use bgp_primitives::ip_prefix::Nlri;
|
||||
use bgp_primitives::ip_prefix::{IpPrefix, Nlri};
|
||||
use bgp_primitives::parser::EncodeError;
|
||||
use bgp_rib::table::{AttrSet, Outcome, PeerIndex, RibTable};
|
||||
use bgp_rpki::{PathValidity, Validity};
|
||||
@@ -130,6 +130,7 @@ pub(crate) fn apply(
|
||||
refused_attributes: OnceCell::new(),
|
||||
mark: only_to_customer_mark(import, update),
|
||||
changed: OnceCell::new(),
|
||||
rules_holding: OnceCell::new(),
|
||||
};
|
||||
applying.withdraw();
|
||||
// Routes in the body have their next hop in the NEXT_HOP attribute,
|
||||
@@ -187,6 +188,10 @@ struct Applying<'a> {
|
||||
/// The UPDATE's attributes as the policy changes them, if it does:
|
||||
/// made when the first route is taken.
|
||||
changed: OnceCell<Vec<PathAttribute>>,
|
||||
/// For each of the policy's rules, whether it holds for the UPDATE's
|
||||
/// routes as far as their communities go: asked once, if there are
|
||||
/// rules.
|
||||
rules_holding: OnceCell<Vec<bool>>,
|
||||
}
|
||||
|
||||
impl Applying<'_> {
|
||||
@@ -267,16 +272,85 @@ impl Applying<'_> {
|
||||
if accepted.is_empty() && invalid.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let attrs = self.attributes(next_hop)?;
|
||||
for route in invalid {
|
||||
self.held.hold(route, &attrs);
|
||||
if self.import.policy.rules.is_empty() {
|
||||
let attrs = self.attributes(next_hop, None)?;
|
||||
for route in invalid {
|
||||
self.held.hold(route, &attrs);
|
||||
}
|
||||
if !accepted.is_empty() {
|
||||
self.applied.outcome += self.table.announce(self.import.peer, &attrs, &accepted);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if !accepted.is_empty() {
|
||||
self.applied.outcome += self.table.announce(self.import.peer, &attrs, &accepted);
|
||||
// A rule may hold for some of the routes and not for others: they
|
||||
// go in with an attribute set for each rule that holds, and one
|
||||
// for those for which none does.
|
||||
let mut by_rule: Vec<(Option<usize>, Vec<Nlri>, Vec<Nlri>)> = Vec::new();
|
||||
let routes = accepted.iter().map(|route| (route, true));
|
||||
for (route, taken) in routes.chain(invalid.iter().map(|route| (route, false))) {
|
||||
let rule = self.rule_for(&route.prefix);
|
||||
let at = match by_rule.iter().position(|(of, ..)| *of == rule) {
|
||||
Some(at) => at,
|
||||
None => {
|
||||
by_rule.push((rule, Vec::new(), Vec::new()));
|
||||
by_rule.len() - 1
|
||||
}
|
||||
};
|
||||
if taken {
|
||||
by_rule[at].1.push(*route);
|
||||
} else {
|
||||
by_rule[at].2.push(*route);
|
||||
}
|
||||
}
|
||||
for (rule, taken, held) in by_rule {
|
||||
let attrs = self.attributes(next_hop, rule)?;
|
||||
for route in held {
|
||||
self.held.hold(route, &attrs);
|
||||
}
|
||||
if !taken.is_empty() {
|
||||
self.applied.outcome += self.table.announce(self.import.peer, &attrs, &taken);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The first of the policy's rules that holds for the route for
|
||||
/// `prefix`, if one does.
|
||||
fn rule_for(&self, prefix: &IpPrefix) -> Option<usize> {
|
||||
let rules = &self.import.policy.rules;
|
||||
let holding = self.rules_holding.get_or_init(|| {
|
||||
// Rules look at the route as the policy keeps it: with the
|
||||
// communities the session puts on.
|
||||
let kept = self.changed();
|
||||
rules.iter().map(|rule| rule.holds_for(kept)).collect()
|
||||
});
|
||||
rules
|
||||
.iter()
|
||||
.zip(holding)
|
||||
.position(|(rule, holds)| *holds && rule.holds_for_prefix(prefix))
|
||||
}
|
||||
|
||||
/// The UPDATE's attributes as its routes are kept, but for what a
|
||||
/// rule does: as the policy changes them, without the prefixes, and
|
||||
/// marked "only to customer" if they are to be.
|
||||
fn changed(&self) -> &Vec<PathAttribute> {
|
||||
self.changed.get_or_init(|| {
|
||||
let mut attributes = self.update.path_attributes.clone();
|
||||
// Not the route's own: they hold the prefixes.
|
||||
attributes.retain(|attribute| {
|
||||
!matches!(
|
||||
attribute,
|
||||
PathAttribute::MpReachNlri(_) | PathAttribute::MpUnreachNlri(_)
|
||||
)
|
||||
});
|
||||
self.import.policy.change(&mut attributes);
|
||||
if let Some(marked_by) = self.mark {
|
||||
attributes.push(PathAttribute::OnlyToCustomer(marked_by));
|
||||
}
|
||||
attributes
|
||||
})
|
||||
}
|
||||
|
||||
/// Judges what routes announced together have in common: the path,
|
||||
/// and the next hop. `routes` is their IP version. Returns the AS they
|
||||
/// originate from, if the path says, and their next hop.
|
||||
@@ -365,28 +439,22 @@ impl Applying<'_> {
|
||||
}
|
||||
|
||||
/// The attribute set the UPDATE's routes go into the table with.
|
||||
fn attributes(&self, next_hop: Option<&NlriNextHop>) -> Result<Arc<AttrSet>, EncodeError> {
|
||||
fn attributes(
|
||||
&self,
|
||||
next_hop: Option<&NlriNextHop>,
|
||||
rule: Option<usize>,
|
||||
) -> Result<Arc<AttrSet>, EncodeError> {
|
||||
let store = self.table.attrs();
|
||||
if let Some(rule) = rule {
|
||||
let mut attributes = self.changed().clone();
|
||||
self.import.policy.rules[rule].apply(&mut attributes);
|
||||
return store.intern(&attributes, next_hop);
|
||||
}
|
||||
// What the policy puts on the peer's routes (communities that say
|
||||
// where they were learned, a preference) is the same for all of
|
||||
// an UPDATE's routes, and worked out once for them.
|
||||
if self.import.policy.changes(&self.update.path_attributes) {
|
||||
let changed = self.changed.get_or_init(|| {
|
||||
let mut attributes = self.update.path_attributes.clone();
|
||||
// Not the route's own: they hold the prefixes.
|
||||
attributes.retain(|attribute| {
|
||||
!matches!(
|
||||
attribute,
|
||||
PathAttribute::MpReachNlri(_) | PathAttribute::MpUnreachNlri(_)
|
||||
)
|
||||
});
|
||||
self.import.policy.change(&mut attributes);
|
||||
if let Some(marked_by) = self.mark {
|
||||
attributes.push(PathAttribute::OnlyToCustomer(marked_by));
|
||||
}
|
||||
attributes
|
||||
});
|
||||
return store.intern(changed, next_hop);
|
||||
return store.intern(self.changed(), next_hop);
|
||||
}
|
||||
let Some(marked_by) = self.mark else {
|
||||
if let Some(set) = self
|
||||
|
||||
Reference in new issue
Block a user