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:
Rayhaan JaufeerallyandClaude Opus 5.5 committed 2026-10-05 16:34:17 +02:00
1 parent 4d13336874
commit 4e945b278f
9 files changed
+477 -66

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(())
}
+28
View File
@@ -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
View File
@@ -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!(
+6
View File
@@ -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
View File
@@ -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());
+46 -1
View File
@@ -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(),
}
}
}
+87 -19
View File
@@ -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)?;
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(());
}
// 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
+75
View File
@@ -268,6 +268,81 @@ advertised it. These values do not overlap the
or the standard communities `64500:n`, which control communities interpret
as "advertise to AS n".
## Recording Where Routes Are Learned
Informational communities record, on each route, where the local AS
learned it. Other routers in the local AS, neighbors, and looking glasses
can then see it, and import rules and control communities can act on it.
A consistent scheme uses large communities with a function number of 100
or higher, for example:
| Community | Meaning |
|---|---|
| `64500:100:R` | Learned by router `R`. |
| `64500:101:V` | Learned at venue `V`: an Internet exchange, a transit provider, or customers. |
| `64500:102:S` | Learned over interconnection `S`. |
Each router adds them with `add_large_communities` on every EBGP session:
```textproto
peers {
name: "exchange-a-rs1-v4"
import {
add_large_communities: "64500:100:1"
add_large_communities: "64500:101:1"
add_large_communities: "64500:102:1001"
}
}
```
The communities are retained on IBGP sessions, so that each router sees
where the other routers learned their routes. Because Ferrix removes the
local AS's communities from routes from EBGP neighbors (see
[Communities of the Local AS](#communities-of-the-local-as)), a neighbor
cannot set them: every such community on a route was added by a router of
the local AS.
## Import Rules
Import rules apply local preference and communities to some routes only:
routes with certain communities, routes for certain prefixes, or both.
Ferrix evaluates the rules of a session in order, and applies the first
rule that matches a route. The communities that the rules match are those
of the route after the session's other import statements are applied, so
rules can match the session's own informational communities.
The following example, on an IBGP session, prefers routes that the other
router learned at exchange A over routes that it learned at exchange B,
and marks the routes of one prefix:
```textproto
peers {
name: "router-2"
remote_asn: 64500
import {
rules { match_communities: "64500:101:1" local_pref: 200 }
rules { match_communities: "64500:101:2" local_pref: 130 }
rules {
match_prefixes { prefix: "192.0.2.0/24" }
add_large_communities: "64500:31:10"
}
}
}
```
Routes that the rules do not match keep the local preference that they
were advertised with. Routes for one prefix that arrive in one UPDATE
message can match different rules; Ferrix stores each group separately.
> [!TIP]
> Choose local preferences so that the local AS's outbound traffic leaves
> through the same venue through which other networks send their traffic
> in. If a route is preferred at venue A for outbound traffic, use control
> communities to prepend the local AS number at the venues that are less
> preferred. Otherwise traffic to and from a network uses different
> venues, which makes troubleshooting harder and can overload a link that
> was meant as a fallback.
## Communities of the Local AS
By default, Ferrix removes the local AS's communities from every route that
@@ -111,8 +111,24 @@ accepted. See [Import Policy](@/docs/guides/import-policy.md).
| `add_large_communities` <span class="type">string, repeated</span> | None | Specifies large communities added to accepted routes, for example `64500:2:1`. |
| `remove_communities` <span class="type">string, repeated</span> | None | Specifies patterns of communities removed from accepted routes, for example `64500:100`, `64500:*`, or `64500:2:*`. Applied before communities are added. |
| `local_pref` <span class="type">uint32, optional</span> | 100 | Specifies the local preference of accepted routes. |
| `rules` <span class="type">[ImportRule](#importrule), repeated</span> | None | Specifies rules for some of the accepted routes. Ferrix applies the first rule that matches a route. See [Import Rules](@/docs/guides/import-policy.md#import-rules). |
| `accept_control_communities` <span class="type">bool</span> | `false` | Retains the local AS's communities on routes from an EBGP neighbor, so that the neighbor can set control communities on its own routes. By default, Ferrix removes them. Has no effect for an IBGP neighbor. See [Communities of the Local AS](@/docs/guides/import-policy.md#communities-of-the-local-as). |
## ImportRule
A rule for the routes with certain communities or prefixes. A route
matches a rule if it matches every condition that the rule specifies. The
communities are those of the route after the other statements of the
import policy are applied.
| Statement | Default | Description |
|---|---|---|
| `match_communities` <span class="type">string, repeated</span> | Any route | Specifies patterns of communities. A route matches if it has a community that matches one of them. The patterns are those of `remove_communities`. |
| `match_prefixes` <span class="type">[PrefixRange](#prefixrange), repeated</span> | Any prefix | Specifies prefixes. A route matches if its prefix is permitted by one of them. |
| `local_pref` <span class="type">uint32, optional</span> | Not changed | Specifies the local preference of matching routes. Replaces the `local_pref` of the import policy. |
| `add_communities` <span class="type">string, repeated</span> | None | Specifies standard communities added to matching routes. |
| `add_large_communities` <span class="type">string, repeated</span> | None | Specifies large communities added to matching routes. |
## PrefixRange
A prefix and, optionally, the more specific prefixes within it.