Securing BGP with RPKI: Configure Route Origin Validation and Register ROAs to Stop BGP Hijacking

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

BGP hijacking is one of those attacks that sounds theoretical until it happens to someone you know. In 2018, a misconfigured router at Nigerian ISP MainOne accidentally redirected Google traffic through Lagos for roughly an hour. In 2020, Rostelecom briefly announced routes belonging to Cloudflare, Amazon, and a dozen other major networks. Not sophisticated exploits — just BGP route leaks that the internet accepted without question.

Running your own ASN? Managing infrastructure that touches the public internet? RPKI belongs on your list. RPKI (Resource Public Key Infrastructure) is the closest thing the industry has to a real fix for this problem. The name is scarier than the setup process.

Quick Start: RPKI in 5 Minutes

Before going deep, here’s the short version so you can get oriented fast.

RPKI does two things:

  • ROA (Route Origin Authorization) — a cryptographically signed record that says “AS12345 is authorized to announce prefix 203.0.113.0/24”
  • ROV (Route Origin Validation) — your router checks incoming BGP announcements against those ROA records and drops the invalid ones

The flow looks like this:

  1. You register a ROA with your RIR (ARIN, RIPE, APNIC, etc.)
  2. Routers around the world pull ROA data via RPKI validators
  3. When someone announces your prefix from an unauthorized ASN, their route gets marked INVALID and dropped

To check if a prefix already has ROA coverage, query the RIPE RPKI validator API:

# Check ROA validity for a prefix
curl -s "https://rpki-validator.ripe.net/api/v1/validity/AS15169/8.8.8.0%2F24" | python3 -m json.tool

A valid response looks like:

{
  "validated_route": {
    "route": {
      "origin_asn": "AS15169",
      "prefix": "8.8.8.0/24"
    },
    "validity": {
      "state": "valid",
      "description": "At least one VRP Matches the Route Prefix"
    }
  }
}

Deep Dive: How BGP Hijacking Actually Works

The Problem With BGP’s Trust Model

BGP was designed in 1989 for a small, trusted internet. Every AS takes other ASes at their word — if AS9999 announces that it owns 192.0.2.0/24, BGP has no built-in way to verify that claim.

Attackers (or misconfigured routers) exploit this by announcing prefixes they don’t own. Because BGP prefers more-specific routes, announcing 192.0.2.0/25 pulls traffic away from the legitimate 192.0.2.0/24 announcement. Traffic gets intercepted, dropped, or silently redirected — sometimes undetected for hours.

What ROA Records Look Like

A ROA is a cryptographically signed statement: “This ASN is authorized to announce this prefix, up to this maximum prefix length.” That max-length field does real work. Without it, nothing stops someone from deaggregating your /24 into 256 more-specific /32s and siphoning traffic one subnet at a time.

ROA Example:
  ASN:        AS64496
  Prefix:     203.0.113.0/24
  Max Length: 24
  Validity:   2024-01-01 to 2026-01-01

Effect:
  203.0.113.0/24 from AS64496  → VALID
  203.0.113.0/25 from AS64496  → INVALID (more specific than max-length)
  203.0.113.0/24 from AS99999  → INVALID (wrong origin ASN)

Setting Up an RPKI Validator

ROV on your router requires a local RPKI validator. Your router queries it via the RTR (RPKI-to-Router) protocol to get a current list of authorized prefixes. The two most widely deployed options are Routinator (from NLnet Labs) and OctoRPKI (from Cloudflare).

Installing Routinator on Ubuntu/Debian:

# Add NLnet Labs repository
curl -fsSL https://packages.nlnetlabs.nl/aptkey.asc | sudo gpg --dearmor -o /usr/share/keyrings/nlnetlabs-archive-keyring.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/nlnetlabs-archive-keyring.gpg] https://packages.nlnetlabs.nl/linux/debian $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/nlnetlabs.list

sudo apt update && sudo apt install routinator -y

# Initialize and accept ARIN RPA
routinator init --accept-arin-rpa

# Start the RTR server (port 3323) and HTTP API (port 8323)
routinator server --rtr 0.0.0.0:3323 --http 0.0.0.0:8323 &

Wait a few minutes for the initial fetch. Then confirm it’s working:

# Check validation status via HTTP API
curl http://localhost:8323/api/v1/status

# Check a specific prefix
curl "http://localhost:8323/api/v1/validity/AS15169/8.8.8.0/24"

Advanced Usage: Enabling ROV on Your Router

FRRouting (FRR) Configuration

FRR has native RTR client support, so if you’re already running it for BGP, RPKI validation layers on without major surgery.

# Connect to FRR vtysh
sudo vtysh
! Configure RPKI cache server (your Routinator instance)
rpki
  rpki cache 127.0.0.1 3323 preference 1
  exit

