Aller au contenu
XDP, le firewall ultra rapide de Linux
  1. Ebpf-Another-Types/

XDP, le firewall ultra rapide de Linux

·2529 mots·12 mins·
Joseph Ligier
Auteur
Joseph Ligier
CNCF ambassador | Kubestronaut 🐝
Sommaire
Apprenons XDP avec Aya - Cet article fait partie d'une série.
Partie 1: Cet article

Je débute la programmation en eBPF avec Aya. L’idée de cette série d’articles est d’apprendre un nouveau type de programme eBPF et de l’expérimenter avec le framework Rust Aya.

Aujourd’hui, nous allons nous plonger dans XDP : des programmes eBPF capables de filtrer ou rediriger des paquets réseau plus vite que son ombre.

Vous allez voir que cela peut être très intéressant pour créer un pare-feu efficace, capable de contrer les attaques en déni de service mais également pour load-balancer des millions de paquets réseaux.

Si tu ne connais pas eBPF, je te conseille de lire les deux premières parties de ma série S’initier à eBPF avec Aya. Cela couvre les bases et t’aidera pour la suite de l’article.

Qu’est-ce qu’XDP ?
#

Comme vous devez vous en doûter, XDP est un type de programme eBPF orienté réseau. XDP est l’acronyme de eXpress Data Path qu’on pourrait traduire par voie rapide pour les données.

Pour bien comprendre la signification de cet acronyme, il faut avoir conscience de ce que parcourt un paquet réseau depuis la carte réseau jusqu’à une application comme un serveur Web. Traditionnellement il fait le parcours suivant sous Linux :

Documentation ebpf

XDP s’exécute directement au niveau du driver, permettant ainsi de réagir plus rapidement qu’un firewall applicatif. Cette rapidité est notamment cruciale contre les attaques par déni de service distribué (DDoS : Distributed Denial of Service attack).

Si on veut être précis, XDP peut intervenir à 3 endroits différents selon la prise en charge de la carte réseau :

  • Au niveau du firmware de la carte réseau (mode offloaded) mais seulement quelques modèles de carte réseaux prennent en charge cela
  • Au niveau du driver réseau (mode native), le cas le plus courant pour des serveurs standards
  • Au niveau de la pile réseau du noyau (mode generic), permettant ainsi de tester sur n’importe quel ordinateur (comme ton portable)

N’hésitez pas à consulter cette page pour plus de précision.

Par contre, XDP intervenant sur une couche réseau très basse, les données en sortie récupérées sont brutes et non transformées par la couche réseau de Linux ce qui le rend plus complexe à gérer pour certaines tâches (comme le suivi des connexions et des retransmissions TCP).

Pour en savoir plus, n’hésitez pas à consulter la documentation officielle :

Documentation ebpf

Nous allons maintenant parler du contexte de la création de cette technologie.


Origin story
#

Les ancêtres de XDP
#

On n’a pas attendu XDP pour filtrer ou rediriger les paquets dans le noyau Linux.

Le premier firewall a été implémenté dans Linux 1.1 en 1994, il se nommait ipfw et pour le configurer il y avait la ligne de commande ipfwadm. Il est inspiré du firewall créé pour FreeBSD. Il a été remplacé par ipchains dans Linux 2.2 en 1999 qui était plus complet et adapté à la nouvelle couche réseau du noyau.

Netfilter : l’immortel
#

Pour la version 2.4 (2001) du noyau Linux, un tout nouveau pare-feu a été créé : Netfilter et sa ligne de commande iptables que tout ingénieur Linux réseau connaît très bien encore aujourd’hui. Netfilter traite les paquets réseaux à divers endroits de la pile réseau de Linux donc bien après le driver.

Depuis cette version, Netfilter a évolué pour gérer les nouveautés comme IPv6, les cgroups.

Vous avez probablement entendu parler de nftables. Elle est juste une ligne de commande qui permet de mettre à jour plus efficacement qu’iptables les règles. Pour cela on a dû modifier l’“api” du noyau Linux. Mais le moteur derrière reste netfilter.

Malgré ses qualités, Linux n’a jamais eu bonne presse pour le matériel réseau. Par exemple, la plupart des switchs et routeurs professionnels tournent sous d’autres systèmes d’exploitation. Est-ce qu’XDP pourrait inverser la tendance ?

XDP : le successeur ?
#

En 2014, eBPF est sortie. Mais c’est deux ans plus tard, pour la version 4.8 du noyau Linux que XDP était enfin utilisable. Il a été développé conjointement par Brenden Blanco (PLUMgrid Inc) et Tom Herbert (Facebook).

Contrairement à d’autres types de programme eBPF, XDP ne peut s’utiliser uniquement avec eBPF. Un hook est placé au niveau du driver réseau permettant ainsi de démarrer son programme eBPF. Ainsi il fallait développer les drivers compatibles XDP pour que cela puisse fonctionner. Il fallait également que les frameworks de l’époque puissent gérer ce nouveau type de programme. Ainsi dès juillet 2016, avant même la sortie officiel du noyau Linux compatible avec XDP, il était possible de créer des programmes XDP avec BCC :

BCC PR

Depuis, XDP a continué d’évoluer, on peut noter :

  • en 2018, l’apparition de la socket AF_XDP (Address Family Express Data Path) qui permet d’exploiter XDP depuis l’espace utilisateur sans passer par la pile réseau.
  • en 2022, la gestion des paquets importants (comme Jumbo packet, GRO ou BIG TCP) avec XDP Fragment.

Aujourd’hui, XDP est considéré comme stable et utilisable en production.

On ne peut pas dire qu’XDP est un remplaçant de Netfilter. Par exemple, recréer une commande équivalente à iptables avec XDP serait un travail colossal. Le projet bpfilter est un projet qui vise à traduire les règles iptables en programmes eBPF n’utilise pas uniquement XDP.

Pour finir, voici une petite frise chronologique de l’histoire de XDP :

XDP history

Maintenant qu’XDP est jugée stable, on peut se poser une question bien légitime : quels projets utilisent XDP ?


Quels projets utilisent XDP ?
#

Pour défier le lieu commun : “eBPF c’est utilisé que par 3 grosses boîtes”, j’ai fait une petite recherche des outils qui utilisaient réellement XDP et que donc vous l’utilisiez peut-être sans le savoir…

Katran : le loadbalancer de Facebook
#

Le projet Katran développe un load balancer XDP de niveau 4. Il est utilisé par Facebook mais également par Netflix. Par ailleurs, Facebook a créé un firewall basé sur XDP pour empêcher le DDoS.

Katran

Cilium : les services externes de Kubernetes
#

Le projet Cilium fournit un plugin CNI pour Kubernetes et peut également remplacer kube-proxy. Selon certaines conditions, Cilium peut activer XDP pour les services de type NodePort, LoadBalancer et externalIPs, permettant ainsi d’être plus performant.

Cilium

Cloudflare : pour empêcher le DDoS
#

En 2018, Cloudflare a publié un article expliquant comment il réduisait le DDoS avec XDP. Récemment Cloudflare a communiqué pour avoir déjoué un DDoS de 22,2 Tbit/s.

Cloudflare

Et bien d’autres
#

Il y a certainement d’autres projets moins connus qui utilisent XDP comme Surricata.

Nous allons maintenant voir les principales difficultés que l’on va rencontrer quand on va commencer à créer un programme XDP.


Comment débuter son programme XDP ?
#

Pour de nombreux programmes eBPF, la première difficulté est de trouver le point d’accroche. Pour XDP, c’est relativement simple : il suffit de connaître le nom de l’interface réseau où on veut accrocher le programme XDP.

La réelle difficulté réside plutôt dans la création d’un environnement réseau. La solution traditionnelle est de créer des VMs (avec Vagrant par exemple) et de les faire communiquer entre eux. Cette solution est tout à fait possible pour XDP mais ça me paraît un peu lourd à mettre en place (par exemple pour les labs).

Pour la suite, je vous propose une solution beaucoup plus légère : en utilisant les namespaces réseaux de Linux. Le même mécanisme qui est utilisé pour Docker.

Namespace

Création d’un environnement pour XDP
#

lab

Je suppose que vous êtes sous Linux pour la démonstration. Si ce n’est pas le cas, vous pouvez utiliser le lab Killercoda :

Killer coda screenshot

Script de création de l’environnement
#

On pourrait faire un tutoriel pour la création de namespaces réseaux mais comme ce n’est pas le sujet central et comme il y a déjà des scripts qu’on peut facilement trouver sur le net, je vous propose le script suivant :

git clone https://github.com/littlejo/eunomia.dev
cd eunomia.dev/docs/tutorials/42-xdp-loadbalancer/

Pour créer les namespaces réseaux :

