Verify AS paths with ASPAs, over RTR version 2

Origin validation says who may originate a prefix and nothing about the
rest of the path: it does not catch a route leak, nor a hijack that keeps
the right origin at the end of a made-up path. ASPAs, in which a network
lists its providers, do.

bgp-rpki:
- aspa: the ASPAs and the upstream and downstream verification procedures
  of draft-ietf-sidrops-aspa-verification-24, tested against the draft's
  wording as written.
- rtr: protocol version 2 (draft-ietf-sidrops-8210bis-23) with the ASPA
  PDU and the new error codes.
- client: asks for version 2 and falls back to whatever older version the
  cache speaks; keeps ASPAs with the VRPs, applied whole or not at all;
  publishes which customers' ASPAs changed; tells the cache why it is
  being left (Error Report); does not ask one version's serial of another.

Server:
- A peer has a role (provider, customer, peer, route server, route server
  client), which decides how its paths are verified. Without one they are
  not.
- A route whose path is invalid is refused ("ASPA invalid") and held, like
  one with an invalid origin; accept_aspa_invalid exempts a peer.
- When ASPAs change, routes in the table whose path has a changed customer
  on it are verified again, and held routes are released when they pass
  both checks.
- The revalidator finds a route's peer by index instead of searching a
  list for every route.

The status page and API show each peer's role, each route's path validity,
the number of ASPAs and the RTR version in use; bgp_rpki_aspas is on
/metricz.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-02 14:12:59 +02:00
1 parent bdaf226a16
commit 8086aa994f
35 files changed
+2484 -359

No files matched your search

+34 -1
View File
@@ -12,7 +12,7 @@ use bgp_rpki::RpkiConfig;
use crate::error::{ConfigError, ImportProblem, OriginateProblem, PeerProblem};
use crate::family::FamilySet;
use crate::policy::{ImportPolicy, PrefixSet};
use crate::policy::{ImportPolicy, PeerRole, PrefixSet};
use crate::proto::config::{self as proto, BgpConfig, OriginatedRoute, Peer};
const DEFAULT_BGP_PORT: u16 = 179;
@@ -64,6 +64,8 @@ pub struct PeerConfig {
pub add_path: Option<AddPathSendRecv>,
/// Which of the peer's routes are taken.
pub import: ImportPolicy,
/// What the peer is to us, if that was said.
pub role: Option<PeerRole>,
}
impl Config {
@@ -225,6 +227,16 @@ fn parse_peer(peer: Peer) -> Result<PeerConfig, PeerProblem> {
None => ImportPolicy::default(),
};
let role = match peer.role.enum_value() {
Ok(proto::Role::ROLE_UNSPECIFIED) => None,
Ok(proto::Role::PROVIDER) => Some(PeerRole::Provider),
Ok(proto::Role::CUSTOMER) => Some(PeerRole::Customer),
Ok(proto::Role::PEER) => Some(PeerRole::Peer),
Ok(proto::Role::ROUTE_SERVER) => Some(PeerRole::RouteServer),
Ok(proto::Role::ROUTE_SERVER_CLIENT) => Some(PeerRole::RouteServerClient),
Err(_) => return Err(PeerProblem::Role),
};
Ok(PeerConfig {
// A peer without a name goes by its address.
name: if peer.name.is_empty() {
@@ -239,6 +251,7 @@ fn parse_peer(peer: Peer) -> Result<PeerConfig, PeerProblem> {
families,
add_path,
import,
role,
})
}
@@ -323,6 +336,7 @@ fn parse_import(import: &proto::ImportPolicy) -> Result<ImportPolicy, ImportProb
)?,
accept_any_first_as: import.accept_any_first_as,
accept_rpki_invalid: import.accept_rpki_invalid,
accept_aspa_invalid: import.accept_aspa_invalid,
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))?,
@@ -737,6 +751,25 @@ mod tests {
);
// A peer can be exempt.
// A peer's paths are verified only if it is said what the peer is.
let role_of = |body: &str| {
parse(&format!(
"{BASE}peers {{ endpoint {{ address: \"192.0.2.2\" }} remote_asn: 1 {body} }}"
))
.map(|config| config.peers[0].role)
};
assert_eq!(role_of("").unwrap(), None);
assert_eq!(role_of("role: CUSTOMER").unwrap(), Some(PeerRole::Customer));
assert_eq!(
role_of("role: ROUTE_SERVER").unwrap(),
Some(PeerRole::RouteServer)
);
assert!(!import_of("").unwrap().accept_aspa_invalid);
assert!(
import_of("import { accept_aspa_invalid: true }")
.unwrap()
.accept_aspa_invalid
);
assert!(!import_of("").unwrap().accept_rpki_invalid);
assert!(
import_of("import { accept_rpki_invalid: true }")