Le BGP est un protocole de routage dynamique qui se base historiquement sur la confiance. En théorie, n’importe qui peut plus ou moins annoncer n’importe quoi, et ça sera pris en compte. En pratique, les peerings BGP sont protégés par des prefix-lists afin de ne propager que ce qui est réellement voulu. Mais cela implique que chaque opérateur soit extrêment consciencieux, et cela étant bien sûr impossibles, nous pouvons recenser de nombreux incidents. De nombreuses techniques de préventions ont donc été mises en place au fur et à mesure, et l’ASPA est complémentaires à celles déjà en place. C’est discuté en paragraphe 9 « Comparison to Other Technologies ».
Nous avons vu arriver en février 2012 l’une de ces technique : les Route Origin Authorizations (ROAs) par la RFC 6482. En se basant la Resource Public Key Infrastructure (RPKI) nous pouvons cryptographiquement signer la relation entre un préfixe et le (ou les) AS source ; ainsi, un préfixe annoncé par erreur par un AS se verra très fortement filtré. Cela ne protège par contre pas des attaques volontaires car il suffit de prepend le bon AS lors de l’annonce afin de faire correspondre la source avec le ROA et tout se passera comme si de rien n’était. Petit aparté sur les chiffres : à la fin janvier 2025 je vois 519213 ROAs sur 1001519 annonces (en IPv4), soit un peu plus de la moitié. Je n’ai pas de métriques sur les ASes qui vérifient ou non les ROAs, mais il est clair qu’un transitaire qui ne le fait pas en 2025 est à fuir comme la peste.
Depuis 2019 une discussion est actuellement ouverte à l’IETF afin de
pouvoir étendre la RPKI à la vérification de l’AS path au lieu
d’uniquement l’AS source, c’est l’ASPA
(Autonomous System Provider Authorization). L’idée est qu’un AS va publier
quels sont ses fournisseurs, et si un préfixe (signé en ROA évidemment)
est vu avec un chemin qui n’a rien à voir avec la choucroute, l’annonce
sera rejetée. C’est un peu à voir comme la version 2 des IRRs que l’on
trouve dans les whois d’AS.
Tout ceci étant encore en draft, il n’est
pas encore possible de publier des entrées ASPA depuis une CA hébergée
directement par un RIR, il faut l’héberger soi-même (par exemple avec krill).
Cela limite donc grandement l’adoption, ce qui fait que nous avons une
table comprenant seulement 81 entrées. Du
côté de la vérification, le support a été ajouté avec bird 2.16, et
pour la diffusion des entrées, le support via RTR a été ajouté avec routinator
0.14.1. Ce n’est pas un hasard si je suivais l’évolution de ces deux
logiciels, car ils font partie de la stack du second serveur de routes de
Breizh-IX.
Breizh-IX est un point d’échange rennais dont je m’occupe. Comme l’immense majorité des points d’échange nous proposons des serveurs de route, et comme j’aime les choses bien faites je préfère éviter d’annoncer des routes fallacieuses aux membres de l’IX. Pour se faire nous avions déjà mis en place les choses de base comme la vérification des ROAs et la génération de listes de préfixes pour chaque membre, mais aussi des choses moins connues comme la vérification de l’attribut Only to Customer (OTC). Aujourd’hui un pas de plus est fait avec l’ajout d’ASPA.
La mise en place de la partie validation est très simple, il suffit de
compiler routinator en version 0.14.1, d’installer les binaires, d’ajouter
enable-aspa = true
dans le fichier de configuration et de
redémarrer le daemon.
Maintenant que notre serveur RTR (ici routinator) supporte ASPA, il ne
reste plus qu’à dire à bird de le prendre en compte.
Après avoir également compilé et installé la version 2.16, il faut créer
une table en plus qui servira à stocker les entrées ASPA (en plus de
celles pour les ROAs IPv4 et IPv6), et de l’ajouter au protocole RPKI qui
correspond au routinator :
roa4 table r4;
roa6 table r6;
aspa table at;
protocol rpki rpki_conan {
remote "conan.grifon.fr";
roa4 { table r4; };
roa6 { table r6; };
aspa { table at; };
}
Ensuite au reload, le protocole devrait être négocié en version 2 et la nouvelle table va se remplir :
[root@rs2 ~]# birdc
BIRD 2.16 ready.
bird> show protocols all rpki_conan
Name Proto Table State Since Info
rpki_conan RPKI --- up 20:59:54.811 Established
Cache server: 89.234.186.8
Status: Established
Transport: Unprotected over TCP
Protocol version: 2
Session ID: 12256
Serial number: 19
Last update: before 504.687 s
Refresh timer : 648.312/1153
Retry timer : ---
Expire timer : 6695.312/7200
Channel roa4
State: UP
Table: r4
Preference: 100
Input filter: ACCEPT
Output filter: REJECT
Routes: 519235 imported, 0 exported, 83 preferred
Route change stats: received rejected filtered ignored accepted
Import updates: 519247 0 0 0 519247
Import withdraws: 12 0 --- 0 12
Export updates: 0 0 0 --- 0
Export withdraws: 0 --- --- --- 0
Channel roa6
State: UP
Table: r6
Preference: 100
Input filter: ACCEPT
Output filter: REJECT
Routes: 126571 imported, 0 exported, 31 preferred
Route change stats: received rejected filtered ignored accepted
Import updates: 126589 0 0 0 126589
Import withdraws: 18 0 --- 0 18
Export updates: 0 0 0 --- 0
Export withdraws: 0 --- --- --- 0
Channel aspa
State: UP
Table: at
Preference: 100
Input filter: ACCEPT
Output filter: REJECT
Routes: 81 imported, 0 exported, 81 preferred
Route change stats: received rejected filtered ignored accepted
Import updates: 81 0 0 0 81
Import withdraws: 0 0 --- 0 0
Export updates: 0 0 0 --- 0
Export withdraws: 0 --- --- --- 0
Nous pouvons maintenant utiliser les données de cette table dans les
filtres d’entrée, grâce à la fonction intégrée aspa_check
:
# https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-20.html#name-algorithm-for-upstream-path
case aspa_check(at, bgp_path, true) {
ASPA_INVALID:
igp_metric = 12;
reject "Invalid ASPA:", net, bgp_path;
ASPA_VALID:
igp_metric = 10;
ASPA_UNKNOWN:
igp_metric = 11;
}
Le premier paramètre de la fonction est la table depuis laquelle nous
tirons les informations, le second est l’AS path à évaluer et la dernière
est l’algorithme de vérification à utiliser. Les deux premiers étant assez
évidents, je vais uniquement m’attarder sur le dernier.
Dans la version actuelle d’ASPA (draft v20), il existe deux algorithmes :
un pour évaluer des routes upstream, et un autre pour les routes
downstream. Il est explicitement écrit qu’un serveur de route doit
utiliser l’algorithme upstream :
The upstream verification algorithm described here is applied when a route is received from a Customer or Peer, or is received by an RS from an RS-client, or is received by an RS-client from an RS.
6.2. Algorithm for Upstream Paths
Les routes ayant un seul AS sur le chemin sont automatiquement considérées comme valides, et bird respecte bien cela :
bird> show route all where igp_metric = 10
Table master4:
45.94.17.0/24 unicast [bgp_20766_185_1_89_28 21:04:43.510] * (100) [AS20766i]
via 185.1.89.28 on vtnet1
Type: BGP univ
igp_metric: 10
BGP.origin: IGP
BGP.as_path: 20766
BGP.next_hop: 185.1.89.28
BGP.local_pref: 100
BGP.community: (20766,1)
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
45.67.83.0/24 unicast [bgp_207910_185_1_89_26 21:04:43.617] * (100) [AS207910i]
via 185.1.89.26 on vtnet1
Type: BGP univ
igp_metric: 10
BGP.origin: IGP
BGP.as_path: 207910
BGP.next_hop: 185.1.89.26
BGP.local_pref: 100
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
185.204.199.0/24 unicast [bgp_211733_185_1_89_39 21:04:45.785] * (100) [AS211733i]
via 185.1.89.39 on vtnet1
Type: BGP univ
igp_metric: 10
BGP.origin: IGP
BGP.as_path: 211733
BGP.next_hop: 185.1.89.39
BGP.local_pref: 100
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
193.222.128.0/24 unicast [bgp_34019_185_1_89_36 21:04:48.454] * (100) [AS34019i]
via 185.1.89.36 on vtnet1
Type: BGP univ
igp_metric: 10
BGP.origin: IGP
BGP.as_path: 34019
BGP.next_hop: 185.1.89.36
BGP.med: 75
BGP.local_pref: 100
BGP.community: (34019,34019)
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
37.157.128.0/21 unicast [bgp_57943_185_1_89_19 21:04:43.486] * (100) [AS57943?]
via 185.1.89.19 on vtnet1
Type: BGP univ
igp_metric: 10
BGP.origin: Incomplete
BGP.as_path: 57943
BGP.next_hop: 185.1.89.19
BGP.local_pref: 100
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
[…]
Nous n’avons pas de route invalide et les autres routes ont bien le statut inconnu :
bird> show route all filtered where igp_metric = 12
bird>
bird> show route all where igp_metric = 11
Table master4:
37.60.157.0/24 unicast [bgp_34019_185_1_89_36 21:04:44.346] * (100) [AS201080i]
via 185.1.89.36 on vtnet1
Type: BGP univ
igp_metric: 11
BGP.origin: IGP
BGP.as_path: 34019 201080
BGP.next_hop: 185.1.89.36
BGP.med: 75
BGP.local_pref: 100
BGP.community: (34019,20108) (34019,64512) (65512,20001)
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
195.190.3.0/24 unicast [bgp_34019_185_1_89_36 21:04:48.670] * (100) [AS47612i]
via 185.1.89.36 on vtnet1
Type: BGP univ
igp_metric: 11
BGP.origin: IGP
BGP.as_path: 34019 35600 47612
BGP.next_hop: 185.1.89.36
BGP.med: 75
BGP.local_pref: 100
BGP.community: (34019,35600) (34019,64512)
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
45.67.83.0/24 unicast [bgp_204092_185_1_89_13 21:04:43.555] (100) [AS207910i]
via 185.1.89.13 on vtnet1
Type: BGP univ
igp_metric: 11
BGP.origin: IGP
BGP.as_path: 204092 207910
BGP.next_hop: 185.1.89.13
BGP.local_pref: 100
BGP.community: (64496,200)
BGP.ext_community:
BGP.large_community: (204092, 100, 200) (206165, 100, 0)
unicast [bgp_34019_185_1_89_36 21:04:44.684] (100) [AS207910i]
via 185.1.89.36 on vtnet1
Type: BGP univ
igp_metric: 11
BGP.origin: IGP
BGP.as_path: 34019 207910
BGP.next_hop: 185.1.89.36
BGP.med: 75
BGP.local_pref: 100
BGP.community: (34019,20791) (34019,64512) (65512,20001)
BGP.ext_community:
BGP.large_community: (206165, 100, 0)
185.20.5.0/24 unicast [bgp_34019_185_1_89_36 21:04:47.976] * (100) [AS56648i]
via 185.1.89.36 on vtnet1
Type: BGP univ
igp_metric: 11
BGP.origin: IGP
BGP.as_path: 34019 35600 56648
BGP.next_hop: 185.1.89.36
[…]
Si nous étions en train de vérifier les annonces d’un fournisseur, nous
aurions dû appliquer l’algorithme downstream. Pourquoi avoir deux
algorithmes ? Car lors d’une relation client-fournisseur il n’est pas
possible de savoir si le fournisseur a appris la route sur un peering ou
via l’un des transits de l’AS source (qu’ils auraient en communs). Nous ne
pouvons donc pas strictement rechercher si l’un des ASes dans l’entrée
ASPA est dans l’AS path, et on se contente alors d’estimer le statut de la
route par rapport à longueur de l’AS path.
Par exemple, si on prend une route vers telenor (AS2119) du point de vue de gitoyen (AS20766)

