Validate route origins against the RPKI and refuse invalid routes

With rpki { caches { ... } } in the configuration the server keeps the
validated ROA payloads of a validator and validates every route from a
peer outside its own AS (RFC 6811). An invalid route is refused, unless
its peer has accept_rpki_invalid. Valid and not-found routes are taken;
validity does not otherwise enter route selection.

Validity is not settled when a route arrives: the data changes every few
minutes. The routes follow, without the peer sending anything again.

- A route refused as invalid is held by its session, out of the table.
  When the data changes the session looks at what it holds, and what is
  no longer invalid goes into the table. A held route is the peer's to
  withdraw or replace like any other.
- A route in the table that becomes invalid is found by a task that
  looks through the table when the data changes, at the prefixes the
  changed VRPs cover, and is taken out and held by its session.
- If the data expires because no cache confirms it, nothing is invalid
  any more and everything held is taken.
- A session holds at most 200,000 routes; past that it asks the peer for
  a route refresh when the data changes.

So that the table can be looked through without parsing attributes, an
attribute set's key now has the route's origin AS. Memory per route is
unchanged at 52 bytes on the real table.

Shown: /api/rpki and a section of the status page with the cache, its
state and the age of the data; each route's validity; each peer's count
of held routes. Refusals are recorded under "RPKI invalid". Metrics:
bgp_rpki_vrps and bgp_rpki_updates_total.

Also: Negotiated says whether the peer supports route refresh, and
docs/RFCs.md is brought up to date.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Rayhaan JaufeerallyandClaude Fable 5.1 committed 2026-10-02 13:39:40 +02:00
1 parent 1f89fb4b13
commit bdaf226a16
30 files changed
+1486 -109

No files matched your search

+29
View File
@@ -23,6 +23,7 @@ When `http_endpoint` is set the binary also serves:
| `GET /api/routes/local` | Originated routes |
| `GET /api/routes/received` | Routes learned from peers; see "Finding a route" |
| `GET /api/routes/rejected` | Routes peers announced that were refused, by peer |
| `GET /api/rpki` | Route origin validation: the cache, the data and its age |
| `GET /api/events` | Server-sent events for session and route changes |
| `GET /metricz` | Prometheus metrics |
@@ -64,6 +65,34 @@ A refused route is dropped, along with the route it would have replaced.
`bgp_routes_rejected_total` counts them by reason, and the status page shows
how many routes each peer has and how many were refused.
## Route origin validation (RPKI)
With `rpki { caches { address: "..." port: 3323 } }` in the configuration,
the daemon keeps the validated ROA payloads of a validator (Routinator,
rpki-client with StayRTR, FORT) over the RPKI-to-Router protocol, and
validates every route from peers outside its own AS (RFC 6811):
- **invalid** (the holder of the addresses authorised another origin, or a
shorter prefix): refused, like any route the import policy refuses, unless
the peer has `accept_rpki_invalid: true`;
- **valid** and **not found**: taken. Validity does not otherwise change
which route is chosen.
The data changes all the time, and the routes follow without the peer having
to send them again: a route in the table that becomes invalid is taken out,
and a route refused as invalid is kept aside ("held") and taken when it no
longer is. If no cache confirms the data for the expire interval (two hours
unless the cache or the configuration says otherwise) it is dropped, and
nothing is invalid any more.
The status page shows the cache, how fresh the data is, each route's
validity, and how many of each peer's routes are held. `/api/rpki` has the
same as JSON; `bgp_rpki_vrps{family}` and `bgp_rpki_updates_total` are on
`/metricz`. Refused routes are counted under the reason `RPKI invalid`.
The connection to the cache is plain TCP, without authentication: run the
validator on the same host or on a network you trust.
## Finding a route
The status page has a look-up box over the received routes, and
+1
View File
@@ -51,6 +51,7 @@ async fn main() -> Result<()> {
local_rib: server.local_rib.clone(),
rib: Arc::clone(&server.rib),
refusals: server.refusals.clone(),
rpki: server.rpki.clone(),
};
tokio::spawn(async move {
if let Err(e) = bgp_web::serve_on(state, listener).await {