Aller au contenu
Implémentons un petit firewall avec XDP
  1. Ebpf-Another-Types/

Implémentons un petit firewall avec XDP

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

Nous avons vu ce qu’était un programme XDP dans la première partie : cela peut être un moyen de filtrer un flux réseau.

Dans cette partie, je vous propose une petite balade dans les différentes en-têtes et de montrer comment on peut opérer le filtrage. Nous allons ainsi voir comment redévelopper xdp-filter avec le framework Rust Aya.

Suivez le guide !

lab

Je suppose que vous êtes déjà dans un environnement pour développer avec Aya. Si ce n’est pas le cas, vous pouvez utiliser le lab Killercoda :

Killer coda screenshot


Créons le hello world en XDP
#

Recréons l’environnement de développement
#

Comme nous l’avons vu dans la précédente partie, je vous propose d’installer des namespaces :

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

Rappelons ce qu’on a créé :

namepaces

Créons le programme “hello world”
#

Comme nous l’avons fait avec xdp-filter, nous allons installer le programme XDP sur veth0.

Générons déjà le programme Aya :

cargo generate --name browser-xdp \
               -d program_type=xdp \
               -d default_iface=veth0 \
               https://github.com/aya-rs/aya-template

Buildons et installons le programme “hello world” :

cd browser-xdp/
cargo run

Sur un autre terminal, testons le programme :

ip netns exec lb ping -c 2 10.0.0.1

Du côté du terminal cargo run, on voit bien deux paquets :

[INFO  browser_xdp] received a packet
[INFO  browser_xdp] received a packet

Ainsi à chaque fois que l’interface reçoit un paquet, le programme XDP est lancé.

namepaces with xdp program

Maintenant nous allons voir comment arrêter un paquet et quel type de paquet on a affaire.


Les bases pour créer un programme XDP
#

Les actions XDP
#

Regardons d’abord le code généré par cargo.

Le fichier le plus important est celui-là : browser-xdp-ebpf/src/main.rs c’est à dire le code côté noyau.

Globalement le code ressemble aux codes générés pour d’autres types de programmes eBPF comme les Tracepoints :

#[xdp]
pub fn browser_xdp(ctx: XdpContext) -> u32 {
    match try_browser_xdp(ctx) {
        Ok(ret) => ret,
        Err(_) => xdp_action::XDP_ABORTED,
    }
}

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    info!(&ctx, "received a packet");
    Ok(xdp_action::XDP_PASS)
}

Cependant on peut remarquer une petite spécificité : xdp_action::*.

Cela permet de déterminer ce que fait le paquet une fois qu’il a fini de parcourir le programme eBPF.

Documentation ebpf

On a 5 actions différentes :

  • XDP_ABORTED : Arrête le paquet avec erreur
  • XDP_DROP : Arrête le paquet silencieusement
  • XDP_PASS : Laisse passer le paquet
  • XDP_REDIRECT : Redirige le paquet vers une autre interface ou une socket AF_XDP attachée à une interface
  • XDP_TX : Redirige le paquet vers la même interface

Dans cet article, nous utiliserons uniquement XDP_PASS et XDP_DROP.

Le contexte XDP et ses méthodes
#

Comme pour tous les autres programmes eBPF, pour aller au-delà du hello world, il faut comprendre comment jouer avec le contexte.

Regardons la documentation :

Documentation aya

Contrairement aux précédents types de programmes où on utilisait qu’une seule méthode, il y en a 4 différentes pour XDP :

  • data() retourne l’adresse mémoire du début du paquet réseau.
  • data_end() retourne l’adresse mémoire de fin du paquet réseau.
  • metadata() retourne l’adresse mémoire du début de la métadonnées de XDP lié au kernel linux ou au driver.
  • metadata_end() retourne l’adresse mémoire de fin de la métadonnées. De la même manière que data_end(),

Dans cet article, nous utiliserons uniquement les méthodes data() et data_end() pour récupérer les différentes en-têtes.

Comment récupérer les en-têtes ?
#

Rappelons le schéma suivant :

headers

  • data() se trouve au début de l’en-tête ethHdr
  • data_end() se trouve à la fin du payload

Comment récupérer l’en-tête ethernet (ethHdr) ? Elle se situe au tout début du paquet.

let ethhdr = ctx.data();

Pour récupérer l’en-tête suivante, il suffit de rajouter la longueur de l’en-tête ethernet, en pseudo-code Rust ça donnerait :

let ipv4hdr = ctx.data() + sizeof(ethhdr);

Etc.

Concentrons-nous sur l’en-tête ethernet. Comment récupérer ses éléments ? ctx.data() est de type usize. Un entier naturel qui représente une adresse mémoire. Il faut donc récupérer son contenu. Comment faire cela ?

De la même manière que *const u8 représente un pointeur vers une chaine de caractères, ici on doit faire la même chose pour l’en-tête ethernet.

Ainsi il faut convertir dans une structure qui représente l’en-tête de la trame Ethernet. On a plusieurs possibilités pour le faire comme aller voir le code C et le convertir en Rust ce qui est un bon exercice mais un peu fastidieux. Heureusement, comme Rust est désormais bien présent dans l’écosystème d’eBPF, il y a déjà une crate Rust qui nous a fait le travail : network-types.

Documentation network-types

Pour récupérer cette crate, on va modifier le fichier browser-xdp-ebpf/Cargo.toml et rajouter dans la section dependencies :

network-types = "0.1.0"

On va ainsi pouvoir l’inclure dans le code principal (browser-xdp-ebpf/src/main.rs) :

use network_types::eth::EthHdr;

[...]

let ethhdr = ctx.data() as *const EthHdr;

Maintenant qu’on a récupéré la structure de l’en-tête ethernet, nous pouvons récupérer les différents éléments qui composent l’en-tête :

ethhdr

  • DST MAC : l’adresse MAC destination
  • SRC MAC : l’adresse MAC source
  • ETHERTYPE : détermine le protocole du niveau supérieur (Comme IPv4, IPv6, Arp pour citer les plus connus).

Comment le faire avec Aya ? Regardons la documentation :

Documentation ethhdr

Ainsi pour récupérer l’adresse de destination, il suffit de coder :

let dst_addr = unsafe { (*ethhdr).dst_addr};

info!(&ctx, "dst_addr {:mac} ", dst_addr);

Nous devons utiliser unsafe car nous déréférençons un pointeur brut.

On peut remarquer que la macro info! nous permet de convertir des tableaux de 6 octets en une adresse MAC. Si vous préférez la notation en majuscule, il suffit d’écrire :MAC.

Essayons maintenant. Voici le code principal modifié :

use network_types::eth::EthHdr;

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    let ethhdr = ctx.data() as *const EthHdr;

    let dst_addr = unsafe { (*ethhdr).dst_addr};

    info!(&ctx, "dst_addr {:mac} ", dst_addr);

    info!(&ctx, "received a packet");
    Ok(xdp_action::XDP_PASS)
}

Buildons le programme :

cargo build

La compilation fonctionne.

Installons le dans le noyau :

cargo run

On a alors l’erreur suivante :

Error: the BPF_PROG_LOAD syscall failed. Verifier output: 0: R1=ctx() R10=fp0
; unsafe { (*self.ctx).data as usize } @ xdp.rs:16
0: (61) r1 = *(u32 *)(r1 +0)          ; R1_w=pkt(r=0)
; let dst_addr = unsafe { (*ethhdr).dst_addr}; @ main.rs:20
1: (71) r0 = *(u8 *)(r1 +1)
invalid access to packet, off=1 size=1, R1(id=0,off=1,r=0)
R1 offset is outside of the packet
verification time 65 usec
stack depth 0
processed 2 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0


Caused by:
    Permission denied (os error 13)

Le verifier n’aime pas vraiment cette partie du code :

let ethhdr = ctx.data() as *const EthHdr;
Quand on essaie d’installer un programme eBPF dans le noyau Linux, il y a un programme Linux (le verifier) qui fait des vérifications dans le code pour éviter qu’on casse la sécurité du noyau Linux.

L’erreur peut se traduire en français par :

Accès invalide au paquet.
L'offset du registre 1 est en dehors du paquet

Pas super clair. Essayons d’expliquer mieux. Quand on tente let ethhdr = ctx.data() as *const EthHdr;, Il y a un accès à la mémoire. Le verifier exige qu’on prouve que cet accès soit toujours dans le paquet (entre data() et data_end()).

Il faut donc faire une vérification pour le rassurer avant cet accès :

if ctx.data() + EthHdr::LEN > ctx.data_end() {
    return Ok(xdp_action::XDP_PASS);
}

On peut traduire par : si l’accès ne se trouve pas au niveau du paquet, le programme XDP ne traite pas le paquet : il le laisse continuer son chemin dans la pile réseau de noyau Linux.

Sinon on peut alors convertir en EthHdr :

let ethhdr = ctx.data() as *const EthHdr;

Voici le code complet de la fonction principale :

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    let start = ctx.data() ;
    let end = ctx.data_end() ;

    if start + EthHdr::LEN > end { //Check for the verifier
        return Ok(xdp_action::XDP_PASS);
    }

    let ethhdr = start as *const EthHdr;

    let dst_addr = unsafe { (*ethhdr).dst_addr};

    info!(&ctx, "dst_addr {:mac} ", dst_addr);
    info!(&ctx, "received a packet");

    Ok(xdp_action::XDP_PASS)
}

Vérifions que ça marche :

cargo run

Lançons des ping. On récupère bien l’adresse mac :

[INFO  browser_xdp] dst_addr de:ad:be:ef:00:01
[INFO  browser_xdp] received a packet
[INFO  browser_xdp] dst_addr de:ad:be:ef:00:01
[INFO  browser_xdp] received a packet

Maintenant qu’on a réussi à récupérer l’en-tête ethHdr, comment récupérer les autres en-têtes ? Il suffit de rajouter la longueur des en-têtes qui les précèdent.

De manière assez évidente, nous allons utiliser des méthodes très proches pour toutes les en-têtes. Il serait judicieux de créer une fonction. La voilà :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use core::mem::size_of;

#[inline(always)]
fn ptr_at<T>(ctx: &XdpContext, offset: usize) -> Result<*const T, u16> {
    let start = ctx.data();
    let end = ctx.data_end();
    let len = size_of::<T>();

    if start + offset + len > end {
        return Err(1);
    }

    Ok((start + offset) as *const T)
}

Quelques remarques sur la création de cette fonction :

  • (L3) On inline la fonction pour des questions de performance
  • (L4) <T> : permet de créer une fonction générique non dépendante de la structure
  • l’offset permet de naviguer d’en-tête en en-tête (offset = 0 pour ethHdr)
  • (L7) On a dû changer la méthode pour calculer la longueur de la structure avec un size_of pour que ça soit possible pour n’importe quelle structure

Le code principal devient alors :

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    let ethhdr: *const EthHdr = ptr_at(&ctx, 0)?;

    let dst_addr = unsafe { (*ethhdr).dst_addr};

    info!(&ctx, "dst_addr {:mac} ", dst_addr);

    info!(&ctx, "received a packet");
    Ok(xdp_action::XDP_PASS)
}

Maintenant qu’on a créé la fonction permettant de se balader dans les différentes en-têtes, regardons comment on fait pour bloquer ou laisser passer les différents paquets en fonction des en-têtes réseaux.


Visite guidée des différentes en-têtes
#

headers

Niveau 1 : Accès réseau
#

Ce qu’on peut filtrer
#

On l’a déjà vu pour le premier niveau : on peut notamment récupérer l’adresse MAC source, l’adresse MAC destination et l’EtherType. On peut ainsi filtrer en fonction de l’adresse MAC ou si on veut uniquement de l’IPv4 par exemple.

ethhdr

Documentation ethhdr

On va donc utiliser la fonction créée précédemment ptr_at() :

let ethhdr: *const EthHdr = ptr_at(&ctx, 0)?;

Pour récupérer les adresses MAC et pour les afficher :

let dst_addr = unsafe { (*ethhdr).dst_addr };
let src_addr = unsafe { (*ethhdr).src_addr };

info!(&ctx, "src: {:mac} => dst: {:mac}", src_addr, dst_addr);

Testons :

cargo run

Et pingons. regardons le résultat :

[INFO  browser_xdp] src: de:ad:be:ef:00:10 => dst: de:ad:be:ef:00:01
[INFO  browser_xdp] src: de:ad:be:ef:00:10 => dst: de:ad:be:ef:00:01

namepaces with xdp program

Pour coder un peu plus propre, on peut créer une fonction :

#[inline(always)]
fn display_ethhdr(ctx: &XdpContext, ethhdr: *const EthHdr) {
    let dst_addr = unsafe { (*ethhdr).dst_addr };
    let src_addr = unsafe { (*ethhdr).src_addr };
    let ethertype = unsafe { (*ethhdr).ether_type };
    info!(ctx, "EthHdr: src: {:mac} => dst: {:mac} ({})", src_addr, dst_addr, ethertype);
}

Filtrons
#

Ainsi pour filtrer une adresse MAC, on peut faire :

let mac: [u8; 6] = [0xde, 0xad, 0xbe, 0xef, 0x00, 0x10]; //de:ad:be:ef:00:10

if mac == src_addr {
    info!(&ctx, "drop");
    return Ok(xdp_action::XDP_DROP);
}
Dans un “vrai” projet, on ne hard-coderait pas. On créerait une map eBPF qu’on remplirait dans l’espace utilisateur avec une liste d’adresses MAC à filtrer. On a déjà vu un cas similaire dans la section dédiée aux map eBPF de l’initiation.

Testons :

cargo run

Pingons depuis lb : ça ne ping plus. Regardons le résultat au niveau d’Aya :

[INFO  browser_xdp] drop
[INFO  browser_xdp] drop

Le programme repère bien le paquet et l’arrête.

namepaces with xdp program

Pingons depuis le namespace h2 :

ip netns exec h2 ping -c 2 10.0.0.1

Le ping laisse bien passer :

[INFO  browser_xdp] received ethhdr packet src: de:ad:be:ef:00:02 => dst: de:ad:be:ef:00:01
[INFO  browser_xdp] received ethhdr packet src: de:ad:be:ef:00:02 => dst: de:ad:be:ef:00:01

Pour ressembler plus à xdp-filter, on peut créer cette fonction qui permet de distinguer si on veut empêcher la source ou la destination:

enum Mode {
    Src,
    Dst,
}

#[inline(always)]
fn drop_mac(ethhdr: *const EthHdr, mac: [u8; 6], mode: Mode) -> bool {
    let addr = match mode {
        Mode::Src => unsafe {(*ethhdr).src_addr},
        Mode::Dst => unsafe {(*ethhdr).dst_addr},
    };

    mac == addr
}

Au niveau du code principal cela donne alors :

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    let ethhdr: *const EthHdr = ptr_at(&ctx, 0)?;
    let mac: [u8; 6] = [0xde, 0xad, 0xbe, 0xef, 0x00, 0x10]; //de:ad:be:ef:00:10
    display_ethhdr(&ctx, ethhdr);
    if drop_mac(ethhdr, mac, Mode::Src) {
       return Ok(xdp_action::XDP_DROP);
    }

    Ok(xdp_action::XDP_DROP);
}

Level up
#

L’Ethertype determine le protocole du paquet dans le niveau supérieur.

Ainsi pour le récupérer, on peut l’avoir de la manière suivante :

let ethertype = unsafe {(*ethhdr).ether_type} ;

Mais il y a plus propre avec la méthode ether_type() qui vérifie si le numéro du protocole existe vraiment :

Documentation EtherType

Au lieu de connaître par cœur les numéros de protocole, la crate network-types nous simplifie avec le type EtherType :

Documentation EtherType

Ainsi on filtre de cette manière :

match unsafe { (*ethhdr).ether_type() } {
        Ok(EtherType::Ipv4) => {}
        _ => return Ok(xdp_action::XDP_PASS),
}

Les flux autres que l’IPv4 (par exemple : IPv6, Arp) passent directement et ne sont plus analysés.

Niveau 2 : Internet
#

Ce qu’on peut filtrer
#

On va donc supposer qu’on veut analyser uniquement les flux IPv4.

De la même manière qu’Ethernet, regardons ce qu’on peut récupérer comme données avec l’en-tête IPv4 :

ethhdr

Pour la clareté, l’en-tête est représentée sur plusieurs lignes. Il y a nettement plus de choses à récupérer. ceux qu’on va utiliser pour cet article :

  • PROTOCOL : le protocole du niveau supérieur (TCP ou UDP par exemple)
  • SOURCE ADDRESS : l’adresse IP source
  • DESTINATION ADDRESS : l’adresse IP destination
Si vous trouvez que l’en-tête IPv4 est complexe, c’est une des raisons de la création d’IPv6 : son en-tête est un peu plus simple facilitant le traitement des paquets.

Au niveau Rust, on peut récupérer ces en-têtes :

