Networking / routing security deep dive
BGP hijacks and RPKI: why a more-specific route wins until a router rejects it
BGP chooses paths by policy, and the first decisive rule is usually longest prefix match. That lets an unauthorized /24 attract traffic away from a legitimate /16. RPKI adds a separate authorization check: a signed Route Origin Authorization can tell a router that the route’s origin AS, prefix length, or both are not allowed.
2026-09-02
The concrete problem
Suppose a company legitimately serves 162.55.0.0/16. The Internet does not have one central router. Thousands of autonomous systems (ASes) exchange reachability announcements using the Border Gateway Protocol, or BGP. Each receiving router builds its own routing information base and chooses a best route for each destination prefix.
Now an unrelated network announces 162.55.80.0/24. That /24 is contained inside the legitimate /16, but it is more specific: it describes 256 addresses instead of 65,536. For a destination such as 162.55.80.42, ordinary forwarding prefers the /24 wherever that route exists. The attacker does not need to make the whole Internet believe that it owns the /16; it only needs a more-specific announcement to reach enough networks.
This is a routing decision, not a cryptographic decision. Classic BGP authenticates the transport session between neighbors, but it does not by itself prove that the origin AS is authorized to announce the address space. RPKI supplies that missing statement of authority.
A recent incident makes the boundary visible
Virtualizor’s official incident report describes a real event between August 28 and August 30, 2026. AS62390, through AS6204, announced 162.55.80.0/24, while the legitimate infrastructure normally announced the surrounding 162.55.0.0/16. The report says the unauthorized route was accepted by the networks that received it, and that traffic to affected Softaculous systems was diverted intermittently for about 33 hours.
The attack was more serious than packet loss. The diverted endpoint served a technically valid Let’s Encrypt certificate because the certificate authority’s HTTP-based domain-control validation also followed the hijacked route. Clients therefore did not necessarily see a TLS warning. A malicious Virtualizor update reached a small number of installations; because those responses never reached the vendor’s servers, the vendor could not reconstruct a definitive list of clients from its own logs.
RPKI could have helped, but only if the address holder had published an appropriate ROA and the relevant networks were enforcing invalid-route policy. A ROA for 162.55.0.0/16 with maxLength=16 would make the unauthorized /24 invalid even if its BGP path ended with the legitimate origin AS. A permissive maxLength=24, or no covering ROA at all, changes that result. The exact protection depends on the signed policy, not on the word “RPKI” appearing in a network diagram.
What BGP actually compares
BGP carries a route to a prefix, a next hop, and path attributes. The full decision process is implementation- and policy-dependent, but the forwarding lookup has a simple invariant: among installed routes, the route with the longest matching prefix wins. BGP best-path selection then chooses one path for a given prefix, often considering local preference, locally originated routes, AS-path length, origin type, MED, eBGP/iBGP status, and router ID.
RPKI does not replace this selection process. It annotates a received BGP route with an origin-validation state. An operator’s policy decides what that state does to local preference or acceptance.
| Input route | Longest-prefix result | Origin-validation question |
|---|---|---|
162.55.0.0/16, origin AS24940 | Matches broad destinations in the /16. | Does a covering ROA authorize AS24940 for /16? |
162.55.80.0/24, origin AS62390 | Wins for addresses inside that /24 if accepted. | Does a covering ROA authorize AS62390, with a max length that permits /24? |
162.55.80.0/24, origin AS24940 | Still wins by specificity. | Is /24 within the ROA’s allowed maxLength? A correct origin alone is not enough. |
That last row is the common misconception. “The origin AS is right” and “the announced prefix length is authorized” are two different predicates. ROA maxLength is what prevents an authorized AS from turning a broad allocation into arbitrary more-specific routes.
ROAs become VRPs, then route states
A Route Origin Authorization (ROA) is a signed RPKI object that binds an address prefix to an origin AS and a maximum prefix length. A relying-party validator retrieves certificates and signed objects from the RPKI repository, checks the cryptographic chain, and reduces the result to a set of Validated ROA Payloads (VRPs):
VRP = (authorized_prefix, max_length, origin_as) (162.55.0.0/16, 16, AS24940) (203.0.113.0/24, 24, AS64500)
A router does not normally verify every X.509 object during every BGP UPDATE. A validator or cache does the expensive repository and signature work. The router consumes the compact VRP set over the RPKI-RTR protocol, then evaluates each route as it arrives or as the VRP set changes.
For a BGP route R = (prefix P, origin AS A), RFC 6811’s essential logic is:
- Find VRPs whose authorized prefix contains
P. - The route is Valid if at least one matching VRP has
origin_as = Aandprefix_length(P) ≤ max_length. - If covering VRPs exist but none authorize that origin and length, the route is Invalid.
- If no covering VRP exists, the route is NotFound.
“Invalid” and “NotFound” must not be conflated. Invalid means signed routing policy contradicts the route. NotFound means there is no signed policy covering it. A deployment that rejects Invalid but accepts NotFound still leaves every unprotected prefix exposed to ordinary BGP mistakes and hijacks.
The data and control flow in an implementation
The system has three clocks and three kinds of state: repository publication, validator refresh, and router BGP convergence. They are loosely synchronized. A new ROA may be correctly published while a cache still has the old VRP set, or a router may have the new VRP set while a route UPDATE is still converging.
The RPKI-RTR protocol is intentionally small. A router asks for a full or incremental cache response, receives IPv4/IPv6 prefix records carrying the VRP fields, and gets an End of Data marker with serial and timing information. The router can then detect a stale or reset cache. The protocol transfers validated authorization data; it is not a BGP UPDATE channel.
Open-source implementation landmarks
There is a useful separation of concerns in open-source stacks. A relying-party validator implements RPKI repository retrieval, certificate validation, manifest/CRL processing, and VRP generation. A routing daemon maintains the BGP Adj-RIB-In and Loc-RIB, receives VRPs over RTR, classifies routes, and applies operator policy.
| Component | Important implementation boundary | What to inspect when debugging |
|---|---|---|
| Routinator | Relying-party validator and RTR server. It turns distributed signed objects into a validated VRP set. | Repository refresh time, validation errors, serial number, VRP count, and whether the expected ROA is present. |
| rpki-client | Small relying-party implementation that validates RPKI objects and can emit validated data for downstream use. | Trust-anchor configuration, object expiry, rejected manifests, and output for the exact prefix. |
FRR bgpd/bgp_rpki.c | Maintains the router-side RPKI/RTR integration and updates route validation state as VRPs change. | RTR session state, serial/reset handling, cache freshness, and route-state transitions. |
| FRR BGP/RPKI policy | Exposes validation state to route maps and lets operators reject or prefer routes based on Valid, Invalid, or NotFound. | Whether the route map is applied on the inbound edge where the UPDATE enters, before it can become the best path. |
| RPKI-RTR | Defines cache-to-router PDUs such as Serial Notify, Cache Response, Prefix, End of Data, and Cache Reset. | Transport security, session resets, serial mismatch, and stale-data timers. |
FRR’s route object has to retain more than a next hop: it needs the BGP path attributes used by best-path selection and a validation state derived from the current VRP table. The important operational detail is that a VRP update can invalidate an already learned route without a new BGP UPDATE. Implementations therefore need a re-evaluation path from “VRP changed” to “route policy and best path changed.”
What RPKI does not solve
| Problem | Why origin validation is insufficient | Additional control |
|---|---|---|
| Forged or replayed application content | A Valid route can still lead to a compromised origin server. | Signed software updates, package transparency, end-to-end application authentication, and rollback. |
| Path manipulation after the origin | RPKI validates the origin AS and prefix length, not every AS in AS_PATH. | Peer filtering, AS-path monitoring, and, where appropriate, BGPsec or private authenticated overlays. |
| Route leaks by an authorized origin | A correctly authorized route can still be exported to the wrong peers. | Import/export policy, prefix limits, BGP communities, and leak detection. |
| No ROA exists | The route is NotFound, not Invalid. | Publish accurate ROAs and decide an explicit local policy for NotFound. |
| Bad ROA publication | An overly broad or incorrect ROA can make a legitimate route Invalid, creating an outage. | Staged publication, pre-checks, alerting, and a documented emergency rollback. |
RPKI is therefore an authorization layer, not a complete replacement for BGP security. It is strongest when combined with strict prefix filters and when update endpoints verify signed artifacts independently of the network path.
Failure modes and security implications
- Invalid routes are merely tagged. If the operator never applies an inbound policy that rejects or de-preferences Invalid, validation is observational telemetry, not protection.
- Stale cache data creates a policy gap. A router may keep accepting routes based on old VRPs after a ROA is revoked or changed. Monitor refresh and expiry timers; do not treat an RTR TCP session as proof that its contents are fresh.
- Overlapping ROAs are set semantics. A route is Valid if any covering VRP authorizes it. An accidental permissive ROA can therefore keep a supposedly invalid route Valid.
- Fail-open and fail-closed are real choices. If the validator is unavailable, keeping old VRPs preserves reachability but may preserve stale authorization. Discarding all VRPs can turn protected routes into NotFound. Choose and test the behavior instead of inheriting a vendor default.
- ROA mistakes can self-inflict an outage. A holder who announces a /24 but publishes only
maxLength=16for that origin has created an Invalid route. Change management for routing authority deserves the same review as firewall rules. - A valid certificate proves little about routing intent. In the incident above, domain-control validation followed the detour. Software update clients need signed package verification, pinned trust roots where appropriate, and a rollback path even when TLS succeeds.
How to debug a suspected hijack
- Start with the destination. Resolve the exact IP and compare its covering routes. A /24 may be hijacked while the surrounding /16 looks healthy.
- Compare vantage points. Query multiple independent route collectors and your own upstreams. BGP is a distributed system; one router’s table is not “the Internet.”
- Inspect the AS path and prefix length. Record the first-seen time, origin AS, neighboring ASes, and whether the route is more specific than the legitimate announcement.
- Ask the validator for the exact VRP decision. Capture the covering ROA prefix, origin, maxLength, validator serial, and route state. Do not infer “Invalid” from an unfamiliar AS path alone.
- Check enforcement. On the receiving router, confirm that the UPDATE was classified Invalid and that an inbound route policy rejected it before best-path installation. A dashboard showing Invalid is not evidence of rejection.
- Check application evidence separately. Review certificate issuance, update-client logs, package signatures, credential use, and outbound connections. Routing telemetry cannot identify every client that received an attacker’s response.
Useful production metrics include the age of the newest successful validator refresh, VRP serial age, RTR session resets, counts of Valid/Invalid/NotFound routes, rejected Invalid updates, and more-specific announcements inside your own allocations. Alert on a new more-specific route and on a Valid-to-Invalid transition, not only on total BGP session loss.
Compact reproducible lab
Goal and prerequisites
This lab models the two decisions that are easy to confuse: longest-prefix forwarding and RPKI origin validation. It uses only Python’s standard library, so it does not create real BGP sessions or cryptographically validate an RPKI repository. That makes the semantic boundary visible without requiring FRR, a validator, or a lab network.
mkdir -p /tmp/bgp-rpki-lab cd /tmp/bgp-rpki-lab python3 --version
Run this exact program:
python3 - <<'PY'
from dataclasses import dataclass
from ipaddress import ip_address, ip_network
@dataclass(frozen=True)
class VRP:
prefix: object
max_length: int
origin: int
@dataclass(frozen=True)
class Route:
prefix: object
origin: int
name: str
def validation_state(route, vrps):
covering = [v for v in vrps if route.prefix.subnet_of(v.prefix)]
if not covering:
return "NotFound"
valid = any(route.origin == v.origin
and route.prefix.prefixlen <= v.max_length
for v in covering)
return "Valid" if valid else "Invalid"
def best_route(destination, routes, vrps, reject_invalid=False):
candidates = [r for r in routes if destination in r.prefix]
states = {r.name: validation_state(r, vrps) for r in candidates}
if reject_invalid:
candidates = [r for r in candidates if states[r.name] != "Invalid"]
winner = max(candidates, key=lambda r: r.prefix.prefixlen) if candidates else None
return winner, states
destination = ip_address("162.55.80.42")
routes = [
Route(ip_network("162.55.0.0/16"), 24940, "legitimate /16"),
Route(ip_network("162.55.80.0/24"), 62390, "hijack /24"),
]
vrps = [VRP(ip_network("162.55.0.0/16"), 16, 24940)]
for reject in (False, True):
winner, states = best_route(destination, routes, vrps, reject_invalid=reject)
print("states:", states)
print("reject-invalid:", reject, "winner:", winner.name if winner else None)
PY
Expected result:
states: {'legitimate /16': 'Valid', 'hijack /24': 'Invalid'}
reject-invalid: False winner: hijack /24
states: {'legitimate /16': 'Valid', 'hijack /24': 'Invalid'}
reject-invalid: True winner: legitimate /16
The first pass proves that validation does not automatically alter route selection. The /24 is Invalid, but it still wins when policy merely observes the state. The second pass models an inbound “reject Invalid” policy; the /24 disappears before longest-prefix selection, so traffic falls back to the legitimate /16.
Variation 1 — allow the /24 for the real origin: change the VRP line to vrps = [VRP(ip_network("162.55.0.0/16"), 24, 24940)], and change the hijack route’s origin from 62390 to 24940. Both routes become Valid, and the /24 still wins. A permissive maxLength authorizes more-specific announcements.
Variation 2 — remove the ROA: change vrps = [...] to vrps = []. Both routes become NotFound. The reject-Invalid policy no longer removes the hijack, demonstrating why “we reject Invalid” is not equivalent to “we reject every unauthorized route.”
To extend this lab into a protocol test, run FRR with a local Routinator instance and configure an RTR session. Keep the Python model beside that test: it is a small oracle for the expected Valid/Invalid/NotFound state and for the route-policy ordering.
The durable mental model
Think of BGP and RPKI as two layers with different jobs. BGP answers, “Which reachable path should I use for this destination?” RPKI answers, “Is this origin AS authorized to announce this prefix at this length?” The router must join the answers through explicit policy.
For a suspected hijack, ask in order: What exact prefix was announced? Is it more specific? What origin AS does the route claim? Which covering ROAs exist, and what maxLength do they permit? Did the router reject Invalid before best-path selection? That sequence explains both the outage mechanics and why a valid TLS certificate or a healthy BGP session is not enough.
Primary sources
- Virtualizor: Security Incident – BGP Hijacking, official incident report and timeline, Aug 28–30, 2026
- The Register: 33-hour BGP hijack of Softaculous traffic prompts security scramble, technology news report, Sep 1, 2026
- RFC 4271: A Border Gateway Protocol 4 (BGP-4), BGP UPDATEs and path attributes
- RFC 6811: BGP Prefix Origin Validation, VRP matching and Valid/Invalid/NotFound states
- RFC 6482: A Profile for Route Origin Authorizations (ROAs), signed prefix/origin/maxLength policy
- RFC 6810: The RPKI to Router Protocol, cache-to-router protocol and PDUs
- RFC 7454: BGP Operations and Security, operational protections and filtering
- FRRouting BGP documentation: RPKI, open-source router configuration and policy behavior
- NLnet Labs Routinator and rpki-client, open-source relying-party implementations