! Apply route-map to your neighbor (route-map below handles enforcement)
router bgp 64496
  neighbor 198.51.100.1 route-map RPKI-FILTER in
  exit

! valid → accept, notfound → accept, invalid → drop
route-map RPKI-FILTER permit 10
  match rpki valid
  exit
route-map RPKI-FILTER permit 20
  match rpki notfound
  exit
route-map RPKI-FILTER deny 30
  match rpki invalid
  exit

Three route states to understand:

  • valid — ROA exists and origin ASN + prefix match → accept
  • notfound — no ROA exists for this prefix → accept (can’t verify either way)
  • invalid — ROA exists but origin ASN doesn’t match → drop

Verify RPKI is active in FRR:

sudo vtysh -c "show rpki prefix-table"
sudo vtysh -c "show rpki cache-connection"
sudo vtysh -c "show bgp ipv4 unicast 8.8.8.0/24"

Registering Your Own ROA

Got your own IP space? Create ROAs for it. The process differs by RIR:

RIPE NCC (Europe/Middle East/Central Asia):

  1. Log into RIPE NCC portal at my.ripe.net
  2. Go to RPKICreate ROA
  3. Select your prefix, set origin ASN and max-length
  4. Sign and publish

ARIN (North America):

  1. Log into ARIN Online at account.arin.net
  2. Navigate to Manage → RPKI
  3. Create ROA for your resources

For max-length, match the prefix length you actually announce. Only announcing /24? Set max-length to 24. Don’t set it to 32 — that allows any more-specific route from your ASN, which is exactly what a deaggregation attack exploits.

Automating ROA Monitoring With a Python Script

A small monitoring script that checks your announced prefixes against the local validator and flags anything unexpected:

#!/usr/bin/env python3
import requests
import json

MY_PREFIXES = [
    {"asn": "AS64496", "prefix": "203.0.113.0/24"},
    {"asn": "AS64496", "prefix": "198.51.100.0/24"},
]

VALIDATOR_URL = "http://localhost:8323/api/v1/validity"

def check_prefix(asn, prefix):
    encoded = prefix.replace("/", "%2F")
    url = f"{VALIDATOR_URL}/{asn}/{encoded}"
    try:
        r = requests.get(url, timeout=5)
        data = r.json()
        state = data["validated_route"]["validity"]["state"]
        return state
    except Exception as e:
        return f"error: {e}"

for entry in MY_PREFIXES:
    state = check_prefix(entry["asn"], entry["prefix"])
    status = "OK" if state == "valid" else f"ALERT: {state.upper()}"
    print(f"{entry['prefix']} ({entry['asn']}): {status}")
python3 check_rpki.py
# Output:
# 203.0.113.0/24 (AS64496): OK
# 198.51.100.0/24 (AS64496): OK

Practical Tips

Start in Monitoring Mode, Not Enforcement Mode

Don’t flip to strict INVALID-dropping on day one. Log what would have been dropped and review it for a week first. Some legitimate peers have misconfigured ROAs — cutting their routes immediately breaks connectivity before you’ve had a chance to warn them.

# On FRR: spot invalid routes without dropping them
sudo vtysh -c "show bgp ipv4 unicast" | grep "invalid"

Set ROA Expiry Reminders

ROAs expire. Most RIRs default to 1-2 year validity windows. A calendar reminder 30 days before expiry is worth setting — an expired ROA turns your prefixes into notfound, and routers running strict filtering may start rejecting your announcements.

Check What Your Upstreams Are Doing

Worth knowing whether your upstream ISP or IXP actually filters INVALID routes. Check isbgpsafeyet.com or APNIC’s RPKI stats dashboard to find out. If they don’t filter, your ROV work protects the rest of the internet from routes that falsely claim to come from you. It does not protect you from hijacked routes arriving via those same upstreams.

Redundant Validators Matter

Routinator going down won’t immediately break your routing — FRR’s default on RTR connection loss is to mark all routes as notfound, not invalid. Connectivity stays up. That said, run at least two validators for proper resilience:

rpki
  rpki cache 127.0.0.1 3323 preference 1
  rpki cache 192.168.1.10 3323 preference 2
  exit

Track Global RPKI Adoption

As of 2025, over 40% of global routes have ROA coverage — up from roughly 10% in 2020. That trajectory matters. The more networks deploy ROV, the harder BGP hijacking becomes across the board, not just for you. Cloudflare Radar, the RIPE NCC stats dashboard, and Routinator’s HTTP API all give you visibility into valid/notfound/invalid counts across the global routing table.

The upfront work is real: register ROAs, spin up a validator, tune the filter policy. Budget a few hours. After that, you’re mostly renewing ROAs once a year or two. What you get in return is meaningful protection against an attack class that BGP has had open for 35+ years. That trade is worth making.

Share: