Routage L3 jusqu’à l’hyperviseur avec BGP

Vincent Bernat

La mise en haute disponibilité des réseaux L2 peut se faire de plusieurs façons :

Les réseaux L2 nécessitent très peu de configuration mais sont difficile à opérer de manière fiable dans des configurations hautement disponibles : un incident risque souvent d’impacter l’ensemble du réseau2. Il est donc préférable de limiter la portée de chaque réseau L2. Par exemple, il est courant d’avoir un réseau L2 dans chaque baie et de les connecter entre eux via du routage L3. Un incident impacte rarement l’ensemble d’un réseau IP.

Dans le schéma ci-dessous, les commutateurs de baie fournissent une passerelle par défaut pour les clients. Afin d’assurer une certaine redondance, ils utilisent une implémentation de MC-LAG. La portée de chaque réseau L2 est alors limitée à une seule baie. Chaque sous-réseau IP est lié à une baie et les informations de routage sont partagées entre les commutateurs de baie et les routeurs en utilisant un protocole tel qu’OSPF.

Conception classique d'un réseau L2
Conception classique avec routage au niveau des commutateurs d'accès : chaque paire de commutateurs agit en tant que passerelle. La portée d'un réseau L2 est limitée à une baie. Les hyperviseurs étendent le réseau L2 aux machines virtuelles. Chaque baie gère ses propres sous-réseaux IP.

Il y a deux défauts dans cette conception :

  1. La portée de chaque réseau L2 reste très importante. Une baie peut contenir plusieurs dizaines d’hyperviseurs et plusieurs milliers de machines virtuelles. Un incident réseau a donc un impact majeur.

  2. Les sous-réseaux IP sont liés à une baie. Une machine virtuelle ne peut pas migrer dans une autre baie et les IP non utilisées dans une baie ne peuvent pas être utilisées dans une autre.

Pour résoudre ces deux problèmes, il est possible de pousser le réseau L3 encore plus au sud, transformant chaque hyperviseur en routeur. Il convient toutefois de cacher ce changement aux machines virtuelles qui continuent d’obtenir leur configuration via DHCP (IP, sous-réseau et passerelle).

Hyperviseur comme routeur#

En bref, pour une machine virtuelle disposant d’une adresse IPv4 :

  • l’hyperviseur hôte configure une route /32 sur l’interface virtuelle,
  • cette route est distribuée aux autres hyperviseurs et routeurs via BGP.

Nous voulons aussi gérer deux domaines de routage : un domaine public pour les machines virtuelles de clients connectées à Internet et un domaine privé pour notre propre usage, notamment la gestion des hyperviseurs. À cet effet, chaque hyperviseur utilise deux tables de routage.

L’illustration suivante montre la configuration d’un hyperviseur avec cinq machines virtuelles. Notez l’absence de tout pont réseau.

Routage L3 à l'intérieur d'un hyperviseur
Routage L3 pour un hyperviseur. Chaque hyperviseur est connecté à un réseau « public » et un réseau « privé ». Les machines virtuelles peuvent être rattachées à n'importe lequel de ces réseaux. Chaque table de routage contient des routes directement connectées pour les machines virtuelles locales, des routes pour les machines virtuelles distantes et une route par défaut pour les autres destinations.

La configuration complète décrite dans cet article est également disponible sur GitHub. Un agent reste nécessaire pour mettre en place la configuration lors d’un changement. Pour se faire, ce dernier recevrait des notifications de la part du système de gestion des machines virtuelles.

Calico est un projet à l’objectif similaire (routage L3 jusqu’à l’hyperviseur) et des idées très proches (à l’exception de l’utilisation de Netfilter pour assurer l’isolation des domaines de routage). Il fournit un agent, Felix, qui s’interface avec des orchestrateurs (tels qu’OpenStack ou Kubernetes). C’est une alternative possible si on désire une solution clé en main.

Configuration du routage#

À l’aide de règles de routage, chaque interface est « attachée » à une table de routage :

$ ip rule show
0:  from all lookup local
20: from all iif lo lookup main
21: from all iif lo lookup local-out
30: from all iif eth0.private lookup private
30: from all iif eth1.private lookup private
30: from all iif vnet8 lookup private
30: from all iif vnet9 lookup private
40: from all lookup public

Les règles les plus importantes sont surlignées (priorités 30 et 40) : tout trafic provenant d’une interface privée utilise la table private. Tout le trafic restant utilise la table public.

Les deux règles en iif lo gèrent le routage des paquets émis par l’hyperviseur lui-même. La table local-out est une combinaison des tables private et public. À première vue, l’hyperviseur n’a besoin que de la table private mais il doit être capable de contacter les machines virtuelles locales (par exemple, pour répondre à un ping) via la table public. Ces tables contiennent en temps normal une route par défaut toutes les deux (pas de chaînage possible). La table local-out est construite en copiant toutes les routes de la table private et les routes directement connectées de la table public.

Pour éviter toute fuite de trafic accidentelle, les tables public, private et local-out contiennent une route par défaut avec une métrique élevée3. En temps normal, ces routes sont éclipsées par une véritable route par défaut :

ip route add blackhole default metric 4294967294 table public
ip route add blackhole default metric 4294967294 table private
ip route add blackhole default metric 4294967294 table local-out

Les choses sont plus simples avec IPv6 car il n’y a qu’un seul domaine de routage. Nous gardons cependant une table public mais il n’y a nul besoin de la table local-out :

$ ip -6 rule show
0:  from all lookup local
20: from all lookup main
40: from all lookup public

Une fois cette configuration en place, le routage est activé et le taille maximale du cache des routes IPv6 est augmentée (la valeur par défaut est seulement 4096)4 :

sysctl -qw net.ipv4.conf.all.forwarding=1
sysctl -qw net.ipv6.conf.all.forwarding=1
sysctl -qw net.ipv6.route.max_size=524288

Mise à jour (12.2019)

À partir de Linux 4.2, les entrées pour le cache des routes ne sont créées que s’il est nécessaire de noter une valeur spéciale pour le PMTU. Ainsi, il est préférable de garder la valeur par défaut et de surveiller le fichier /proc/net/rt6_stats : l’avant-dernière valeur est la taille actuelle du cache.

Routes pour les machines virtuelles#

La seconde étape est de configurer les routes pour atteindre chaque machine virtuelle. Pour IPv6, l’adresse du lien local (dérivée de l’adresse MAC) est utilisée comme prochain saut5 :

ip -6 route add 2001:db8:cb00:7100:5254:33ff:fe00:f/128 \
    via fe80::5254:33ff:fe00:f dev vnet6 \
    table public

Ajouter d’autres adresses IP ou sous-réseaux peut se faire en spécifiant d’autres routes sur le même modèle :

ip -6 route add 2001:db8:cb00:7107::/64 \
    via fe80::5254:33ff:fe00:f dev vnet6 \
    table public

En IPv4, la route utilise comme prochain saut l’interface à laquelle la machine virtuelle est connectée. Linux émettra une requête ARP avant de pouvoir router les paquets6 :

ip route add 192.0.2.15/32 dev vnet6 \
  table public

Les IP et sous-réseaux supplémentaires peuvent être ajoutées de la même façon. Cela impose toutefois que chaque adresse IP réponde aux requêtes ARP. Pour éviter cela, le routage peut se faire via la première IP configurée7 :

ip route add 203.0.113.128/28 \
  via 192.0.2.15 dev vnet6 onlink \
  table public

Configuration BGP#

La troisième étape consiste à partager les routes entre les hyperviseurs à l’aide de BGP. Cette partie de la configuration dépend du type de réseau connectant les hyperviseurs.

Fabrique#

Les hyperviseurs peuvent être connectés de plusieurs façons. Une première possibilité naturelle est d’utiliser un réseau L3 de type leaf-spine :

