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

+21 -14
View File
@@ -23,6 +23,8 @@ use crate::collision::Direction;
use crate::family::FamilySet;
use crate::fsm::Negotiated;
use crate::metrics::SESSIONS_ESTABLISHED;
use crate::policy::PeerRole;
use crate::rpki::Subject;
use crate::sync::IgnorePoisonRw;
const EVENT_CHANNEL_CAPACITY: usize = 256;
@@ -60,6 +62,8 @@ pub struct ConfiguredPeer {
pub name: String,
pub endpoint: SocketAddr,
pub remote_asn: u32,
/// What the peer is to us, if that was said.
pub role: Option<PeerRole>,
}
/// Counts kept by a session as it goes, for whoever wants to look.
@@ -117,6 +121,8 @@ pub struct SessionHandle {
pub established_at: SystemTime,
/// Whether the peer is in our own AS.
pub ibgp: bool,
/// What the peer is to us, if that is known.
pub role: Option<PeerRole>,
/// Whether the peer negotiated RFC 6793 four-octet ASNs. Determines how
/// AS_PATH is encoded on the wire for this session.
pub four_octet_asn: bool,
@@ -134,9 +140,9 @@ pub struct SessionHandle {
/// How many routes the peer has, and how many were refused.
pub counters: Arc<SessionCounters>,
outbound: mpsc::Sender<Message>,
/// Where the session is told to look at its routes again, if they are
/// subject to route origin validation.
revalidate: Option<mpsc::UnboundedSender<Vec<Nlri>>>,
/// Which of the RPKI's checks the peer's routes are subject to, if
/// any, and where the session is told to look at its routes again.
validation: Option<(Subject, mpsc::UnboundedSender<Vec<Nlri>>)>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
@@ -160,6 +166,7 @@ impl SessionHandle {
local_addr,
established_at: SystemTime::now(),
ibgp: negotiated.ibgp,
role: None,
four_octet_asn: negotiated.four_octet_asn,
families: negotiated.families.clone(),
add_path_receive: negotiated.add_path_receive.clone(),
@@ -167,29 +174,29 @@ impl SessionHandle {
rib_peer,
counters: Arc::default(),
outbound,
revalidate: None,
validation: None,
}
}
/// Marks the session's routes as subject to route origin validation.
/// What is sent with [`SessionHandle::revalidate`] arrives at the
/// receiver returned.
pub(crate) fn validated(&mut self) -> mpsc::UnboundedReceiver<Vec<Nlri>> {
/// Marks the session's routes as subject to validation with the
/// RPKI's data. What is sent with [`SessionHandle::revalidate`]
/// arrives at the receiver returned.
pub(crate) fn validate(&mut self, subject: Subject) -> mpsc::UnboundedReceiver<Vec<Nlri>> {
let (tx, rx) = mpsc::unbounded_channel();
self.revalidate = Some(tx);
self.validation = Some((subject, tx));
rx
}
/// Whether the session's routes are subject to route origin
/// validation.
pub fn is_validated(&self) -> bool {
self.revalidate.is_some()
/// Which of the RPKI's checks the session's routes are subject to, if
/// any.
pub fn validated(&self) -> Option<Subject> {
self.validation.as_ref().map(|(subject, _)| *subject)
}
/// Tells the session that the validation data has changed. `suspects`
/// are routes of its peer's in the table that are now invalid.
pub(crate) fn revalidate(&self, suspects: Vec<Nlri>) {
if let Some(revalidate) = &self.revalidate {
if let Some((_, revalidate)) = &self.validation {
// Fails only if the session has just ended.
let _ = revalidate.send(suspects);
}