A cosa serve?

Un router da qualche parte nel mondo annuncia che la tua rete passa da lui. Non è un errore di battitura raro: è successo a Google, a Cloudflare, a interi pezzi di traffico bancario dirottati per ore verso reti che non c’entravano niente. Il BGP, il protocollo che tiene insieme Internet, si fida: se un operatore dice «quella rete è mia», gli altri gli credono.

La RPKI smonta questa fiducia cieca con le firme crittografiche. Chi possiede un blocco di indirizzi pubblica un record firmato — una ROA — che dice «questi IP li può annunciare solo questo AS». Un validator esterno verifica quelle firme e passa al tuo router, via RTR, l’elenco già validato; il router, prima di accettare una rotta BGP, controlla se l’origine di chi annuncia è in quell’elenco e scarta chi non torna. Non è teoria da grandi operatori: su RouterOS 7 bastano due menu, e in questo howto lo vediamo fermare un dirottamento in diretta, in laboratorio.

Cos’è una ROA (e perché il BGP ne ha bisogno)

Il BGP annuncia prefissi (blocchi di indirizzi) e, per ognuno, l’AS di origine: il numero dell’operatore che per primo lo mette in rete. Il problema è che l’origine non la verifica nessuno. Chiunque può annunciare 198.51.100.0/24 a nome proprio, e se lo fa “più specifico” degli altri, vince: il traffico gli piove addosso.

La ROA (Route Origin Authorization) è un record firmato dal titolare degli indirizzi che dice tre cose: quale prefisso, fino a quale lunghezza (il maxLength) e quale AS può annunciarlo. Le ROA stanno nei repository dei RIR (RIPE, ARIN…), firmate; un validator le raccoglie, le verifica e tiene una lista di coppie «prefisso → AS autorizzato» (le VRP, Validated ROA Payloads).

RouterOS non è un validator: non raccoglie le ROA da solo. Si collega a un validator esterno con un protocollo leggero, RTR (RPKI-to-Router, RFC 8210), scarica le VRP e le usa per giudicare le rotte. Validator liberi: Routinator, rpki-client, StayRTR.

I quattro verdetti

Confrontando una rotta (prefisso + AS di origine) con il database, RouterOS assegna uno di quattro stati:

  • valid — c’è una ROA e l’origine è quella giusta.
  • invalid — c’è una ROA, ma l’origine è sbagliata (o il prefisso è più specifico del maxLength). È la firma del dirottamento.
  • unknown — nessuna ROA copre quel prefisso. Non è un giudizio: è «non so». Ancora una bella fetta di Internet, anche se sempre meno.
  • unverified — nessuna sessione RTR del gruppo ha ancora sincronizzato il database. Serve a gestire il caso «validator irraggiungibile» senza scartare tutto per sbaglio.

Il laboratorio: un dirottamento in tre prefissi

Montiamo la scena più piccola possibile per vedere tutti e tre i casi utili. Tre pezzi:

  • un validator (StayRTR) su 10.99.99.1, con un database VRP finto ma coerente;
  • gw1 (AS 65001), il nostro router di bordo, su 10.99.99.2: fa RPKI e filtra;
  • gw2 (AS 65002), un “vicino” su 10.99.99.3 che annuncia tre prefissi, uno dei quali rubato.

Il database del validator contiene due ROA (gli indirizzi sono quelli di documentazione, RFC 5737):

203.0.113.0/24   maxLength 24   AS65002    # gw2's legitimate announcement
198.51.100.0/24  maxLength 24   AS64500    # belongs to AS64500, NOT gw2

Di 192.0.2.0/24 non c’è nessuna ROA: resterà unknown. Il trucco è nella seconda riga: gw2 annuncerà 198.51.100.0/24 a nome proprio (AS65002), ma la ROA dice che quella rete è di AS64500. Quello è il dirottamento, e la RPKI deve beccarlo.

Il validator

StayRTR è un binario unico (lo prendi dai rilasci su GitHub). Gli diamo un file JSON con le due ROA e lo facciamo servire in RTR sulla porta 8282:

# vrps.json
{
  "metadata": { "buildtime": "2026-10-22T00:00:00Z" },
  "roas": [
    { "prefix": "203.0.113.0/24",  "maxLength": 24, "asn": "AS65002" },
    { "prefix": "198.51.100.0/24", "maxLength": 24, "asn": "AS64500" }
  ]
}
./stayrtr -cache ./vrps.json -checktime=false -bind 10.99.99.1:8282