./setup.sh
J’ai dû installer le paquet net-tools sous Debian pour avoir accès à la commande arp qui est utilisée dans le script pour effectuer des vérifications de bon fonctionnement.

Ce script va créer trois namespaces qui peuvent communiquer entre eux (lb, h2 et h3) via un bridge (br0) :

ip netns list
lb (id: 23)
h3 (id: 18)
h2 (id: 15)

Pour voir les IPs d’un namespace, on peut taper :

ip netns exec lb ip -brief -family inet addr
lo               UNKNOWN        127.0.0.1/8
veth6@if98       UP             10.0.0.10/24
Il faut voir ces namespaces un peu comme des containers à part qu’à l’intérieur des namespaces on a toutes les commandes de l’hôte !

On peut représenter de cette manière ces namespaces :

namepaces

Vérification du bon fonctionnement
#

Bien que le script fait déjà les vérifications nécessaires, on va tout de même en faire quelques uns nous même pour montrer les possibilités de tests qu’on peut faire.

On peut ainsi pinger l’ip d’un namespace depuis l’hôte :

ping -c 3 10.0.0.10

Ça ping bien :

PING 10.0.0.10 (10.0.0.10) 56(84) bytes of data.
64 bytes from 10.0.0.10: icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from 10.0.0.10: icmp_seq=2 ttl=64 time=0.124 ms
64 bytes from 10.0.0.10: icmp_seq=3 ttl=64 time=0.122 ms

--- 10.0.0.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2053ms
rtt min/avg/max/mdev = 0.060/0.102/0.124/0.029 ms

namepaces

Pour tester la connexions à un serveur web dans un namespace, je peux le faire de cette manière dans le namespace h2 :

ip netns exec h2 python3 -m http.server 8080

Si je veux tester que depuis lb, je peux me connecter au serveur web, je tape sur un autre terminal :

ip netns exec lb curl http://10.0.0.2:8080

namepaces

On pourra ainsi créer un petit load balancer dans le namespace lb et le rediriger vers le namespace h2 et h3.

On a ainsi un environnement de test complet pour la suite des articles.

Nettoyage
#

Pour supprimer les namespaces et les interfaces réseaux, on peut redémarrer la machine ou juste taper :

./teardown.sh

Mon premier programme xdp
#

Nous allons maintenant créer un petit programme XDP dans l’environnement créé.

xdp-tools est une suite d’outils pour créer et debugger des programmes XDP.

Pour l’installer sous Debian 13, j’ai installé le paquet :

apt install xdp-tools
Si vous galérez pour l’installer j’ai créé un container pour cela (littlejo/xdp-tools). Tout est .

On va utiliser xdp-filter qui permet de créer des programmes xdp qui filtrent les paquets.

On va installer un programme xdp sur l’interface veth0 :

xdp-filter load veth0

L’option load permet de charger le programme XDP.

Pour le décharger, il suffit d’utiliser l’option unload : xdp-filter unload veth0.

Voilà ce qu’on a fait schématiquement :

namepaces xdp

Depuis un autre terminal, on peut vérifier que le ping fonctionne :

ip netns exec lb ping -c 3 10.0.0.1

namepaces xdp

Vous pouvez également le voir :

xdp-filter status

Vous voyez qu’xdp a bien laissé passer 3 packets :

CURRENT XDP-FILTER STATUS:

Aggregate per-action statistics:
  XDP_ABORTED                                   0 pkts           0 KiB
  XDP_DROP                                      0 pkts           0 KiB
  XDP_PASS                                      3 pkts           0 KiB

Loaded on interfaces:
                                           Enabled features
xdpfilt_alw_all
  veth0 (native mode)                      tcp,udp,ipv6,ipv4,ethernet,allow

Filtered ports:
                                           Mode             Hit counter

Filtered IP addresses:
                                           Mode             Hit counter

Filtered MAC addresses:
                                           Mode             Hit counter

Pour filtrer l’ip de lb :

xdp-filter ip -m src 10.0.0.10

namepaces xdp

On peut supprimer la règle avec l’option -r : xdp-filter ip -m src -r 10.10.0.10

Avec cet exemple, il faut bien comprendre que les programmes XDP fonctionnent uniquement à l’arrivée de l’interface (ingress). Ainsi l’IP source est 10.0.0.10 et l’IP destination est toujours 10.0.0.1.