Pour un ordinateur il n’est pas possible de deviner quels sont les chemins légitime ou pas. En tant qu’humain, comme j’ai géré le peering de gandi avant que ça ne tourne au FullBullshit™ et que je connais le peering manager de ielo, je sais que ces deux ASes ont un peering avec telenor, et donc que ces deux chemin sont fiables. Cette théorie se confirme avec un mtr vers l’une des IPs du range qui montre que ça passe par l’AMS-IX :
grifon@gitoyen01:~$ mtr -bzwe 148.122.1.1
Start: 2025-01-22T23:16:35+0000
HOST: gitoyen01.ring.nlnog.net Loss% Snt Last Avg Best Wrst StDev
1. AS20766 x-ray.probe.gitoyen.net (80.67.163.243) 0.0% 10 0.3 0.2 0.2 0.3 0.0
2. AS20766 vodka.gitoyen.net (80.67.168.7) 0.0% 10 0.9 0.6 0.4 1.3 0.3
3. AS20766 gandi-pa3.gitoyen.net (80.67.168.149) 0.0% 10 0.7 0.7 0.5 1.5 0.3
4. AS??? ti9000b400.ti.telenor.net (80.249.209.192) 0.0% 10 10.7 9.9 9.3 13.0 1.2
5. AS2119 ti3003c400-ae13-0.ti.telenor.net (146.172.105.37) 0.0% 10 29.1 29.7 29.1 31.3 0.9
[MPLS: Lbl 9197 TC 0 S u TTL 1]
6. AS??? ??? 100.0 10 0.0 0.0 0.0 0.0 0.0
7. AS2119 ti0001b400-ae0-0.ti.telenor.net (146.172.105.49) 0.0% 10 29.0 29.9 28.9 38.0 2.8
8. AS2119 148.122.9.50 0.0% 10 30.2 29.4 29.2 30.2 0.3
9. AS2119 tix01c01-fge1-17.tix.telenor.net (148.122.2.81) 0.0% 10 29.2 29.3 29.2 29.6 0.1
[MPLS: Lbl 29 TC 0 S u TTL 1]
[MPLS: Lbl 648 TC 0 S u TTL 1]
10. AS2119 tix01d01-fge7-1.tix.telenor.net (148.122.2.66) 0.0% 10 29.3 29.6 29.2 32.1 0.9
[MPLS: Lbl 648 TC 0 S u TTL 1]
11. AS2119 148.122.205.225 0.0% 10 30.0 29.9 29.3 32.7 1.0
12. AS2119 tix01d01-ec2-v100.tix.telenor.net (148.122.205.25) 0.0% 10 31.2 29.6 29.1 31.2 0.6
13. AS2119 part1.online.no (148.122.1.1) 0.0% 10 29.3 29.4 29.3 29.8 0.1
L’IRR de l’AS2119 nous dit que les deux upstreams connus sont telia et level3 et dtag est censé être un peering :
alarig@x280 ~ % whois AS2119 | grep 'accept ANY'
import: from AS1299 action pref=100; accept ANY
import: from AS3356 action pref=100; accept ANY
alarig@x280 ~ % whois AS2119 | grep 3320
import: from AS3320 action pref=100; accept AS3320:AS-DTAG
export: to AS3320 announce AS-TELENOR
Si telenor publiait une entrée ASPA, elle contiendrait a priori les ASes 1299 et 3356. La route que nous voyons ici via telia aurait alors le statut valide. Les deux routes par gandi ou ielo auraient le statut inconnu. Nous pouvons par contre légitimement nous demander si le chemin via Hopus et DTAG est entièrement légitime ou non ; mais les IRRs étant principalement là à titre indicatif, nous ne pouvons pas nous baser dessus à 100%. Bien que DTAG soit un tier 1 de piètre qualité, il serait tout de même étonnant qu’ils réannoncent les préfixes d’un opérateur national sans que ce ne soit l’un de leur clients, même si ce n’est jamais à exclure non plus. En effet, dans ce cas présent la politique tarifaire d’Hopus fait que DTAG gagne de l’argent sur le trafic qui rentre dans son réseau sur ce port. Afin d’augmenter le volume dudit trafic, ils peuvent très bien ré-annoncer leurs peers en plus de leurs clients, bien que ça soit contraire aux pratiques communes.