TTR Networks

Intermediate · RPKI and Route Origin Validation · section 6 of 8

Lab: dropping Invalids in FRR

✓ Lab-tested 17 October 2026 on FRR 10.2.1, StayRTR 0.6.4

This lab was run exactly as shown: three containers on one Docker network. R1 (AS64500) validates and R2 (AS64501) announces four prefixes. StayRTR serves two VRPs from a JSON file. All the output on this page is copied from the running lab.

FRR's RPKI support is a loadable module: bgpd must start with -M rpki (set in /etc/frr/daemons), or the rpki commands won't exist.

To run it yourself: put the four files below in a folder, copy /etc/frr/daemons out of the FRR image, enable bgpd with the -M rpki option, and run docker compose up -d.

Diagram: The ROV lab — R1 validates routes from R2 against VRPs served by StayRTR.
  1. R1 loads the VRPs. 203.0.113.0/24-24 may be originated by AS64501; 198.51.100.0/24-24 by AS64502. Nothing covers 192.0.2.0/24.
  2. R2 announces four prefixes. All four are originated by AS64501: 203.0.113.0/24, 203.0.113.128/25, 198.51.100.0/24 and 192.0.2.0/24.
  3. R1 classifies and filters. Valid: 203.0.113.0/24. Invalid (too specific): 203.0.113.128/25. Invalid (wrong origin): 198.51.100.0/24. NotFound: 192.0.2.0/24. The route-map drops the two invalids.
R1 — validating router
frr defaults traditional
hostname R1
!
rpki
 rpki polling_period 300
 rpki cache tcp 10.0.12.10 3323 preference 1
exit
!
route-map R2-IN deny 10
 match rpki invalid
exit
!
route-map R2-IN permit 20
exit
!
router bgp 64500
 bgp router-id 10.255.0.1
 neighbor 10.0.12.2 remote-as 64501
 !
 address-family ipv4 unicast
  neighbor 10.0.12.2 soft-reconfiguration inbound
  neighbor 10.0.12.2 route-map R2-IN in
 exit-address-family
exit
!
  • Lines 4–7: The RPKI block. It exists only when bgpd runs with -M rpki.
  • Line 5: How often to poll the cache for updates (seconds). RTR also pushes notifications when the data changes.
  • Line 6: The RTR server. FRR 10 syntax includes the transport (tcp; ssh is also supported). Add a second line with preference 2 for a backup validator.
  • Lines 9–11: Drop Invalids. match rpki also accepts valid and notfound.
  • Lines 13–14: Accept everything else (Valid and NotFound). In production this route-map would also contain your prefix filters.
  • Line 21: Keeps a pre-policy copy of received routes so received-routes can show what was filtered. Costs memory; enable it while deploying.
  • Line 22: Attach the policy inbound.
R2 — the announcing peer
frr defaults traditional
hostname R2
!
ip route 192.0.2.0/24 Null0
ip route 198.51.100.0/24 Null0
ip route 203.0.113.0/24 Null0
ip route 203.0.113.128/25 Null0
!
router bgp 64501
 bgp router-id 10.255.0.2
 no bgp ebgp-requires-policy
 neighbor 10.0.12.1 remote-as 64500
 !
 address-family ipv4 unicast
  network 192.0.2.0/24
  network 198.51.100.0/24
  network 203.0.113.0/24
  network 203.0.113.128/25
 exit-address-family
exit
!
  • Lines 4–7: Discard routes so the network statements have something to originate.
  • Line 11: Lab shortcut so R2 sends without an outbound policy. Never do this in production.
  • Lines 15–18: One NotFound, two Invalids and one Valid, from R1's point of view.
Lab compose file
# docker compose up -d   — then: docker compose exec r1 vtysh
# Needs ./daemons from the FRR image with bgpd=yes and
#   bgpd_options="   -A 127.0.0.1 -M rpki"
name: rpki-lab
services:
  rtr:
    image: rpki/stayrtr:latest
    command: ["-cache", "/vrps.json", "-checktime=false", "-bind", ":3323", "-refresh", "30"]
    volumes: ["./lab-vrps.json:/vrps.json:ro"]
    networks: { lab: { ipv4_address: 10.0.12.10 } }
  r1:
    image: quay.io/frrouting/frr:10.2.1
    privileged: true
    volumes: ["./daemons:/etc/frr/daemons:ro", "./r1-rov-frr.conf:/etc/frr/frr.conf"]
    networks: { lab: { ipv4_address: 10.0.12.1 } }
  r2:
    image: quay.io/frrouting/frr:10.2.1
    privileged: true
    volumes: ["./daemons:/etc/frr/daemons:ro", "./r2-frr.conf:/etc/frr/frr.conf"]
    networks: { lab: { ipv4_address: 10.0.12.2 } }
networks:
  lab:
    ipam: { config: [{ subnet: 10.0.12.0/24, gateway: 10.0.12.254 }] }
  • Lines 2–3: FRR's RPKI module must be loaded in /etc/frr/daemons.
  • Line 8: StayRTR serves a local JSON file instead of a validator's output. -checktime=false stops it rejecting the file as stale.
  • Line 23: Docker reserves the first address as the gateway by default, so it's moved out of the way of R1's 10.0.12.1.
States before the drop policy (real output)
R1# show bgp ipv4 unicast
BGP table version is 4, local router ID is 10.255.0.1, vrf id 0
Default local pref 100, local AS 64500
Status codes:  s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

     Network          Next Hop            Metric LocPrf Weight Path
N*>  192.0.2.0/24     10.0.12.2                0             0 64501 i
I*>  198.51.100.0/24  10.0.12.2                0             0 64501 i
V*>  203.0.113.0/24   10.0.12.2                0             0 64501 i
I*>  203.0.113.128/25 10.0.12.2                0             0 64501 i

Displayed 4 routes and 4 total paths
R1# show bgp ipv4 unicast 198.51.100.0/24
BGP routing table entry for 198.51.100.0/24, version 3
Paths: (1 available, best #1, table default)
  Not advertised to any peer
  64501
    10.0.12.2 from 10.0.12.2 (10.255.0.2)
      Origin IGP, metric 0, valid, external, best (First path received), rpki validation-state: invalid
      Last update: Fri Oct 16 17:25:51 2026
  • Line 8: With RPKI enabled, FRR adds a state code in front of each route.
  • Line 11: N: no VRP covers 192.0.2.0/24.
  • Line 12: I: covered by 198.51.100.0/24-24, but only AS64502 is authorised and the origin here is 64501.
  • Line 13: V: origin, prefix and length all match the VRP.
  • Line 14: I: right origin, but /25 exceeds maxLength 24.
  • Line 23: The per-route view spells the state out.
After the drop policy (real output, headers trimmed)
R1# show rpki cache-connection
Connected to group 1
rpki tcp cache 10.0.12.10 3323 pref 1 (connected)
R1# show rpki prefix-table
RPKI/RTR prefix table
Prefix                                   Prefix Length  Origin-AS
198.51.100.0                                24 -  24   64502
203.0.113.0                                 24 -  24   64501
Number of IPv4 Prefixes: 2
Number of IPv6 Prefixes: 0
R1# show bgp ipv4 unicast neighbors 10.0.12.2 received-routes
     Network          Next Hop            Metric LocPrf Weight Path
 *> 192.0.2.0/24     10.0.12.2                0             0 64501 i
 *> 198.51.100.0/24  10.0.12.2                0             0 64501 i
 *> 203.0.113.0/24   10.0.12.2                0             0 64501 i
 *> 203.0.113.128/25 10.0.12.2                0             0 64501 i

Total number of prefixes 4 (2 filtered)
R1# show bgp ipv4 unicast
     Network          Next Hop            Metric LocPrf Weight Path
N*>  192.0.2.0/24     10.0.12.2                0             0 64501 i
V*>  203.0.113.0/24   10.0.12.2                0             0 64501 i

Displayed 2 routes and 2 total paths
  • Lines 1–3: The RTR session is up.
  • Lines 4–10: The two VRPs the router is enforcing.
  • Lines 11–18: Pre-policy view (thanks to soft-reconfiguration): all four routes arrived, and 2 were filtered.
  • Lines 19–24: Post-policy: only the Valid and NotFound routes remain.

Check your understanding

In the lab, the VRP for 203.0.113.0/24 is changed to maxLength 25. R1 receives the new VRP over RTR within seconds. What happens to 203.0.113.128/25, which R1 had already filtered as Invalid?

  • It is accepted automatically as soon as the VRP arrives
  • It stays filtered until inbound policy is re-run, e.g. clear bgp ipv4 unicast 10.0.12.2 soft in
  • The BGP session resets so it can be re-validated
  • 203.0.113.0/24 becomes Invalid