Si on avait voulu empêcher lb de pinger en créant un programme XDP sur l’interface jumelle veth1, on aurait dû lancer la commande suivante :

xdp-filter ip -m dst 10.0.0.10

namepaces xdp

En effet, l’ip source est maintenant l’IP de l’interface veth0 et l’IP destination est l’ip où envoie le echo reply c’est à dire 10.0.0.10.

Vérifiez tout de même que le ping ne fonctionne plus :

ip netns exec lb ping -c 3 10.0.0.1

Mais qu’on peut toujours pinger depuis un autre namespace :

ip netns exec h2 ping -c 3 10.0.0.1

On peut le voir avec xdp-filter status :

CURRENT XDP-FILTER STATUS:

Aggregate per-action statistics:
  XDP_ABORTED                                   0 pkts           0 KiB
  XDP_DROP                                      5 pkts           0 KiB
  XDP_PASS                                     16 pkts           1 KiB

Loaded on interfaces:
                                           Enabled features
xdpfilt_alw_all
  veth0 (native mode)                      tcp,udp,ipv6,ipv4,ethernet,allow

Filtered ports:
                                           Mode             Hit counter

Filtered IP addresses:
                                           Mode             Hit counter
  10.0.0.10                                src              5

Filtered MAC addresses:
                                           Mode             Hit counter
Dans le prochain épisode, nous allons montrer comment recréer la commande xdp-filter avec Aya.

Anatomie d’un paquet réseau
#

Maintenant qu’on a réussi à créer des programmes XDP avec xdp-filter, une autre difficulté qui va apparaître assez rapidement quand on va essayer de réellement développer un programme XDP, c’est les connaissances en réseau. Comme je l’écrivais au début de l’article, avec XDP on récupère des données bruts du paquet, il faut réussir à l’analyser en comprenant comment est architecturé un paquet réseau.

packet

Ainsi un paquet réseau a deux parties :

  • Le messager (les en-têtes) : les adresses IP, MAC, le protocole, le port utilisé, etc. La partie intéressante pour l’ingénieur réseau.
  • Le contenu du message (payload) : la partie intéressante pour l’utilisateur.

Comment sont structurés ses en-têtes ? Par les différentes couches du modèle TCP/IP. Ça vous dit quelques choses ? Moi j’avais (presque) tout oublié.

Modèle TCP/IP
#

Le modèle TCP/IP a 4 couches :

  1. Accès réseau : Filaire (Ethernet) ou sans fil (Wifi)
  2. Internet : IPv4, IPv6, ICMP, etc
  3. Transport : TCP et UDP
  4. Application : HTTP, SSH, DNS, etc

layers

XDP intervient dès la réception de la couche 1. Le paquet encapsule les différentes couches. Ainsi le paquet sera structuré de la manière suivante :

  1. l’en-tête accès réseau (on verra uniquement l’en-tête ethernet : ethernet header (ethhdr)), il va permettre de savoir si on est en IPv4 ou en IPv6
  2. l’en-tête internet (par exemple : internet protocol header (iphdr)), il va permettre de savoir si on est en UDP, TCP ou ICMP
  3. l’en-tête transport (par exemple : transmission control protocol header (tcphdr)), il va permettre de savoir sur quel port on est par exemple.
  4. l’en-tête applicative est beaucoup moins standard, nous n’irons pas jusque là.

Voici par exemple les différentes en-têtes pour une requête DNS :

headers

Ainsi pour créer un programme XDP, il faut se balader sur les différentes en-têtes du paquet et récupérer un élément particulier de l’en-tête.

L’en-tête d’UDP est composée des éléments suivants :

udphdr

  1. Port source : depuis quel port le paquet est envoyé (un port variable définit par l’application)
  2. Port destination : à quel port le paquet doit être envoyé (par exemple le port 53 pour le DNS)
  3. Longueur : longueur total du segment UDP (en-tête + payload)
  4. Checksum : vérification de l’intégrité du paquet.

Une fois qu’on a récupéré l’élément cible, nous pourrons décider par exemple si nous bloquons l’accès au paquet.


Dans cet épisode, on a vu les bases d’XDP : à quoi cela sert, son histoire, qui l’utilise et les pré-requis pour débuter un programme XDP.

Tout cela vous paraît abstrait ? Nous allons maintenant passer à la pratique dans l’épisode suivant : on va créer un petit firewall XDP avec Aya !

Apprenons XDP avec Aya - Cet article fait partie d'une série.
Partie 1: Cet article