Check roles in OPEN and prevent route leaks with Only to Customer (RFC 9234)
A peer's configured role now does three more things: - Our OPEN carries the BGP Role capability with what we are to the peer. A peer whose own says it is something that does not pair with that, or that sends two different roles, is refused with OPEN Message Error / Role Mismatch. A peer that says nothing is accepted unless require_role is set. A role on a peer in our own AS is a configuration error. - Routes from a provider, a peer or a route server are marked Only to Customer on the way in if they are not already, and our routes are marked on the way out to a customer, a peer or a route server client. - A marked route from a customer or route server client, or from a peer with someone else's mark, is a leak and is refused. No policy setting turns that off. The packet crate gains the Only to Customer attribute (malformed: treat-as-withdraw), and the Role Mismatch subcode is corrected from 8 to 11. The status page shows whether the peer confirmed its role. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
8086aa994f
commit
f9ef781d51
30 files changed
+1128
-25
No files matched your search
@@ -6,6 +6,7 @@ use std::time::Duration;
|
||||
|
||||
use bgp_packet::errors::{BgpError, CeaseSubcode, DecodeError, FsmSubcode, OpenMsgSubcode};
|
||||
use bgp_packet::message::NotificationMessage;
|
||||
use bgp_packet::open::BgpRole;
|
||||
use bgp_primitives::parser::EncodeError;
|
||||
use bgp_rib::RibStopped;
|
||||
use bgp_rib::table::TooManyPeers;
|
||||
@@ -13,6 +14,21 @@ use thiserror::Error;
|
||||
|
||||
use crate::family::FamilySet;
|
||||
use crate::fsm::State;
|
||||
use crate::policy::PeerRole;
|
||||
|
||||
/// Says what is wrong between what a peer is configured as and what it
|
||||
/// says it is.
|
||||
fn role_mismatch(configured: Option<PeerRole>, theirs: Option<BgpRole>) -> String {
|
||||
let configured = configured.map_or("no role is configured".to_string(), |role| {
|
||||
format!("the peer is configured as our {}", role.as_str())
|
||||
});
|
||||
match theirs {
|
||||
Some(theirs) => format!("role mismatch: {configured} and says it is {theirs:?}"),
|
||||
None => format!(
|
||||
"role mismatch: {configured} and does not say what it is, or says more than one thing"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Why a session ended abnormally.
|
||||
#[derive(Debug, Error)]
|
||||
@@ -40,6 +56,13 @@ pub enum SessionError {
|
||||
OpenRefused(BgpError),
|
||||
#[error("peer announced AS{actual} but AS{expected} was configured")]
|
||||
WrongPeerAs { expected: u32, actual: u32 },
|
||||
/// RFC 9234 §4.2. `theirs` is `None` if the peer did not say, or said
|
||||
/// more than one thing.
|
||||
#[error("{}", role_mismatch(*.configured, *.theirs))]
|
||||
RoleMismatch {
|
||||
configured: Option<PeerRole>,
|
||||
theirs: Option<BgpRole>,
|
||||
},
|
||||
#[error("closed by connection collision resolution")]
|
||||
Collision,
|
||||
/// The routing table has no room for another peer.
|
||||
@@ -88,6 +111,7 @@ impl SessionError {
|
||||
}),
|
||||
Self::OpenRefused(error) => error.clone(),
|
||||
Self::WrongPeerAs { .. } => BgpError::OpenMsg(OpenMsgSubcode::BadPeerAs),
|
||||
Self::RoleMismatch { .. } => BgpError::OpenMsg(OpenMsgSubcode::RoleMismatch),
|
||||
Self::Collision => BgpError::Cease(CeaseSubcode::ConnCollisionResolution),
|
||||
Self::TableFull(_) => BgpError::Cease(CeaseSubcode::OutOfResources),
|
||||
Self::PrefixLimitExceeded { .. } => BgpError::Cease(CeaseSubcode::MaxPrefixes),
|
||||
@@ -179,6 +203,10 @@ pub enum PeerProblem {
|
||||
AddressFamily,
|
||||
#[error("add_path has a value that is not one of its settings")]
|
||||
AddPath,
|
||||
#[error("role is set for a peer in our own AS; roles are between networks")]
|
||||
RoleInOwnAs,
|
||||
#[error("require_role is set without a role")]
|
||||
RequireRoleWithoutRole,
|
||||
#[error("role has a value that is not one of its settings")]
|
||||
Role,
|
||||
#[error("import: {0}")]
|
||||
|
||||
Reference in new issue
Block a user