-
Notifications
You must be signed in to change notification settings - Fork 3
rpki
Pre-Alpha. This page describes behavior that may change.
Ze validates received BGP routes against RPKI ROA data. Invalid routes are rejected before they enter the RIB. The feature connects to RTR cache servers (RFC 8210), downloads Validated ROA Payloads, and applies the RFC 6811 origin validation algorithm to each received prefix. The implementation is a plugin (bgp-rpki) that depends on bgp-adj-rib-in for the validation gate.
bgp-rpki and bgp-adj-rib-in are internal plugins loaded automatically.
bgp {
rpki {
cache-server 192.0.2.1 {
port 323;
}
}
peer upstream {
connection {
remote { ip 10.0.0.1; }
local { ip 10.0.0.2; }
}
session {
asn { local 65000; remote 65001; }
family { ipv4/unicast { prefix { maximum 1000000; } } }
}
process bgp-rpki { receive [ update ]; }
process bgp-adj-rib-in { receive [ update state ]; }
}
}
bgp-rpki must be bound to the peer with process bgp-rpki { receive [ update ] }. bgp-adj-rib-in must be bound with process bgp-adj-rib-in { receive [ update state ] }. bgp-adj-rib-in is what holds routes pending validation.
| Path | Type | Default | Description |
|---|---|---|---|
rpki/cache-server <addr> |
list | required | RTR cache server, keyed by IP or hostname. |
rpki/cache-server/port |
uint16 | 323 | RTR TCP port. |
rpki/cache-server/preference |
uint8 | 100 | Server preference. Lower is preferred. |
rpki/validation-timeout |
uint16 | 30 | Seconds before fail-open on pending routes. |
rpki/aspa/validation |
boolean | false | Enable ASPA path verification using RTR v2 ASPA records. |
rpki/action/invalid |
enum | reject |
Action for Invalid routes: reject, log-only, accept. |
rpki/action/not-found |
enum | accept |
Action for NotFound routes: accept, reject, log-only. |
Multiple cache servers are supported for redundancy. The VRP tables from all servers are merged as a union.
The action block (both the origin action and the ASPA action) can also be set under a
peer or a group, overriding the global rpki/action for routes learned from that peer.
Only the action blocks are per-peer; cache-server, validation-timeout, and aspa/validation
remain global. Resolution is peer > group > global, per leaf: a leaf left unset on the peer
inherits the group's value, then the global value. Peers with a static connection/remote/ip
can be overridden; dynamically-addressed peers use the global actions.
bgp {
rpki { /* global: caches + baseline actions */
cache-server 192.0.2.1 { port 323; }
action { invalid reject; not-found accept; }
}
group transit {
rpki { action { invalid reject; } } /* group default for members */
peer customer-a {
rpki { action { invalid log-only; } } /* per-peer override; not-found inherits global */
}
}
}
show bgp rpki status reports the effective global actions and the resolved per-peer overrides
with the source of each leaf.
| Path | Type | Default | Description |
|---|---|---|---|
peer / rpki / blackhole-exempt |
boolean | false | Keep a BLACKHOLE-tagged route whose only origin-validation fault is prefix length. |
The leaf resolves peer then group, and it has NO global level. RFC 7999 Section 3.3 binds the blackhole agreement to one BGP session, so a daemon-wide exemption would reach sessions that agreed to nothing.
A blackhole prefix is as long as possible, usually a /32 or a /128, while a ROA for the covering
block carries its maxLength at the aggregate. RFC 6811 then makes the announcement Invalid on
length alone, and a session running action { invalid reject; } drops it before anything can
honor it. RFC 7999 Section 3.3 states that an operator must make sure origin validation does not
block a legitimate announcement carrying BLACKHOLE, and this leaf is that mechanism.
bgp {
peer transit-a {
rpki {
action { invalid reject; }
blackhole-exempt true;
}
blackhole {
communities blackhole;
prefixes 192.0.2.0/24;
}
}
}
The exemption is narrow. It applies only when a covering VRP names the route's own origin AS and disagrees on nothing but length. A wrong origin AS stays Invalid, which is the hijack RFC 6811 exists to catch. A prefix with no covering VRP is NotFound rather than Invalid, so the exemption never reaches it.
Set it on the same session that names a blackhole community. The exemption reads the communities
THAT session agreed to, so a peer running RTBH on 65001:666 gets it for 65001:666. On a
session that names no community the leaf does nothing: it would accept a route it would have
rejected and discard nothing, and ze logs that at configure time naming the peer. See
blackhole honoring for the blackhole container.
Each received route gets one of three states, per RFC 6811.
| State | Meaning | Default action |
|---|---|---|
| Valid | Origin AS and prefix length match a VRP. | Accept. |
| Invalid | A VRP covers the prefix, but the origin AS or the prefix length does not match. | Reject. |
| NotFound | No VRP covers the prefix. | Accept. |
A prefix ze cannot parse gets Invalid and a warning, never NotFound. NotFound states that the VRP
set was consulted and covers nothing, and the default not-found accept action accepts a route on
that reading. A prefix that was never validated fails closed instead.
Override the actions with rpki/action/invalid and rpki/action/not-found. Setting action/invalid reject is what you want in production: it implements RPKI-based route origin validation. Setting it to log-only is useful during deployment while you build confidence in your cache. The action can also be overridden per peer or per group (see above).
When the VRP set changes (a cache update adds or removes ROAs), Ze automatically re-validates every tracked route and applies the current action to any whose state changed, per RFC 6811 Section 4. You do not need to clear or refresh the session to pick up a new ROA.
The decision reaches routes that are already INSTALLED, not only routes still pending. An accept rewrites the state in place and keeps the route's sequence number, so the route is not re-sent to a peer replaying from a cursor. A reject removes the route from the Adj-RIB-In.
This matters most when UPDATEs arrive before the first sync completes. Those routes validate
NotFound against an empty VRP set, and the default not-found accept installs them. The
re-validation after the sync is what turns an RPKI-Invalid one into a reject.
The order is deterministic and the same on every UPDATE.
- Ze connects to the configured RTR cache servers and downloads the VRPs.
- A BGP UPDATE arrives from a peer.
-
bgp-adj-rib-instores the route as "pending". -
rpkiextracts the origin AS (the rightmost AS in the final AS_SEQUENCE segment). - For each NLRI prefix in the UPDATE,
rpkilooks up covering VRPs and computes the validation state. - Valid and NotFound routes are promoted to "installed". Invalid routes are discarded (or logged, depending on policy).
If the bgp-rpki plugin does not respond within validation-timeout seconds (default 30), pending routes are automatically promoted. This prevents route black-holing when the RPKI infrastructure is down.
If every RTR cache server disconnects, the existing VRP cache is retained until a connection is re-established. Routes continue to be validated against the last known good cache. The failure mode is graceful: a cache outage does not immediately invalidate everything.
A few edge cases worth knowing about.
| AS_PATH | Origin AS | Result |
|---|---|---|
Normal sequence [65000 65001]
|
65001 (rightmost) | Normal validation. |
Ends with AS_SET {65001 65002}
|
None | Always Invalid when a covering VRP exists. |
| Empty (iBGP, no prepend) | None | NotFound. |
An AS_SET at the end of the path has no single origin, so RPKI cannot validate it. The safest default is Invalid when a VRP covers the prefix.
ze cli -c "show bgp rpki status" # RTR session count, sync state, VRP counts
ze cli -c "show bgp rpki cache" # Cache server connection details
ze cli -c "show bgp rpki roa" # ROA table summary
ze cli -c "show bgp rpki summary" # Validation statistics
ze cli -c "show bgp rpki aspa" # ASPA records and validation state
ze cli -c "request bgp rpki validate <prefix> <origin-asn>" # Manual validation checkcache, summary, roa <prefix> and request bgp rpki validate each carry the synced flag
too, because a not-found verdict from a cache that never synced is not a verdict about the
prefix.
Example output:
{"running":true,"vrp-count-ipv4":3,"vrp-count-ipv6":0,"sessions":1,"sessions-synced":1,"synced":true,"aspa-enabled":false,"aspa-records":0,"cache-servers":[{"address":"192.0.2.1","port":3323,"state":"idle","synced":true,"version":2}],"actions":{"invalid":"reject","not-found":"accept","aspa-invalid":"log-only","aspa-unknown":"accept"},"peer-actions":[]}running says that a cache server is CONFIGURED. synced says that a cache server completed a
sync and gave ze a VRP set. The two are different states, and the difference is the one an
operator cannot otherwise see: while synced is false, ze holds no VRP set at all, so every
prefix reads not-found and the default not-found accept action accepts it. A cache whose PDUs
ze mis-decodes reads as a healthy validating router on running alone.
sessions-synced counts the cache servers that delivered data, and each entry in cache-servers
carries its own synced. state is the RTR connection state, which returns to idle between
polls even after a successful sync, so it is not a sync indicator either.
RFC 8210 Section 6 splits two intervals, and ze uses each for its own event.
| Interval | Source | Counted from | Used when |
|---|---|---|---|
| Refresh | End Of Data PDU | receipt of that PDU | the previous query SUCCEEDED |
| Retry | End Of Data PDU | the failure | the query FAILED, or none has ever succeeded |
Waiting the retry interval after a completed sync polls the cache more often than the cache asked for. Against a cache sending refresh 3600 and retry 600, six times more often.
The bgp-rpki plugin emits validation events that other plugins can subscribe to. A plugin that subscribes to bgp-rpki direction received receives a JSON event for each validated UPDATE.
{
"type": "bgp",
"bgp": {
"peer": {"address": "10.0.0.1", "remote": {"as": 65001}},
"message": {"id": 42, "type": "rpki"},
"rpki": {
"ipv4/unicast": {
"10.0.1.0/24": "valid",
"10.0.2.0/24": "invalid"
}
}
}
}When the ROA cache is empty, the event is "rpki": {"status": "unavailable"}.
Instead of receiving separate UPDATE and RPKI events, subscribe to merged update-rpki events from the bgp-rpki-decorator plugin. Each event carries the full UPDATE plus the validation state in one envelope.
plugin {
internal rpki-decorator {
use bgp-rpki-decorator
}
}
bgp {
peer upstream {
process my-consumer { receive [ update-rpki ] }
process bgp-rpki { receive [ update ] }
process rpki-decorator { receive [ update rpki ] }
process bgp-adj-rib-in { receive [ update state ] }
}
}
The merged event is the full UPDATE JSON with an rpki section injected. If the RPKI validation does not arrive within the decorator's 2-second timeout, the event is emitted without the rpki section (graceful degradation).
ASPA (Autonomous System Provider Authorization) verifies that AS_PATH hops are authorized by provider-customer relationships. ASPA records are distributed via RTR v2 (RFC 9582) alongside VRPs. Ze implements the upstream path verification algorithm from draft-ietf-sidrops-aspa-verification Section 6.
ASPA is opt-in. Enable it with aspa { validation true; } under the rpki block. It requires a cache server that supports RTR v2 (the session negotiates v2 automatically when the server supports it, falling back to v1 otherwise).
By default, ASPA operates in informational mode: the state is included in the RPKI event JSON but does not trigger accept/reject decisions. To enforce ASPA results, set the ASPA action:
bgp {
rpki {
cache-server 192.0.2.1 { port 323; }
aspa {
validation true;
action { invalid reject; }
}
}
}
| Path | Type | Default | Description |
|---|---|---|---|
rpki/aspa/action/invalid |
enum | log-only |
Action for ASPA Invalid paths: reject, log-only, accept. |
rpki/aspa/action/unknown |
enum | accept |
Action for ASPA Unknown paths (missing records for some ASes): accept, reject, log-only. |
The default for invalid is log-only (conservative) rather than reject, because ASPA deployment is incomplete and missing records can cause false Invalid results. Set it to reject once your upstream providers have published ASPA records. Like the origin action, the ASPA action can be overridden per peer or per group.
bgp {
rpki {
cache-server 192.0.2.1 {
port 323;
}
aspa { validation true; }
}
}
Each route receives one of three ASPA states, independent of the origin validation state:
| State | Meaning |
|---|---|
| Valid | Every hop pair in the AS_PATH is authorized by an ASPA record. |
| Invalid | At least one hop pair has an ASPA record that does not list the provider candidate. |
| Unknown | No ASPA records exist for one or more customer ASNs in the path. |
- The RTR session negotiates v2 with the cache server (falls back to v1 on error code 4, which disables ASPA since v1 has no ASPA PDU)
- The cache server sends ASPA PDUs (type 11, RFC 9582 Section 5.12) alongside VRPs
- For each received UPDATE, the plugin normalizes the AS_PATH: removes consecutive duplicate ASNs (prepend artifacts), strips AS_CONFED_SEQUENCE segments, and flags AS_SET or AS_CONFED_SET as unverifiable
- Each adjacent pair (provider candidate, customer) is checked against the ASPA cache
- The result appears as
"aspa-state"in the RPKI event JSON
The "aspa-state" field is included alongside per-prefix origin validation results:
{
"type": "bgp",
"bgp": {
"peer": {"address": "10.0.0.1", "name": "upstream", "remote": {"as": 65001}},
"message": {"id": 42, "type": "rpki"},
"rpki": {
"ipv4/unicast": {
"10.0.1.0/24": "valid"
},
"aspa-state": "valid"
}
}
}When ASPA validation is disabled or the cache has no ASPA records, the "aspa-state" field is omitted from the event.
Routes are tracked with their normalized AS_PATH. When ASPA cache data changes (new or removed records from the RTR server), affected routes are automatically re-validated and updated events are emitted for any route whose ASPA state changed.
The ze-test rtr-mock command supports ASPA records with the --aspa flag:
ze-test rtr-mock --port 3323 \
--vrp 10.0.0.0/8,24,65001 \
--aspa 64502:64501 \
--aspa 64501:64500The format is customer:provider1,provider2,... (repeatable). When ASPA records are present, the mock server responds with RTR v2. Four functional tests validate all ASPA states: rpki-aspa-valid, rpki-aspa-invalid, rpki-aspa-unknown, and rpki-aspa-disabled.
ze-test rpki --port 3323ze-test rpki starts a deterministic mock RTR server that auto-generates VRPs based on the first octet of each /8 prefix. Validation states are predictable for routes from AS 65001.
| First octet | State |
|---|---|
| 0, 3, 6, 9, 12, ... | Valid |
| 1, 4, 7, 10, 13, ... | Invalid |
| 2, 5, 8, 11, 14, ... | NotFound |
When the bgp-rpki plugin is not loaded, routes flow directly into bgp-adj-rib-in with zero overhead. No pending state, no validation delay. The validation gate only activates when the bgp-rpki plugin sends bgp-adj-rib-in enable-validation during startup.
| Symptom | Cause | Fix |
|---|---|---|
| Routes delayed 30 seconds then accepted | RTR cache server unreachable. | Check connectivity, verify the port. |
| All routes Invalid | Wrong cache server data, or origin AS mismatch. | Check show bgp rpki roa, verify VRP coverage. |
| No VRPs loaded | RTR session not established. | Check show bgp rpki status, verify the cache server is running. |
Every prefix reads not-found
|
No cache server ever delivered data. |
show bgp rpki status reports "synced":false with "running":true. Check the cache server and the RTR version it speaks. |
| The cache is polled far more often than its refresh interval | A ze older than the RFC 8210 Section 6 poll fix. | Upgrade. Ze waits the Refresh Interval after a completed sync and the Retry Interval after a failure. |
| A blackhole announcement is never honored | Origin validation rejected it on length. | Set blackhole-exempt true on the same session (see above). |
| Routes accepted without validation | The bgp-rpki plugin is not bound to the peer. |
Add process bgp-rpki { receive [ update ] } to the peer config. |
- Overview for the surrounding config model.
- BGP peers for the peer-level settings.
-
Community filters for blackhole honoring and the
blackholecontainer. -
Plugins for
bgp-rpkiandbgp-rpki-decorator.
Adapted from main/docs/guide/rpki.md.
Unreviewed draft. This wiki was authored in bulk and has not been reviewed. File corrections on the issue tracker.
- Overview
- YANG Model
- Editor Workflow
- Archive and Rollback
- System
- Interfaces
- VRRP
- BFD
- FIB
- OSPF
- IS-IS
- MPLS / LDP / RSVP-TE
- RSVP-TE
- SRv6
- Static Routes
- Policy Routing
- Firewall
- Traffic Control
- Class of Service
- L2TP/PPP
- PPPoE
- VPP Data Plane
- RPKI
- IPsec VPN
- TACACS+ AAA
- RADIUS AAA
- AS112 DNS
- DNS
- Authorization
- Fleet
- BGP
- Starting and Stopping
- Show Commands
- Monitoring
- Flow Export
- DDoS Mitigation
- Anomaly Detection
- Health Checks
- Audit Trail
- Production Diagnostics
- Logging
- Operational Reports
- Healthcheck
- Self-Update
- Zero-Touch Provisioning
- MRT Analysis
- Upgrade and Restart
- Storage
- Policy
- Core
- Resilience
- Validation
- Capabilities
- Address Families
- Protocol
- Subsystems
- Infrastructure
- Route Server at an IXP
- Transit Edge with RPKI
- Public Looking Glass
- ExaBGP Migration Walkthrough
- FlowSpec Injection
- Chaos-Tested Peering
- AS Path Topology