In laboratorio il validator sta su una Debian; in produzione, al posto del JSON fatto a mano, StayRTR (o Routinator) pesca le ROA vere dai RIR. Per il router non cambia niente: stessa porta RTR, stessa configurazione.

gw2, il vicino che annuncia (anche il prefisso rubato)

Su RouterOS 7 un prefisso si origina mettendolo in una address-list e dicendo alla sessione BGP di annunciare quella lista; network-blackhole=yes crea per ognuno una rotta blackhole attiva, così il prefisso parte davvero.

/ip address add address=10.99.99.3/24 interface=ether1
/ip firewall address-list add list=bgp-nets address=203.0.113.0/24
/ip firewall address-list add list=bgp-nets address=198.51.100.0/24
/ip firewall address-list add list=bgp-nets address=192.0.2.0/24
/routing/bgp/instance add name=as65002 as=65002
/routing/bgp/connection add name=to-gw1 remote.address=10.99.99.2 instance=as65002 \
    remote.as=65001 local.role=ebgp-peer output.network=bgp-nets output.network-blackhole=yes

gw1, il bordo che valida

Prima la sessione RTR verso il validator, poi la regola BGP. Il gruppo RPKI lo chiamiamo lab:

/ip address add address=10.99.99.2/24 interface=ether1
/routing/rpki add group=lab address=10.99.99.1 port=8282 refresh-interval=60

Il filtro vero e proprio sta nelle regole di routing: una prima regola marca ogni rotta con il verdetto RPKI del gruppo lab, la seconda scarta le invalid e accetta il resto.

/routing/filter/rule add chain=bgp_in rule="rpki-verify lab"
/routing/filter/rule add chain=bgp_in rule="if (rpki invalid) { reject } else { accept }"

E la sessione verso gw2, con quel filtro applicato in ingresso:

/routing/bgp/instance add name=as65001 as=65001
/routing/bgp/connection add name=to-gw2 remote.address=10.99.99.3 instance=as65001 \
    remote.as=65002 local.role=ebgp-peer input.filter=bgp_in

La prova: controllare un verdetto a mano

Appena la sessione RTR è sincronizzata, su gw1 possiamo interrogare il database per un singolo prefisso con rpki-check. Attenzione a una differenza fra versioni: su RouterOS 7.24.5 il parametro si chiama prefix (il manuale più recente scrive prfx).

[admin@gw1] > /routing/rpki rpki-check group=lab prefix=203.0.113.0/24 origin-as=65002
 valid
[admin@gw1] > /routing/rpki rpki-check group=lab prefix=198.51.100.0/24 origin-as=65002
 invalid
[admin@gw1] > /routing/rpki rpki-check group=lab prefix=198.51.100.0/24 origin-as=64500
 valid
[admin@gw1] > /routing/rpki rpki-check group=lab prefix=192.0.2.0/24 origin-as=65002
 unknown

Tutto come previsto: lo stesso prefisso 198.51.100.0/24 è valid dall’AS giusto (64500) e invalid da quello sbagliato (65002). E 192.0.2.0/24, senza ROA, è unknown.

La prova che conta: il dirottamento scartato in BGP

Qui si vede il punto. Sul vicino gw2, tutti e tre i prefissi partono davvero verso gw1 — compreso quello rubato:

[admin@gw2] > /routing/bgp/advertisements print
DST              PEER      AFI  NEXTHOP
192.0.2.0/24     to-gw1-1  ip   10.99.99.3
198.51.100.0/24  to-gw1-1  ip   10.99.99.3
203.0.113.0/24   to-gw1-1  ip   10.99.99.3

Ma sul bordo gw1, nella tabella di routing ne entrano solo due:

[admin@gw1] > /ip route print where bgp
Flags: D - DYNAMIC; A - ACTIVE; b - BGP
    DST-ADDRESS     GATEWAY     DISTANCE
DAb 192.0.2.0/24    10.99.99.3        20
DAb 203.0.113.0/24  10.99.99.3        20