Fabrique routée
Fabrique L3 de type leaf-spine. Une session BGP est établie pour chacun des liens. Les routeurs spine doivent apprendre toutes les routes.

Chaque hyperviseur établit une session eBGP vers chaque routeur de type leaf. Ceux-ci établissent une session eBGP avec chaque routeurs de type spine. Cette solution peut s’avérer coûteuse car les routeurs de type spine doivent être capables de gérer l’intégralité des routes. Avec la génération actuelle de routeurs, cela implique une limite sur le nombre total de routes en fonction de la densité voulue8. Elle nécessite de plus beaucoup de configuration pour définir les sessions BGP, à moins d’utiliser certaines fonctionnalités d’autoconfiguration actuellement encore peu répandues. D’un autre côté, les routeurs de type leaf (et les hyperviseurs) peuvent apprendre moins de routes et pousser le reste du trafic vers le nord.

Une autre solution est d’utiliser une fabrique de type L2. Cela peut sembler surprenant après avoir critiqué les réseaux L2 pour leur manque de fiabilité mais nous n’avons pas besoin de haute disponibilité. Ils permettent alors d’obtenir une solution peu coûteuse et facile à mettre en place9 :

Fabrique de type L2
Fabrique L2 de type leaf-spine utilisant 4 réseaux L2 distincts. Chaque hyperviseur établit une session BGP vers au moins un réflecteur de route sur chaque réseau L2.

Chaque hyperviseur est connecté à 4 réseaux L2 distincts. Si un incident survient sur l’un d’eux, nous ne perdons qu’un quart de la bande passante disponible. Cette solution repose entièrement sur iBGP. Pour éviter de multiplier les connexions BGP entre les hyperviseurs, des réflecteurs de routes sont utilisés. Chaque hyperviseur établit une session iBGP avec un ou plusieurs réflecteurs sur chaque réseau L2. Les réflecteurs d’un même réseau L2 se partagent également leurs routes via iBGP. Calico documente ce concept de manière plus détaillée.

C’est la solution utilisée par la suite. La partie publique et la partie privée partagent la même infrastructure sur des VLAN différents.

Mise à jour (07.2022)

Bien que cette solution avait du sens il y a quelques années, le matériel récent est désormais capable de gérer bien plus de routes et je vous conseille donc d’opter pour une fabrique L3. Dans ce cas, nul besoin de réflecteurs de routes. En utilisant FRR au lieu de BIRD, il est même possible de n’utiliser qu’une unique session eBGP IPv6 avec VPNv4 (public et privé), VPNv6 (public) et EVPN (pour VXLAN, non présenté ici). Selon les équipements impliqués dans le réseau, vous pouvez configuré des sessions BGP sans IP. On peut alors se passer de définir un plan de numérotation et la configuration est alors encore réduite.

Réflecteurs de routes#

Les réflecteurs de routes centralisent et redistribuent les routes via BGP mais ne routent aucun trafic. Il en faut au moins un dans chaque réseau L2. Pour une meilleure disponibilité, on peut en utiliser plusieurs.

Voici un exemple de configuration avec Junos10 :

protocols {
    bgp {
        group public-v4 {
            family inet {
                unicast {
                    no-install; # ❶
                }
            }
            type internal;
            cluster 198.51.100.126; # ❷
            allow 198.51.100.0/25; # ❸
            neighbor 198.51.100.127;
        }
        group public-v6 {
            family inet6 {
                unicast {
                    no-install;
                }
            }
            type internal;
            cluster 198.51.100.126;
            allow 2001:db8:c633:6401::/64;
            neighbor 2001:db8:c633:6401::198.51.100.127;
        }
        ttl 255;
        bfd-liveness-detection { # ❹
            minimum-interval 100;
            multiplier 5;
        }
    }
}
routing-options {
    router-id 198.51.100.126;
    autonomous-system 65000;
}

Ce réflecteur accepte et redistribue toutes les routes IPv4 et IPv6. En ❶, on s’assure que les routes ne sont pas installées dans la FIB car un réflecteur n’est pas un routeur.