Documentation Ipv4Hdr

Pour récupérer les en-têtes de l’IPv4, il suffit de décaler le pointeur à la longueur de la structure de l’en-tête Ethernet avec la fonction qu’on a créé :

let ipv4hdr: *const Ipv4Hdr = ptr_at(&ctx, EthHdr::LEN)? ;

De la même manière que pour la couche inférieure on peut créer une fonction :

#[inline(always)]
fn display_iphdr(ctx: &XdpContext, iphdr: *const Ipv4Hdr) {
    let dst_addr = unsafe { (*iphdr).dst_addr };
    let src_addr = unsafe { (*iphdr).src_addr };
    let proto = unsafe { (*iphdr).proto };
    info!(ctx, "Ipv4Hdr: src: {:i} => dst: {:i} ({})", src_addr, dst_addr, proto as u8);
}
Tout comme pour les adresses MAC, la macro info! nous simplifie la vie en convertissant un tableau de 4 octets en la notation d’IPv4 avec :i.

Testons le code suivant :

use network_types::{eth::{EthHdr,EtherType},
                    ip::Ipv4Hdr,
};

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    let ethhdr: *const EthHdr = ptr_at(&ctx, 0)?;
    match unsafe { (*ethhdr).ether_type() } {
        Ok(EtherType::Ipv4) => {}
        _ => return Ok(xdp_action::XDP_PASS),
    }
    let ipv4hdr: *const Ipv4Hdr = ptr_at(&ctx, EthHdr::LEN)? ;
    let mac: [u8; 6] = [0xde, 0xad, 0xbe, 0xef, 0x00, 0x10]; //de:ad:be:ef:00:10
    //display_ethhdr(&ctx, ethhdr);
    display_iphdr(&ctx, ipv4hdr);
    Ok(xdp_action::XDP_PASS)
}

Si on ping :

ip netns exec lb ping -c 2 10.0.0.1

Du côté de cargo run, cela donne :

[INFO  browser_xdp] Ipv4Hdr: src: 10.0.0.10 => dst: 10.0.0.1 (1)
[INFO  browser_xdp] Ipv4Hdr: src: 10.0.0.10 => dst: 10.0.0.1 (1)

namepaces with xdp program

De la même manière que l’adresse Mac, pour empêcher l’ip du lb, on peut utiliser cette fonction:

#[inline(always)]
fn drop_ip(iphdr: *const Ipv4Hdr, ip: [u8; 4], mode: Mode) -> bool {
    let addr = match mode {
        Mode::Src => unsafe {(*iphdr).src_addr},
        Mode::Dst => unsafe {(*iphdr).dst_addr},
    };

    ip == addr
}

Le code principal donne alors :

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    let ethhdr: *const EthHdr = ptr_at(&ctx, 0)?;
    match unsafe { (*ethhdr).ether_type() } {
        Ok(EtherType::Ipv4) => {}
        _ => return Ok(xdp_action::XDP_PASS),
    }
    let ipv4hdr: *const Ipv4Hdr = ptr_at(&ctx, EthHdr::LEN)? ;
    display_iphdr(&ctx, ipv4hdr);
    if drop_ip(ipv4hdr, [10, 0, 0, 10], Mode::Src) {
       return Ok(xdp_action::XDP_DROP);
    }
    Ok(xdp_action::XDP_PASS)
}

Le ping ne fonctionne plus :

ip netns exec lb ping -c 2 10.0.0.1
[INFO  browser_xdp] Ipv4Hdr: src: 10.0.0.10 => dst: 10.0.0.1 (1)
[INFO  browser_xdp] Ipv4Hdr: src: 10.0.0.10 => dst: 10.0.0.1 (1)

Le programme repère bien le paquet et l’arrête.

namepaces xdp

Level up
#

Pour aller au niveau supérieur, il faut savoir quel protocole on veut regarder. Pour cela on utilise l’option proto. Il est de type IPProto qui est une énumération :

Documentation Ipv4Hdr

Nous allons regarder UDP pour la fin de l’article. Le principe est le même que pour l’Ethertype :

match unsafe { (*ipv4hdr).proto } {
         IpProto::Udp => {}
         _ => return Ok(xdp_action::XDP_PASS),
}

On peut facilement filtrer si on veut bloquer certains protocoles.

Niveau 3 : Transport
#

Ce qu’on peut filtrer
#

Pour UDP, on peut ainsi récupérer l’en-tête de cette manière :

let udphdr: *const UdpHdr = ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN)? ;

Regardons maintenant l’en-tête du segment UDP :

udphdr

La partie la plus intéressante à filtrer sont les ports.

Au niveau Rust :

Documentation UdpHdr

Les ports ne sont pas des entiers mais des tableaux de 2 octets. Cela n’est pas pratique. Heureusement il y a les méthodes suivantes :

Documentation UdpHdr src port
Documentation UdpHdr dst port

On peut filtrer au niveau du port UDP :

#[inline(always)]
fn drop_udp_port(udphdr: *const UdpHdr, port: u16, mode: Mode) -> bool {
    let packet_port = match mode {
        Mode::Src => unsafe {(*udphdr).src_port()},
        Mode::Dst => unsafe {(*udphdr).dst_port()},
    };

    port == packet_port
}

Testons le code suivant :

use network_types::{eth::{EthHdr,EtherType},
                    ip::{Ipv4Hdr,IpProto},
                    udp::UdpHdr,
};

fn try_browser_xdp(ctx: XdpContext) -> Result<u32, u32> {
    let ethhdr: *const EthHdr = ptr_at(&ctx, 0)?;

    match unsafe { (*ethhdr).ether_type() } {
        Ok(EtherType::Ipv4) => {}
        _ => return Ok(xdp_action::XDP_PASS),
    }
    let ipv4hdr: *const Ipv4Hdr = ptr_at(&ctx, EthHdr::LEN)? ;

    match unsafe { (*ipv4hdr).proto } {
         IpProto::Udp => {}
         _ => return Ok(xdp_action::XDP_PASS),
    }

    let udphdr: *const UdpHdr = ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN)? ;
    display_udphdr(&ctx, udphdr);
    if drop_udp_port(udphdr, 8888, Mode::Dst){
        return Ok(xdp_action::XDP_DROP);
    }
    Ok(xdp_action::XDP_PASS)
}

Installons le programme XDP :

cargo run

Créons un serveur UDP avec netcat :

nc -u 0.0.0.0 -l 8000

Créons un client udp maintenant :

ip netns exec lb netcat -u 10.0.0.1 8000
coucou

Cette commande ouvre un client UDP et envoie le message coucou.

Du côté sur le serveur UDP, on voit bien :

coucou

Mais surtout, on voit bien du côté cargo run :

[INFO  browser_xdp] UdpHdr: src: 44024 => dst: 8000

Testons maintenant avec un serveur UDP avec le port 8888 :

nc -u 0.0.0.0 -l 8888

Créons un client udp maintenant :

ip netns exec lb netcat -u 10.0.0.1 8888
coucou

Il ne s’affiche rien au niveau du serveur c’est normal le programme XDP l’a bloqué :

[INFO  browser_xdp] UdpHdr: src: 39540 => dst: 8888
Les fonctions drop_mac(), drop_ip() et drop_udp_port() sont très proches il est tout à fait possible en Rust de les rendre générique pour qu’il y en ait qu’une. De même pour display_ethdr(), display_iphdr() et display_udphdr(). Vous pouvez voir le code plus générique au niveau de la crate dédiée au blog ici et .

Niveau 4 : Application
#

La crate network-types n’aide pas pour récupérer les en-têtes des protocoles applicatifs. En connaissant le port du niveau précédent, on peut facilement filtrer l’application. Si tu filtres le port 53 tu vas filtrer le DNS par exemple. Mais si tu veux filtrer une requête spécifique, c’est une autre affaire.

curl command to retrieve http headers

C’est possible d’aller plus haut. Mais les en-têtes des applications peuvent être :

  • trop importantes pour la stack (limité à 512 octets), il faudrait obligatoirement passé par des maps eBPF ;
  • chiffrées comme pour HTTPS ou SSH ce qui rend l’analyse impossible avec XDP.

Cet épisode est maintenant terminé ! Nous avons vu les bases pour créer un firewall comme xdp-filter.

Cependant, nous n’avons pas vu comment faire pour qu’il soit un peu plus intelligent en évitant par exemple les DDoS.

Ça tombe bien ! Dans le prochain épisode, nous allons créer un programme XDP qui va atténuer les dénis de service.

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