Il valid (203.0.113.0/24) è entrato. L’unknown (192.0.2.0/24) pure: RPKI non scarta ciò che non conosce, lo lascia passare. L’invalid — 198.51.100.0/24 annunciato da chi non ne ha diritto — non c’è: l’ha fermato il filtro, prima che toccasse la tabella. Il dirottamento non è mai entrato in casa.

Il maxLength: la ROA “lasca” che lascia uno spiraglio

C’è un dettaglio che fa la differenza fra una ROA stretta e una che lascia la porta socchiusa. La ROA dice anche fino a che lunghezza un prefisso è ammesso. La nostra è maxLength 24: autorizza /24 e basta. Se qualcuno annuncia un pezzo più specifico, è invalid anche se l’AS fosse quello giusto:

[admin@gw1] > /routing/rpki rpki-check group=lab prefix=203.0.113.128/25 origin-as=65002
 invalid

Perché conta? Perché il trucco classico del dirottamento è annunciare un prefisso più specifico di quello legittimo, che in BGP vince sempre. Se la tua ROA ha un maxLength largo (per esempio /24 su un blocco che usi solo come /24), stai autorizzando tu stesso tutti i più-specifici fino a lì: un attaccante può annunciare un /24 dentro il tuo blocco e restare valid. Regola d’oro: il maxLength uguale alla lunghezza che annunci davvero, non più largo.

L’onestà di fine articolo: l’origine sì, il percorso no

Quello che hai visto finora — la validazione d’origine, basata sulle ROA — controlla chi annuncia per primo un prefisso. Non controlla il percorso: non sa dire se l’AS-path è plausibile o se qualcuno nel mezzo sta mentendo pur lasciando l’origine corretta. Un attaccante che si infila come “transito” senza falsificare l’origine passa comunque.

Il pezzo che manca — validare anche il percorso — si chiama ASPA (Autonomous System Provider Authorization), ed è anch’esso un oggetto RPKI: ogni rete dichiara chi sono i suoi provider legittimi, così si può controllare che l’AS-path abbia senso. È ancora agli inizi (copre una fetta piccola del routing), ma è la direzione. Per oggi: validando l’origine accetti meno spazzatura, e i dirottamenti d’origine — i più comuni — li fermi davvero. Non è poco.

Per toccare ASPA con mano — vedere gli ASPA pubblicati, chi dichiara chi come provider e verificare un AS-path dal vivo — c’è ASPA Explorer, servizio libero e open source del DEI dell’Università di Padova: aspa.ping6.it.

Domande frequenti

RouterOS può fare da validator da solo?

No. RouterOS parla solo RTR verso un validator esterno (Routinator, rpki-client, StayRTR). Il validator raccoglie e verifica le ROA; RouterOS ne usa il risultato nei filtri.

Se il validator cade, il router scarta tutte le rotte?

No, se scrivi bene il filtro. Finché nessuna sessione del gruppo è sincronizzata lo stato è unverified, non invalid: puoi gestirlo apposta (if (rpki unverified) { accept }) per non isolarti quando il validator è irraggiungibile.

Posso bloccare un attacco solo in base alla RPKI?

La RPKI va benissimo per scartare le invalid. Ma le unknown sono ancora una fetta importante di Internet (oggi circa un terzo delle rotte: la copertura ROA ha ormai superato il 65%): scartarle ti taglierebbe fuori da troppa rete in buona fede. Si scartano le invalid, si accettano valid e unknown.

Perché la mia rotta risulta invalid anche se l’AS è giusto?

Quasi sempre è il maxLength: stai annunciando un prefisso più specifico di quanto la ROA permetta. Controlla con rpki-check e allinea il maxLength della ROA a ciò che annunci davvero.

Che differenza c’è fra validazione d’origine e ASPA?

Sono due controlli della stessa RPKI. La validazione d’origine (basata sulle ROA) controlla chi annuncia il prefisso; ASPA controlla il percorso (se gli AS attraversati sono quelli attesi). Sono complementari: la validazione d’origine è matura e diffusa, ASPA è il pezzo nuovo che aggiunge il controllo del percorso.

Provato in laboratorio su PNETLab con CHR RouterOS 7.24.5 (stable) e il validator StayRTR: sincronizzazione RTR, i quattro verdetti con rpki-check e il filtro BGP che scarta la rotta invalid lasciando passare valid e unknown. Gli output sono quelli reali del lab.