DEV Community

pathvector-dev
pathvector-dev

Posted on • Originally published at blog.pathvector.dev

RPKI Origin Validation: Watch BGP Routes Turn Valid, Invalid, and Not Found

Originally published at https://blog.pathvector.dev/protocol-lab-rpki-04/ — part of the free Protocol Lab series.

This post is part of Protocol Lab, a free, hands-on series for learning networking protocols by building and breaking them in a container lab. All the lab material — topologies, configs, and scripts — lives in the repo: github.com/pathvector-studio/protocol-lab.

BGP will happily show you who originated a route — but it can't tell you whether that origin was allowed to. That's the gap RPKI origin validation fills. In this lab, you'll connect FRRouting to a local RPKI-to-Router (RTR) cache and observe all three origin validation states side by side:

  • valid: the route's origin matches a VRP.
  • invalid: the prefix is covered by a VRP, but the origin AS is not authorized.
  • not found: no VRP covers the route.

Reading guide: rfc-notes/rpki-origin-validation.md

Expected time: 60–75 minutes.

The Goal

By the end, you should be able to explain this table:

Route observed on r2 Origin AS Local VRP says State
203.0.113.0/24 via 10.0.12.1 65001 203.0.113.0/24, max length 24, AS 65001 valid
203.0.113.0/24 via 10.0.23.2 65003 same prefix, but AS 65001 only invalid
198.51.100.0/24 via 10.0.24.2 65004 no covering VRP not found

The theme is simple: BGP can show who originated a route, but RPKI origin validation helps answer whether that origin is authorized. This lab is the entry point for cross-checking the origin AS you see in BGP against the authorization data RPKI provides.

What You Will Learn

  • A ROA authorizes an AS to originate an IP prefix up to a maximum prefix length.
  • A router usually receives validated prefix data as VRPs from an RTR cache.
  • Origin validation compares a BGP route's prefix and origin AS against VRPs.
  • valid, invalid, and not found are different states.
  • RPKI origin validation checks the origin AS, not the entire AS_PATH.
  • A router does not necessarily reject invalid routes unless policy tells it to do so.

This lab does not cover:

  • real RPKI repository synchronization
  • public RTR cache operation
  • route filtering policy
  • ASPA or full AS_PATH validation
  • production RPKI operations

Where to Read in the RFCs

The must-reads for this lab:

RFC Section What to focus on
RFC 6482 3 A ROA binds an AS to an IP prefix
RFC 6811 2 The terms route, origin AS, VRP, and covering prefix
RFC 6811 2.1 How valid / invalid / not found are determined
RFC 8210 1–2 The validated cache and router exchange data over the RTR protocol
RFC 5737 3 203.0.113.0/24 and 198.51.100.0/24 are documentation prefixes

The Big Picture

We build four FRRouting routers and one local StayRTR cache.

AS65001 / r1 ----\
                  \
                   AS65002 / r2 ---- StayRTR local RTR cache
                  /
AS65003 / r3 ----/

AS65004 / r4 ----/

r1 advertises:
  203.0.113.0/24  -> expected valid

r3 advertises:
  203.0.113.0/24  -> expected invalid

r4 advertises:
  198.51.100.0/24 -> expected not found

local VRP data:
  203.0.113.0/24, maxLength 24, AS65001
Enter fullscreen mode Exit fullscreen mode

203.0.113.0/24 and 198.51.100.0/24 are RFC 5737 documentation prefixes. They are never advertised externally and are used only inside the lab.

Note: Everything here uses documentation address space (RFC 5737), so nothing in this lab touches the real internet.

flowchart LR
  r1["r1<br/>AS65001<br/>originates 203.0.113.0/24"]
  r2["r2<br/>AS65002<br/>BGP observer<br/>RPKI client"]
  r3["r3<br/>AS65003<br/>originates 203.0.113.0/24"]
  r4["r4<br/>AS65004<br/>originates 198.51.100.0/24"]
  stayrtr["StayRTR<br/>local RTR cache<br/>VRP: 203.0.113.0/24 maxLength 24 AS65001"]

  r1 -- "eBGP<br/>10.0.12.0/30" --> r2
  r3 -- "eBGP<br/>10.0.23.0/30" --> r2
  r4 -- "eBGP<br/>10.0.24.0/30" --> r2
  r2 -- "RTR<br/>10.0.25.0/30<br/>tcp/8282" --> stayrtr
