VXLAN & Linux
Vincent Bernat
VXLAN est un protocole réseau permettant de transporter du trafic Ethernet au-dessus d’un réseau IP existant tout en supportant un nombre très elevé de locataires. Il est défini dans la RFC 7348.
Depuis Linux 3.12, l’implémentation de VXLAN est plutôt complète et supporte aussi bien le multicast que l’unicast, au-dessus d’IPv6 ou d’IPv4. Regardons les différentes façons de le mettre en œuvre.
Pour illustrer les exemples qui suivent, nous utilisons la configuration suivante :
- un réseau IP existant (hautement disponible et évolutif, peut-être Internet),
- trois ponts Linux servant de terminaison VXLAN (VXLAN tunnel endpoints ou VTEP),
- quatre serveurs croyant partager un segment Ethernet commun.
Un tunnel VXLAN étend les segments Ethernet existants en un unique
segment Ethernet (virtuel). Depuis n’importe quel hôte (par exemple
H1
), nous pouvons atteindre tous les autres hôtes partageant le
segment virtuel:
$ ping -c10 -w1 -t1 ff02::1%eth0 PING ff02::1%eth0(ff02::1%eth0) 56 data bytes 64 bytes from fe80::5254:33ff:fe00:8%eth0: icmp_seq=1 ttl=64 time=0.016 ms 64 bytes from fe80::5254:33ff:fe00:b%eth0: icmp_seq=1 ttl=64 time=4.98 ms (DUP!) 64 bytes from fe80::5254:33ff:fe00:9%eth0: icmp_seq=1 ttl=64 time=4.99 ms (DUP!) 64 bytes from fe80::5254:33ff:fe00:a%eth0: icmp_seq=1 ttl=64 time=4.99 ms (DUP!) --- ff02::1%eth0 ping statistics --- 1 packets transmitted, 1 received, +3 duplicates, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.016/3.745/4.991/2.152 ms
Usage simple#
Le déploiement de référence pour VXLAN est d’utiliser un groupe IP multicast pour joindre les autres VTEP:
# ip -6 link add vxlan100 type vxlan \ > id 100 \ > dstport 4789 \ > local 2001:db8:1::1 \ > group ff05::100 \ > dev eth0 \ > ttl 5 # brctl addbr br100 # brctl addif br100 vxlan100 # brctl addif br100 vnet22 # brctl addif br100 vnet25 # brctl stp br100 off # ip link set up dev br100 # ip link set up dev vxlan100
Les commandes ci-dessus créent une interface de terminaison VXLAN
appelée vxlan100
et la place dans un pont avec d’autres interfaces
classiques1. Chaque segment VXLAN est associé à un identifant
sur 24 bits, le VXLAN Network Identifier (VNI). Dans notre exemple,
le VNI par défaut est configuré avec la directive id 100
.
La première implémentation de VXLAN remonte à Linux 3.7 et le port UDP
à utiliser n’était pas encore défini. Plusieurs constructeurs ont
choisi 8472 et Linux a aussi opté pour cette valeur. Afin de ne pas
casser les déploiements existants, c’est toujours la valeur par
défaut. Pour utiliser le port assigné par l’IANA, il faut indiquer
explictement la directive dstport 4789
.
Pour utiliser le transport multicast, nous devons en préciser le
groupe (group ff05::100
) ainsi que l’interface physique à laquelle
s’associer (dev eth0
). Le TTL par défaut est alors de 1. Si le
réseau sous-jacent utilise du routage, il peut être nécessaire
d’augmenter cette valeur, comme ici avec la directive ttl 5
.
L’interface vxlan100
se comporte comme un pont et considère les VTEP
distants comme des ports virtuels :
- les trames dont la destination n’est pas connue et celles à large diffusion (broadcast, unknown unicast and multicast ou « BUM ») sont envoyées à tous les VTEP en utilisant le groupe multicast,
- les associations entre les adresses MAC et les IP des VTEP sont découvertes via l’apprentissage des adresses source.
La figure suivante détaille la configuration mise en place:
La table de correspondance MAC/VTEP (« Forwarding Database » ou FDB)
de l’interface VXLAN peut être consultée avec la commande
bridge
. Lorsque l’adresse MAC destination est connue, la trame est
envoyée au VTEP associé (unicast). L’adresse 00:00:00:00:00:00
n’est
utilisée que lorsqu’une entrée plus spécifique n’existe pas.
# bridge fdb show dev vxlan100 | grep dst 00:00:00:00:00:00 dst ff05::100 via eth0 self permanent 50:54:33:00:00:0b dst 2001:db8:3::1 self 50:54:33:00:00:08 dst 2001:db8:1::1 self
Pour plus de détails sur la façon de configurer un réseau multicast et l’utiliser pour construire des segments VXLAN, jetez un œil sur mon article « Réseaux virtuels avec VXLAN ».
Sans multicast#
L’utilisation d’un réseau IP multicast comme transport pour VXLAN présente plusieurs avantages :
- découverte automatique des autres VTEP,
- bonne utilisation de la bande passante (les paquets sont dupliqués tardivement),
- conception décentralisée et sans contrôleur2.
Toutefois, le multicast n’est pas disponible partout et sa mise en œuvre est parfois compliquée. Depuis Linux 3.8, les extensions DOVE ont été ajoutées et elles permettent notamment de supprimer cette dépendance sur le multicast.
Unicast avec déclaration statique des VTEP#
Il est possible de remplacer le multicast par une réplication par l’émetteur de chaque trame de type « BUM » vers une liste de VTEP distants3 :
# ip -6 link add vxlan100 type vxlan \ > id 100 \ > dstport 4789 \ > local 2001:db8:1::1 # bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 2001:db8:2::1 # bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 2001:db8:3::1
Le VXLAN est défini sans groupe multicast. À la place, les VTEP
distants sont associés à l’adresse 00:00:00:00:00:00
. Une trame de
type « BUM » sera envoyée à chacune de ces destinations. L’interface
VXLAN apprend également toujours les adresses MAC sources, limitant
ainsi ce type d’envois.
Il s’agit d’une solution très simple. Avec un peu d’automatisation, il est possible de garder à jour la liste des VTEP. La duplication des trames à la source devient problématique quand il y a des centaines de VTEP.
Le démon vxfld de Cumulus est un exemple d’utilisation de cette stratégie dans l’un des deux modes de fonctionnement supportés. « Keepalived et unicast avec plusieurs interfaces » présente un autre cas d’usage.
Unicast avec déclaration statique des entrées L2#
Quand l’association entre les adresses MAC et les VTEP est connue, il est possible de renseigner par avance la FDB et de désactiver l’apprentissage :
# ip -6 link add vxlan100 type vxlan \ > id 100 \ > dstport 4789 \ > local 2001:db8:1::1 \ > nolearning # bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 2001:db8:2::1 # bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 2001:db8:3::1 # bridge fdb append 50:54:33:00:00:09 dev vxlan100 dst 2001:db8:2::1 # bridge fdb append 50:54:33:00:00:0a dev vxlan100 dst 2001:db8:2::1 # bridge fdb append 50:54:33:00:00:0b dev vxlan100 dst 2001:db8:3::1
Le drapeau nolearning
désactive l’apprentissage des adresses
sources. Si une adresse MAC est manquante, la trame sera toujours
envoyée en utilisant les adresses pour l’entrée 00:00:00:00:00:00
.
Cette dernière est toujours nécessaire pour le trafic à diffusion générale ou restreinte (ARP et la découverte des voisins en IPv6). Ce type de configuration fonctionne bien pour des machines virtuelles (les adresses MAC sont connues, mais pas les adresses IP). Un composant qui maintient à jour les entrées de la FDB est nécessaire.
BGP EVPN avec FRR est un exemple d’utilisation de cette stratégie (voir mon article « VXLAN: BGP EVPN avec FRR » pour plus d’informations).
Unicast avec déclaration statique des entrées L3#
Dans l’exemple précédent, l’entrée 00:00:00:00:00:00
reste
nécessaire pour permettre à ARP et à la découverte des voisins IPv6 de
fonctionner. Toutefois, Linux peut répondre à ces requêtes à la place
des nœuds distants4. Quand cette fonctionnalité est activée,
les entrées par défaut ne sont plus nécessaires (mais il est possible
de les conserver) :
# ip -6 link add vxlan100 type vxlan \ > id 100 \ > dstport 4789 \ > local 2001:db8:1::1 \ > nolearning \ > proxy # ip -6 neigh add 2001:db8:ff::11 lladdr 50:54:33:00:00:09 dev vxlan100 # ip -6 neigh add 2001:db8:ff::12 lladdr 50:54:33:00:00:0a dev vxlan100 # ip -6 neigh add 2001:db8:ff::13 lladdr 50:54:33:00:00:0b dev vxlan100 # bridge fdb append 50:54:33:00:00:09 dev vxlan100 dst 2001:db8:2::1 # bridge fdb append 50:54:33:00:00:0a dev vxlan100 dst 2001:db8:2::1 # bridge fdb append 50:54:33:00:00:0b dev vxlan100 dst 2001:db8:3::1
Il n’y a plus de duplication des paquets vers tous les VTEP. Toutefois, les protocoles reposant sur le multicast ne fonctionnent alors plus. Avec un peu d’automatisation, cette configuration fonctionne bien avec des conteneurs : s’il y a un registre recensant les adresses IP et les adresses MAC utilisées, un programme peut écouter les changements effectués dans ce registre et mettre à jour la FDB et la table des voisins.
Le moteur VXLAN de libnetwork (utilisé par Docker) est un exemple d’utilisation d’une telle stratégie (il utilise aussi la méthode suivante).
Unicast avec entrées L3 dynamiques#
Linux peut également notifier un programme quand une entrée (L2 ou L3) est manquante. Ce programme peut interroger un registre et ajouter l’entrée nécessaire. Toutefois, pour une entrée L2, les notifications sont envoyées uniquement si les conditions suivantes sont réunies :
- l’adresse MAC destination n’est pas connue,
- il n’y a pas d’entrée pour
00:00:00:00:00:00
dans la FDB, - l’adresse MAC n’est ni multicast, ni broadcast.
Ces limitations empêchent tout scénario de type « unicast avec entrées L2 dynamiques ».
Tout d’abord, créons une interface VXLAN avec les drapeaux l2miss
et
l3miss
5:
ip -6 link add vxlan100 type vxlan \ id 100 \ dstport 4789 \ local 2001:db8:1::1 \ nolearning \ l2miss \ l3miss \ proxy
Les notifications sont envoyées à tout programme écoutant sur une
chaussette AF_NETLINK
avec le protocole NETLINK_ROUTE
et liée au
groupe RTNLGRP_NEIGH
. La commande suivante se comporte de cette
façon et décode les notifications reçues :
# ip monitor neigh dev vxlan100 miss 2001:db8:ff::12 STALE miss lladdr 50:54:33:00:00:0a STALE
La première notification concerne l’absence d’une entrée dans la table des voisins pour l’adresse IP mentionnée. Elle peut être ajoutée avec la commande suivante :
ip -6 neigh replace 2001:db8:ff::12 \ lladdr 50:54:33:00:00:0a \ dev vxlan100 \ nud reachable
L’entrée n’est pas permanente et expirera sans action particulière de notre part. Une autre notification sera générée afin de la rafraîchir si nécessaire.
Une fois que la réponse parvient à l’hôte source, ce dernier peut envoyer une trame en utilisant l’adresse MAC ainsi apprise. La seconde notification concerne l’absence de cette adresse MAC dans la FDB. La commande suivante permet d’ajouter l’entrée nécessaire6 :
bridge fdb replace 50:54:33:00:00:0a \ dst 2001:db8:2::1 \ dev vxlan100 dynamic
L’entrée est marquée comme non permanente pour permettre à la MAC de migrer sur le pont local (une entrée dynamique ne peut pas être mise en place s’il y a déjà une entrée permanente).
Cette méthode fonctione bien avec des conteneurs et un registre global. Elle impose cependant une légère latence lors des premières connexions. De plus, le multicast et broadcast ne sont pas disponibles pour le réseau sous-jacent. Le moteur VXLAN de flannel, une fabrique réseau pour Kubernetes est une application de cette stratégie.
Décision#
Il n’y a pas de solution universelle.
L’utilisation du multicast s’impose si :
- vous êtes dans un environnement où le multicast est disponible,
- vous êtes prêts à opérer (et étendre) un réseau multicast,
- vous avez besoin du multicast et du broadcast dans les segments virtuels,
- vous ne disposez pas des adresses L2/L3 utilisées par les locataires.
Cette solution reste valable avec plusieurs milliers de locataires si l’on prend garde à ne pas mettre tous les VXLAN dans le même groupe multicast (par exemple, en utilisant le dernier octet du VNI comme dernier octet du group multicast).
Quand le multicast n’est pas disponible, une autre solution générique est BGP EVPN : BGP est utilisé comme contrôleur pour assurer la distribution de la liste des VTEP et de leurs FDB. Comme indiqué précédemment, FRR fournit une implémentation de cette solution. J’explore davantage cette option dans un article séparé : « VXLAN: BGP EVPN avec FRR ».
Si vous êtes dans un environnement utilisant des conteneurs avec un registre, une solution reposant sur la gestion statique et/ou dynamique des entrées L2 et L3, sans apprentissage automatique des adresses, convient également. Elle apporte une meilleure sécurité (limite des ressources utilisées, protection contre les attaques MiTM, maîtrise de la bande passante). Différentes solutions sont disponibles selon l’orchestrateur utilisé7. Il est aussi possible d’écrire sa propre solution.
Considérations annexes#
Indépendamment de la solution choisie, voici quelques points importants à garder à l’esprit en utilisant VXLAN.
Isolation#
Alors que l’on peut penser qu’une interface VXLAN ne concerne que le trafic Ethernet, Linux ne désactive pas le traitement IP pour ces interfaces. Quand la MAC de destination est locale, Linux route ou délivre le paquet IP encapsulé. Pour plus de détails, référez-vous à mon article sur l’isolation d’un pont réseau sous Linux.
Chiffrement#
VXLAN apporte une isolation entre les différents « locataires » mais le trafic circule en clair. La solution la plus simple pour le chiffrer est IPsec. Certaines solutions dédiées aux conteneurs disposent déjà d’un tel support (notamment libnetwork pour Docker mais flannel a aussi ça dans les cartons). C’est un point crucial pour les déploiements sur un nuage public.
Coût supplémentaire en octet#
Le format d’une trame encapsulée dans VXLAN est le suivant :
L’utilisation de VXLAN alourdit donc la trame Ethernet envoyée de 50 octets supplémentaires. Si vous utilisez en plus IPsec, le coût engendré dépend de plusieurs facteurs. En mode transport, avec AES et SHA256, il est de 56 octets. Avec le transport compatible avec le NAT, il passe à 64 octets. En mode tunnel, il devient de 72 octets.
Certains utilisateurs s’attendent à ce que le MTU Ethernet soit de 1500 ce qui impose d’augmenter le MTU du réseau sous-jacent. Si ce n’est pas possible, il est important de s’assurer que tous les clients utilisent un MTU réduit8.
IPv6#
Bien qu’IPv6 ait été utilisé dans tous les exemples, l’écosystème reste très jeune. La stratégie multicast fonctionne bien avec IPv6 mais tous les autres scénarios nécessitent des rustines (1, 2, 3, 4).
Mise à jour (11.2020)
À partir de Linux 4.13, tous les problèmes mentionnés sont résolus. IPv6 est désormais mieux testé car Cumulus met en avant ses propres solutions reposant sur IPv6 pour le transport et la signalisation.
De plus, IPv6 n’est souvent pas implémenté dans les outils orchestrant VXLAN :
- Cumulus vxfld ne supporte pas IPv6,
- flannel n’a absolument aucun support pour IPv6,
- libnetwork pour Docker n’a pas de support IPv6 pour la partie VXLAN.
Multicast#
Linux n’effectue pas d’IGMP snooping sur les interfaces VXLAN. Le trafic multicast est alors diffusé à tous les VTEP à moins d’insérer manuellement les adresses MAC multicast appropriées dans la FDB.
-
Il s’agit d’une implémentation possible. Le pont n’est nécessaire que si une forme d’apprentissage des adresses sources est nécessaire. Une autre stratégie est d’utiliser des interfaces MACVLAN. ↩︎
-
Le réseau multicast sous-jacent peut cependant nécessiter des composants centraux, tels que des points de rendez-vous pour le protocole PIM-SM. Il est possible de rendre ceux-ci hautement disponibles et évolutifs (en utilisant par exemple Anycast-RP, RFC 4610). ↩︎
-
Pour cet exemple et les suivants, une rustine pour utiliser IPv6 comme transport est nécessaire pour la commande
ip
(versions 4.10 et antérieures). Il est aussi possible d’utiliser cette astuce pour contourner le problème :↩︎# ip -6 link add vxlan100 type vxlan \ > id 100 \ > dstport 4789 \ > local 2001:db8:1::1 \ > remote 2001:db8:2::1 # bridge fdb append 00:00:00:00:00:00 \ > dev vxlan100 dst 2001:db8:3::1
-
Une rustine supplémentaire pour IPv6 peut être nécessaire (versions 4.11 ou antérieures). De plus, pour faire fonctionner ICMPv6, une rustine supplémentaire est requis (présent dans 4.14.2, 4.13.16 et 4.15). ↩︎
-
Une rustine est nécessaire pour recevoir des notifications L3 pour les adresses IPv6 (versions 4.11 et antérieures). ↩︎
-
Il aurait été judicieux d’ajouter directement l’entrée dans la FDB lors de la première notification afin d’éviter à l’hôte source de retransmettre les premières trames. ↩︎
-
flannel et libnetwork pour Docker ont déjà été mentionnés. Il existe aussi des expérimentations intéressantes comme BaGPipe BGP pour Kubernetes qui utilise BGP EVPN et est donc interopérable avec des solutions constructeurs. ↩︎
-
Il n’existe pas de mécanisme de découverte du MTU pour un segment Ethernet. ↩︎