- Janvier 2018 -
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éseau. 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.

Il y a deux défauts dans cette conception :
-
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.
-
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.

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 clef 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ée. 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
nombre maximal de routes IPv6 est augmenté (la valeur par défaut est
seulement 4096) :
sysctl -qw net.ipv4.conf.all.forwarding=1
sysctl -qw net.ipv6.conf.all.forwarding=1
sysctl -qw net.ipv6.route.max_size=4194304
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 saut :
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 paquets :
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ée :
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 :

Chaque hyperviseur établit une session eBGP vers chaque routeur de
type leaf. Ceux-ci établissent une session iBGP avec leur voisin
ainsi qu’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é
voulue. 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
traffic 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 place :

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.
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 JunOS :
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
❸. 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 seconde 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) :
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
disponibles. 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
noyau :
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
sysctl 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 incorrect.
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
}
}
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.
Planète des utilisateurs Debian - https://planet.debian.org/fr/
Expansion automatique des alias dans Zsh
- 8 mars -
Pour éviter les frappes superflues, fish propose des abréviations de commandes qui se substituent après avoir appuyé sur la barre d’espace. Nous (...)
Debian France renouvelle ses instances
- Septembre 2024 -
AGO de Debian France RappelL'assemblée générale annuelle de l'association Debian-France vient de se terminer. Pour rappel, Debian France est une (...)
Création de chemins d'AS infinis dans BGP
- Juillet 2024 -
La combinaison des confédérations BGP et du remplacement d’AS peut potentiellement créer une boucle de routage BGP, résultant en un chemin d’AS qui (...)
Pourquoi les fournisseurs de contenu ont besoin d'IPv6
- Juin 2024 -
IPv4 est une ressource coûteuse. Cependant, de nombreux fournisseurs de contenu sont encore uniquement en IPv4. La raison la plus souvent avancée (...)
Retour du Meetup Debian à Bordeaux du 16 mai
- Mai 2024 -
Retour du Meetup du 16 Mai à BordeauxLe 16 mai dernier s'est tenu le meetup Debian au sein du Yack (le local de coworking de Yaal Coop).Nous (...)