Enter fullscreen mode Exit fullscreen mode

And here's the decision logic origin validation applies to every route:

flowchart TB
  route["BGP route<br/>prefix + origin AS"]
  vrp["VRP<br/>prefix + max length + AS"]
  compare["Origin validation<br/>compare route against VRPs"]
  valid["valid<br/>prefix covered and origin AS matches"]
  invalid["invalid<br/>prefix covered but origin AS mismatches"]
  notfound["not found<br/>no covering VRP"]

  route --> compare
  vrp --> compare
  compare --> valid
  compare --> invalid
  compare --> notfound
Enter fullscreen mode Exit fullscreen mode

What You Need

Recommended environment:

  • Linux / WSL2 / a Linux VM
  • Docker
  • containerlab

Images used:

  • frrouting/frr:latest — the four BGP routers
  • rpki/stayrtr:latest — the local RTR cache

On macOS, run this inside a Linux VM, a WSL-equivalent environment, or a Linux VM on OrbStack/Colima.

Running the Lab

All the steps below run inside the Linux environment where containerlab is installed.

If you have the repo checked out, the quick path deploys, verifies FRRouting output, checks the RPKI validation states, and tears everything down for you:

./scripts/labctl.sh run rpki-04
Enter fullscreen mode Exit fullscreen mode

Or step through it manually:

1. Move into the working directory

cd protocol-lab/examples/rpki-04
Enter fullscreen mode Exit fullscreen mode

2. Read the local VRP data

In this lab, StayRTR gets its VRPs from a local JSON file rather than a real RPKI repository.

cat stayrtr/roas.json
Enter fullscreen mode Exit fullscreen mode

Expected content:

{
  "roas": [
    {
      "prefix": "203.0.113.0/24",
      "maxLength": 24,
      "asn": 65001
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

How to read it:

  • 203.0.113.0/24 may be originated by AS65001.
  • maxLength is 24, so more-specific prefixes are not authorized.
  • AS65003 is not an authorized origin for this prefix.
  • There is no VRP for 198.51.100.0/24 at all.

3. Deploy

sudo containerlab deploy -t rpki-04.clab.yml
Enter fullscreen mode Exit fullscreen mode

After it comes up, confirm the containers exist:

docker ps --format "table {{.Names}}\t{{.Status}}"
Enter fullscreen mode Exit fullscreen mode

Checkpoints:

  • clab-rpki-04-r1 is running.
  • clab-rpki-04-r2 is running.
  • clab-rpki-04-r3 is running.
  • clab-rpki-04-r4 is running.
  • clab-rpki-04-stayrtr is running.

4. Confirm r2 is connected to the RTR cache

docker exec -it clab-rpki-04-r2 vtysh -c "show rpki cache-connection"
Enter fullscreen mode Exit fullscreen mode

Checkpoint:

Connected to group 1
rpki tcp cache 10.0.25.2 8282 pref 1 (connected)
Enter fullscreen mode Exit fullscreen mode

Next, look at the VRPs r2 has received:

docker exec -it clab-rpki-04-r2 vtysh -c "show rpki prefix-table"
Enter fullscreen mode Exit fullscreen mode

Checkpoint:

RPKI/RTR prefix table
Prefix                                   Prefix Length  Origin-AS
203.0.113.0                                 24 -  24        65001
Enter fullscreen mode Exit fullscreen mode

5. See the validation states in the BGP table

docker exec -it clab-rpki-04-r2 vtysh -c "show bgp ipv4 unicast"
Enter fullscreen mode Exit fullscreen mode

Checkpoint:

RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
N*> 198.51.100.0/24  10.0.24.2                0             0 65004 i
I*  203.0.113.0/24   10.0.23.2                0             0 65003 i
V*>                  10.0.12.1                0             0 65001 i
Enter fullscreen mode Exit fullscreen mode

When FRRouting shows multiple paths for the same prefix, it may leave the Network column blank on the second and later lines. In the example above, the V*> line is also a path for 203.0.113.0/24.

How to read it:

  • V is valid: 203.0.113.0/24 originated by AS65001 matches the VRP.
  • I is invalid: 203.0.113.0/24 originated by AS65003, but the VRP only authorizes AS65001.
  • N is not found: there is no VRP covering 198.51.100.0/24.

You can also filter the view by state:

docker exec -it clab-rpki-04-r2 vtysh -c "show bgp ipv4 unicast rpki valid"
docker exec -it clab-rpki-04-r2 vtysh -c "show bgp ipv4 unicast rpki invalid"
Enter fullscreen mode Exit fullscreen mode

Expected Output

Rather than an exact character-for-character match, focus on capturing these fields.

show rpki cache-connection

rpki tcp cache 10.0.25.2 8282 pref 1 (connected)
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • r2 is connected to the local StayRTR cache.
  • The RTR cache is at 10.0.25.2:8282.

show rpki prefix-table

203.0.113.0                                 24 -  24        65001
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • The VRP covers 203.0.113.0/24.
  • The max length is 24.
  • The authorized origin AS is 65001.

show bgp ipv4 unicast

What to look for:

  • 203.0.113.0/24 from AS65001 is V.
  • 203.0.113.0/24 from AS65003 is I.
  • 198.51.100.0/24 from AS65004 is N.

Why It Works

RPKI origin validation compares each BGP route's prefix and origin AS against the VRPs obtained from a validated cache.

This lab has exactly one local VRP:

203.0.113.0/24, maxLength 24, AS65001
Enter fullscreen mode Exit fullscreen mode
  • r1's route matches the VRP on both prefix and origin AS, so it's valid.
  • r3's route has a prefix that is covered by the VRP, but its origin AS is 65003, so it's invalid.
  • r4's route has no covering VRP for 198.51.100.0/24, so it's not found.

One thing to keep firmly in mind: a route being labeled invalid and a route being rejected are two different things. In FRRouting, unless you write policy, an invalid route can still become the best path. This lab stops at observing the validation states; filter policy is a separate topic.

Common Pitfalls

  • A "blank" Network column in show bgp ipv4 unicast. That's just FRRouting omitting the prefix when showing multiple paths for the same prefix — the line still belongs to the prefix above it.
  • Treating not found as invalid. They're different: not found means no covering VRP exists at all.
  • Reading invalid as "the whole AS_PATH is forged." It only means the origin AS doesn't match the VRP.
  • Expecting origin validation to filter routes by itself. It's not a route filtering policy — rejecting invalid routes requires separate policy.
  • Confusing this lab's JSON with real RPKI. The JSON here is local experiment data. In the real RPKI, ROAs are signed objects validated from repositories.

Cleanup

If you deployed manually, destroy the topology:

sudo containerlab destroy -t rpki-04.clab.yml --cleanup
Enter fullscreen mode Exit fullscreen mode

If you used labctl.sh run rpki-04, the script runs destroy for you at the end.

Check Your Understanding

  1. Why is 203.0.113.0/24 from AS65001 valid?
  2. Why is 203.0.113.0/24 from AS65003 invalid?
  3. Why is 198.51.100.0/24 from AS65004 not found?
  4. Are invalid routes always rejected automatically?
  5. Does RPKI origin validation verify the entire AS_PATH?

References


That's RPKI origin validation in one sitting: one VRP, three routes, three states — and the crucial realization that invalid is a label, not a verdict, until policy says otherwise.

Explore the full Protocol Lab series here: github.com/pathvector-studio/protocol-lab. If these labs are useful to you, please ⭐ star the repo on GitHub — it genuinely helps others find the project.

Next up, we'll look at turning validation states into action — writing route policy that actually rejects invalid routes.

Top comments (0)