Chaque réflecteur doit disposer d’un cluster identifier qui est utilisé pour détecter les boucles. Dans notre cas, nous utilisons l’adresse IPv4 à cet effet (en ❷). En utilisant un identifiant différent pour chaque réflecteur attaché à un même réseau L2, on s’assure qu’ils s’échangeront les routes reçues, apportant ainsi une meilleure résilience.

Au lieu de déclarer explicitement chacun des hyperviseurs devant se connecter au réflecteur, un sous-réseau entier est autorisé en ❸11. Nous déclarons aussi le second réflecteur présent sur le même réseau L2 afin qu’ils s’échangent leurs routes.

Un autre point important est de réagir rapidement en cas d’indisponibilité d’un chemin. Avec des sessions BGP directement connectées, un lien défaillant peut être détecté immédiatement et la session BGP est aussitôt invalidée. Cela n’est pas toujours fiable et dans notre cas, cela ne fonctionne pas en raison de la présence de commutateurs sur les chemins. En ❹, nous activons BFD, un protocole qui permet de détecter en moins d’une seconde12 un problème entre deux pairs BGP (RFC 5880).

Un dernier point à prendre en compte est la possibilité de faire du routage anycast : si une IP est publiée sur plusieurs hyperviseurs, deux solutions sont acceptables :

  • envoyer tous les flux vers un seul hyperviseur ou
  • répartir les flux entre les hyperviseurs.

Le second choix permet d’obtenir un répartiteur de charge L3. Avec la configuration ci-dessus, pour chaque préfixe, le réflecteur va choisir un seul chemin et redistribuer celui-ci. Ainsi, un seul hyperviseur recevra les paquets. Pour obtenir une répartition de charge, il faut redistribuer l’ensemble des chemins possibles (RFC 7911)13 :

set protocols bgp group public-v4 family inet  unicast add-path send path-count 4
set protocols bgp group public-v6 family inet6 unicast add-path send path-count 4

Voici un extrait de show route montrant quelques routes « simples » ainsi qu’une route anycast :

> show route protocol bgp
inet.0: 6 destinations, 7 routes (7 active, 1 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0        *[BGP/170] 00:09:01, localpref 100
                    AS path: I, validation-state: unverified
                  > to 198.51.100.1 via em1.90
192.0.2.15/32    *[BGP/170] 00:09:00, localpref 100
                    AS path: I, validation-state: unverified
                  > to 198.51.100.101 via em1.90
203.0.113.1/32   *[BGP/170] 00:09:00, localpref 100
                    AS path: I, validation-state: unverified
                  > to 198.51.100.101 via em1.90
203.0.113.6/32   *[BGP/170] 00:09:00, localpref 100
                    AS path: I, validation-state: unverified
                  > to 198.51.100.102 via em1.90
203.0.113.18/32  *[BGP/170] 00:09:00, localpref 100
                    AS path: I, validation-state: unverified
                  > to 198.51.100.103 via em1.90
203.0.113.10/32  *[BGP/170] 00:09:00, localpref 100
                    AS path: I, validation-state: unverified
                  > to 198.51.100.101 via em1.90
                  [BGP/170] 00:09:00, localpref 100
                    AS path: I, validation-state: unverified
                  > to 198.51.100.102 via em1.90

La configuration complète est disponible sur GitHub. Des configurations similaires pour GoBGP, BIRD ou FRR (sur Cumulus Linux) sont également disponibles14. La configuration pour le domaine de routage privé est identique. Pour éviter d’investir dans du matériel dédié pour les réflecteurs, il est possible de convertir certains commutateurs de baie à cet usage.

Configuration de l’hyperviseur#

Passons maintenant à la dernière étape : la configuration de l’hyperviseur. BIRD (1.6.x) est utilisé en tant que démon BGP. Il maintient trois tables de routage internes (public, private and local-out). Nous définissons un patron avec les propriétés communes pour se connecter à un réflecteur :

template bgp rr_client {
  local as 65000;   # ASN local correspond à l'ASN des réflecteurs
  import all;       # Accepte toutes les routes reçues
  export all;       # Envoie toutes les routes de la table
  next hop self;    # Modifie le saut suivant avec l'IP de la session BGP
  bfd yes;          # Active BFD
  direct;           # Pair directement connecté
  ttl security yes; # GTSM activé
  add paths rx;     # Accepte ADD-PATH en réception

  # Réétablissement rapide des sessions BGP
  connect delay time 1;
  connect retry time 5;
  error wait time 1,5;
  error forget time 10;
}

table public;
protocol bgp RR1_public from rr_client {
  neighbor 198.51.100.126 as 65000;
  table public;
}
# […]

Avec la configuration ci-dessus, toutes les routes de la table public de BIRD sont envoyées au réflecteur 198.51.100.126. Toutes les routes reçues sont acceptées. Il nous reste à connecter la table public de BIRD à celle du noyau15 :

protocol kernel kernel_public {
  persist;
  scan time 10;
  import filter {
    # Accepte n'importe quelle route du noyau
    # sauf celle de dernier secours
    if krt_metric < 4294967294 then accept;
    reject;
  };
  export all;      # Envoie toutes les routes au noyau
  learn;           # Apprend les routes autres que celles de BIRD
  merge paths yes; # Utilise des routes ECMP si besoin
  table public;    # Nom de la table de routage dans BIRD
  kernel table 90; # Numéro de la table de routage dans le noyau
}

Il faut également activer BFD sur toutes les interfaces :

protocol bfd {
  interface "*" {
    interval 100ms;
    multiplier 5;
  };
}

Afin d’éviter qu’un encombrement temporaire de la table de suivi de Netfilter n’impacte BFD, il est prudent de désactiver le suivi de connexions pour ces paquets :

ip46tables -t raw -A PREROUTING -p udp --dport 3784 \
  -m addrtype --dst-type LOCAL -j CT --notrack
ip46tables -t raw -A OUTPUT -p udp --dport 3784 \
  -m addrtype --src-type LOCAL -j CT --notrack

Il faut également ajouter :

Une fois les sessions BGP établies, on peut vérifier les routes apprises :

$ ip route show table public proto bird
default
        nexthop via 198.51.100.1 dev eth0.public weight 1
        nexthop via 198.51.100.254 dev eth1.public weight 1
203.0.113.6
        nexthop via 198.51.100.102 dev eth0.public weight 1
        nexthop via 198.51.100.202 dev eth1.public weight 1
203.0.113.18
        nexthop via 198.51.100.103 dev eth0.public weight 1
        nexthop via 198.51.100.203 dev eth1.public weight 1

Performance#

Avec de nombreuses routes, on peut se soucier de la quantité de mémoire utilisée par Linux. Elle est très raisonnable :

  • 128 MiB permettent de gérer 1 million de routes IPv4,
  • 512 MiB permettent de gérer 1 million de routes IPv6.

Les chiffres doivent être doublés pour prendre en compte la mémoire utilisée par BIRD. En ce qui concerne les temps de recherche d’une route, les performances sont aussi excellentes avec IPv4 et très bonnes avec IPv6 :

  • 30 ns par recherche avec 1 million de routes IPv4,
  • 1.25 µs par recherche avec 1 million de routes IPv6.

Ainsi, l’impact de laisser la gestion d’un très grand nombre de routes à Linux est très faible. Pour plus de détails, sur le sujet, voyez « Fonctionnement de la table de routage IPv4 sous Linux » et « Fonctionnement de la table de routage IPv6 sous Linux ».

Filtrage par la source#

Pour éviter l’usurpation d’adresse IP, le filtrage par la source (reverse-path filtering) est activé sur chaque interface virtuelle : Linux va vérifier la légitimité de chaque IP source en vérifiant qu’une réponse serait renvoyée sur la même interface. Cela interdit toute tentative d’usurpation de la part d’une machine virtuelle.

Pour IPv4, il est possible d’activer cette fonctionnalité à travers un sysctl17 ou à travers Netfilter. Pour IPv6, seula la dernière option est disponible.

# Pour IPv6, utilise Netfilter
ip6tables -t raw -N RPFILTER
ip6tables -t raw -A RPFILTER -m rpfilter -j RETURN
ip6tables -t raw -A RPFILTER -m rpfilter --accept-local \
  -m addrtype --dst-type MULTICAST -j DROP
ip6tables -t raw -A RPFILTER -m limit --limit 5/s --limit-burst 5 \
  -j LOG --log-prefix "NF: rpfilter: " --log-level warning
ip6tables -t raw -A RPFILTER -j DROP
ip6tables -t raw -A PREROUTING -i vnet+ -j RPFILTER

# Pour IPv4, utilise les sysctls
sysctl -qw net.ipv4.conf.all.rp_filter=0
sysctl -qw net.ipv4.conf.all.rp_filter=0
for iface in /sys/class/net/vnet*; do
    sysctl -qw net.ipv4.conf.${iface##*/}.rp_filter=1
done

Il n’est pas utile de s’inquiéter d’une usurpation sur le L2, l’attaquant n’y gagnerait aucun avantage.

Leurrer les machines virtuelles#

Un point important est de s’assurer que les machines virtuelles continuent de penser qu’elles sont rattachées à un réseau L2 classique (une IP, un sous-réseau, une passerelle).

La première étape consiste à leur fournir la passerelle par défaut. Sur l’hyperviseur, il suffit d’assigner l’IP associée à l’interface virtuelle correspondante :

ip addr add 203.0.113.254/32 dev vnet5 scope link

Le but de cette manœuvre est de s’assurer que Linux répondra aux requêtes ARP concernant cette IP. La configuration d’un /32 est suffisante et il ne faut pas configurer un sous-réseau plus grand : Linux installerait la route correspondante sur cette interface, ce qui serait incorrect18.

Pour IPv6, ce n’est pas utile car les adresses de lien local sont utilisées comme passerelle.

Une machine virtuelle peut également vouloir échanger des paquets avec des machines partageant le même sous-réseau IP. L’hyperviseur va répondre aux requêtes ARP à leur place. Une fois que le trafic IP est reçu, il lui suffira alors de le router normalement. Cela peut se faire en activant le proxy ARP sur l’interface virtuelle :

sysctl -qw net.ipv4.conf.vnet5.proxy_arp=1
sysctl -qw net.ipv4.neigh.vnet5.proxy_delay=0

Pour IPv6, le proxy NDP de Linux est beaucoup moins pratique. À la place, le démon ndppd va gérer efficacement cette tâche. Pour chaque interface, nous utilisons la configuration suivante :

proxy vnet5 {
  rule 2001:db8:cb00:7100::/64 {
    static
  }
}

ndppd n’usurpe pas l’adresse source et certains systèmes sont plus pointilleux à ce sujet. Par conséquent, une adresse adéquate doit être configurée sur chaque interface :

ip addr add 2001:db8:cb00:7100::1/64 dev vnet5 noprefixroute nodad

En ce qui concerne le DHCP, certains démons peuvent être contrariés par l’adresse en /32 associée à l’interface. Toutefois, dnsmasq accepte de s’y faire. Si besoin, l’écriture d’un démon DHCP est relativement triviale. Pour IPv6, si l’adresse assignée est de type EUI-64, radvd fonctionne parfaitement avec cette configuration :

interface vnet5 {
  AdvSendAdvert on;
  prefix 2001:db8:cb00:7100::/64 {
    AdvOnLink on;
    AdvAutonomous on;
    AdvRouterAddr on;
  };
};

Conclusion et avenir#

La configuration présentée ici fonctionne avec BIRD 1.6.3 et Linux 3.15 ou plus récent. Elle permet de s’affranchir des limitations inhérentes des réseaux L2 en terme de flexibilité ou de disponibilité tout en restant transparent du point de vue des machines virtuelles hébergées. En faisant reposer l’effort de routage sur Linux, la solution est également économique car le matériel existant peut être réutilisé. Enfin, l’exploitation d’un tel réseau reste simple après avoir compris les bases (règles de routage, tables de routage et sessions BGP).

Il existe plusieurs améliorations potentielles :

Utilisation des VRF
À partir de Linux 4.3, les domaines virtuels de routage L3 (VRF) permettent d’associer des interfaces à des tables de routage. Nous pourrions donc définir trois VRF : public, private et local-out. Cela permettrait d’améliorer les performances en retirant la nécessiter de parcourir plusieurs règles de routage (cependant, avant Linux 4.8, les performances sont dégradées en raison de la non-utilisation des fonctionnalités d’accélération, voir le commit 7889681f4a6c). Pour plus d’informations, reportez vous à la documentation du noyau.
Routage L3 de bout en bout
Plutôt que de reposer sur une fabrique L2, la configuration BGP peut être améliorée et simplifiée en utilisant un certain nombre de mécanismes d’autoconfiguration. Cumulus a publié un livret sur le sujet : « BGP in the datacenter ». Toutefois, cela nécessite que toutes les implémentations BGP utilisées supportent ces fonctionnalités. Sur les hyperviseurs, cela impose l’utilisation de FRR et sur les équipements réseau, seul Cumulus Linux remplit les critères adéquats.
Utilisation de BGP LLGR
Utiliser BFD avec des temps de réaction courts permet d’invalider très rapidement un chemin non fonctionnel. En contre-partie, en cas de congestion ou de charge élevée, des paquets BFD peuvent être perdus rendant l’hyperviseur et les machines qu’il héberge indisponibles jusqu’au rétablissement des sessions BGP. Certaines implémentations de BGP supportent le Long-Lived BGP Graceful Restart, une extension conservant les routes perdues mais avec une priorité plus faible (voir draft-uttaro-idr-bgp-persistence-03). C’est une solution idéale pour notre problème : les routes perdues ne sont utilisées qu’en dernier secours quand tous les liens sont devenus indisponibles. Actuellement, aucune implémentation libre n’existe. Jetez un œil sur « BGP LLGR : sessions BGP fiables et réactives » pour plus de détails.

Mise à jour (08.2019)

Pour une meilleure sécurité, il est possible d’utiliser les fonctionnalités autour de la RPKI pour valider les annonces de chaque hyperviseur. Voir « Sécuriser BGP sur le serveur avec la validation de l’origine ».

Mise à jour (05.2021)

Facebook a publié « Running BGP in Data Centers at Scale ». Ce papier explique comment Facebook utilise BGP dans ses datacentres. Il s’agit d’une problématique orthogonale au fait d’étendre BGP jusqu’au serveur mais c’est une lecture intéressante avec, notamment, l’utilisation des politiques de routage pour faciliter l’opérabilité.

Mise à jour (10.2021)

Nous avons publié la configuration utilisée chez Blade dans nos datacentres de San Francisco et Séoul qui sont en BGP jusqu’aux serveurs avec une fabrique L3. Cela comprend la configuration pour des switchs top-of-the-rack Juniper et des switchs top-of-the-rack Cumulus. Ceux-ci disposent d’un serveur DHCP pour le provisioning. La configuration du BGP sur les serveurs se fait via une API servie par nginx.


  1. Le MC-LAG a été standardisé dans IEEE 802.1AX-2014. Toutefois, il est probable que la plupart des vendeurs conservent leurs propres implémentations. La caractéristique essentielle d’un MC-LAG est de garder des plans de contrôle indépendants. ↩︎

  2. Un incident peut se déclencher à la suite d’une erreur humaine mais également à cause de bugs logiciels. Ceux-ci surviennent notamment lors des opérations peu fréquentes, comme une mise à jour. ↩︎

  3. Ces routes ne doivent pas être distribuées via BGP. Une route par défaut avec une métrique plus faible doit être apprise depuis les routeurs de bordure. ↩︎

  4. Il ne faut pas augmenter cette value de manière trop brutale. Il est possible pour un attaquant de remplir le cache assez facilement. La valeur proposée ici correspond à 256 Mio de mémoire. Voir « Fonctionnement de la table de routage IPv6 sous Linux » pour plus de détails. ↩︎

  5. Cet exemple ne fonctionne que si la machine virtuelle n’utilise pas les extensions pour la vie privée (RFC 3041). Une solution alternative est d’assigner un /64 à chaque machine virtuelle. Cela présente aussi l’avantage de ne plus nécessiter de traiter les paquets NDP↩︎

  6. Une entrée ARP statique peut également être ajoutée. ↩︎

  7. Par exemple, un Juniper QFX5100 accepte environ 200 000 routes IPv4 (pour environ 10 000 $, avec des puces Broadcom Trident II). Un Arista 7208SR accepte quant à lui plus d’un million de routes IPv4 (pour environ 20 000 $, avec des puces Broadcom Jericho), à travers l’utilisation d’une TCAM externe. Un Juniper MX240 peut gérer quant à lui plus de 2 millions de routes IPv4 (pour environ 30 000 $ pour un chassis vide et deux cartes de routage, avec des puces Juniper Trio) avec une densité moindre. ↩︎

  8. En ce qui concerne la taille maximale d’une telle fabrique, avec des commutateurs capables de gérer 32 000 adresses MAC, la fabrique peut héberger 8000 hyperviseurs (plus de 5 millions de machines virtuelles). Ainsi, que ce soit pour la partie leaf ou pour la partie spine, il est possible d’utiliser des commutateurs classiques. Chaque hyperviseur devra savoir gérer toutes les routes, ce qui n’est pas un problème avec Linux. ↩︎

  9. L’utilisation d’instances de routage permettrait de configurer plusieurs réflecteurs sur le même équipement. L’exemple ne montre pas cette possibilité mais elle réduirait les coûts. ↩︎

  10. Cela interdit généralement d’utiliser un mécanisme d’authentification. BGP utilise habituellement les signatures TCP MD5 (RFC 2385). Sur la plupart des systèmes, cela implique de connaître par avance les pairs. Pour améliorer la sécurité, il est possible d’utiliser le Generalized TTL Security Mechanism (RFC 5082). Pour Junos, la configuration présentée ici (avec ttl 255) est incomplère. Un filtre de firewall est aussi nécessaire↩︎

  11. Sur la série de commutateurs QFX5000, l’interval minimal est officiellement d’une seconde. C’est regrettable car cela rend BFD quasiment inutile. ↩︎

  12. Malheureusement, pour une raison particulièrement mystérieuse, Junos ne supporte pas l’extension BGP add-path dans une instance de routage. Une telle configuration est toutefois possible avec Cumulus Linux. ↩︎

  13. Seul BIRD implémente BFD mais il ne supporte pas les pairs implicites. FRR nécessite d’être couplé à PTMD de Cumulus. Si BFD ne vous intéresse pas, GoBGP fait un excellent réflecteur de routes. ↩︎

  14. Les tables de routage du noyau son numérotées. La commande ip peut utiliser les noms déclarés dans le fichier /etc/iproute2/rt_tables↩︎

  15. À noter que BIRD 1.6.1 est nécessaire pour faire fonctionner l’ECMP avec IPv6. De plus, avec un Linux 4.11 ou plus récent, le commit 98bb80a243b5 est également requis. ↩︎

  16. Pour une interface donnée, Linux utilise la valeur maximale entre le sysctl pour all et celui de l’interface. ↩︎

  17. Il est possible d’empêcher Linux d’installer la route connectée en utilisant le drapeau noprefixroute. Celui-ci n’est disponible pour IPv4 que depuis Linux 4.4. Il n’est utile que si le serveur DHCP donne du fil à retordre car cela peut entraîner d’autres problèmes liés à la promotion des IP secondaires. ↩︎