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

+51 -3
View File
@@ -23,7 +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/rpki` | Validation with the RPKI: the cache, the data and its age |
| `GET /api/events` | Server-sent events for session and route changes |
| `GET /metricz` | Prometheus metrics |
@@ -87,12 +87,60 @@ 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`.
same as JSON; `bgp_rpki_vrps{family}`, `bgp_rpki_aspas` and
`bgp_rpki_updates_total` are on `/metricz`. Refused routes are counted under
the reasons `RPKI invalid` and `ASPA invalid`.
The connection to the cache is plain TCP, without authentication: run the
validator on the same host or on a network you trust.
## AS path verification (ASPA)
Origin validation says who may originate a prefix. It says nothing about the
rest of the path, so it does not catch a route leak (a network passing its
provider's or peer's routes on to another provider or peer), nor a hijack
that keeps the right origin at the end of a made-up path. ASPAs do: in one,
a network lists its providers, and a path that goes from that network to
anyone else, where only a provider could come next, is invalid
(draft-ietf-sidrops-aspa-verification).
Two things are needed for paths to be verified:
- a validator that hands out ASPAs, which takes version 2 of the
RPKI-to-Router protocol (draft-ietf-sidrops-8210bis). The daemon asks for
version 2 and falls back to 1 or 0 if that is what the validator speaks;
the status page says which is in use, and warns when it is one without
ASPAs;
- `role` on the peer, saying what the peer is to us, because what a path may
look like depends on who hands it over:
| `role` | The peer | Its paths |
|--------|----------|-----------|
| `CUSTOMER` | buys transit from us | must lead up through providers all the way |
| `PEER` | exchanges its own and its customers' routes with us | the same |
| `ROUTE_SERVER` | is an exchange's route server | the same; the path need not begin with the route server's AS |
| `ROUTE_SERVER_CLIENT` | is a client of ours, where we are the route server | the same |
| `PROVIDER` | sells us transit | may lead up, across one peering, and down |
A peer without a role has its origins validated and its paths not.
A path is **invalid** (refused, unless the peer has
`accept_aspa_invalid: true`), **valid**, or **unknown**, which is what most
paths are while few networks have an ASPA; unknown ones are taken. A path
with an AS_SET in it is invalid, and so is one that does not begin with the
peer's AS (other than from a route server). As with origins, routes follow
the data: held while invalid, taken when they no longer are.
```
rpki { caches { address: "127.0.0.1" port: 3323 } }
peers {
name: "transit"
endpoint { address: "192.0.2.1" }
remote_asn: 64496
role: PROVIDER
}
```
## Finding a route
The status page has a look-up box over the received routes, and