~/posts/rpki-route-origin-validation-on-vyos
RPKI Route Origin Validation on VyOS with Routinator
If you run your own ASN in 2025 and you are not dropping RPKI-invalid routes yet, now is a good time. Most large transit providers and IX route servers already filter invalids, but that only protects you from what they see. Validating on your own edge protects you from leaks and hijacks on every session, including direct peerings and PNIs that never pass a route server.
The good news: on VyOS this is a small change. The router itself doesn’t validate anything, it talks to a validator (a “relying party”) over the RTR protocol and only receives the resulting list of Validated ROA Payloads (VRPs). In this post I use Routinator from NLnet Labs as the validator.
Running Routinator
Routinator runs fine on a small VM or in Docker next to your other tooling. It fetches and validates the RPKI repositories of all five RIRs and serves the result over RTR (port 3323) and HTTP (port 8323):
docker run -d --restart=unless-stopped --name routinator \
-p 3323:3323 \
-p 8323:8323 \
nlnetlabs/routinator
The first full sync takes a few minutes. You can follow it in the logs, and once it is done the HTTP interface shows the status and lets you look up prefixes:
docker logs -f routinator
curl -s http://localhost:8323/status
Run two validators, preferably in different locations or on different hypervisors. If the router loses all its RTR sessions it keeps working, but it slowly loses its view of what is valid. More on that below.
Connecting VyOS to the validator
Add both caches to VyOS. The preference decides which one is used first; lower is preferred:
set protocols rpki cache 192.0.2.10 port '3323'
set protocols rpki cache 192.0.2.10 preference '1'
set protocols rpki cache 198.51.100.10 port '3323'
set protocols rpki cache 198.51.100.10 preference '2'
commit
Check that the session is up and that the prefix table is filled:
show rpki cache-connection
show rpki prefix-table
The prefix table should contain several hundred thousand entries for IPv4 and IPv6 combined. If it is empty, check the firewall between router and validator first; RTR is plain TCP.
Filtering invalids
Validation on its own changes nothing: VyOS now knows the validation state of each route, but still accepts all of them. To act on it, match on the RPKI state in your import route-maps. I add one rule at the top of every eBGP import policy:
set policy route-map TRANSIT-IN rule 5 action 'deny'
set policy route-map TRANSIT-IN rule 5 match rpki 'invalid'
set policy route-map PEERS-IN rule 5 action 'deny'
set policy route-map PEERS-IN rule 5 match rpki 'invalid'
commit
The rest of the route-map (local-preference, communities, prefix filters) continues from rule 10 onwards, exactly as before. Because the deny rule sits on top, an invalid route never reaches the rest of the policy.
A few choices I made on purpose:
- Only drop
invalid, nevernotfound. A large part of the routing table still has no ROA at all. Dropping “not found” would break a big chunk of the internet. - Don’t prefer
validroutes over the others. It is tempting to give valid routes a higher local-preference, but that overrides your normal traffic engineering for the sake of a signal that says nothing about path quality. - Filter at the edge only. Apply the check on eBGP sessions, where routes enter your network. There is no need to repeat it on iBGP.
What happens when the validator is down
This is the obvious next question. FRR (the routing daemon in VyOS) keeps the last received VRPs for a while after the RTR session drops. When those expire and no cache is reachable, every route becomes notfound, and since we only drop invalid, everything is accepted again.
In other words: RPKI on VyOS fails open. You lose protection, not connectivity. That is the right trade-off, but it does mean you should monitor the RTR sessions: alert when show rpki cache-connection reports no connected cache, for example from Zabbix.
Testing
After committing, test from a host behind the router:
- isbgpsafeyet.com from Cloudflare loads a resource from a deliberately invalid prefix. If your network filters correctly, that request fails and the page tells you so.
- On the router, look up a single prefix and check its state:
vtysh -c "show rpki prefix 1.1.1.0/24"
Don’t forget your own ROAs
Filtering is only half the work. Make sure every prefix you announce is covered by a ROA with the correct origin ASN and a sensible max-length, otherwise other networks will drop your routes. You create ROAs in the RIPE NCC portal (or that of your RIR). Before you create a ROA, check that it matches what you actually announce. bgp.tools shows the RPKI state of your announcements per prefix.