NOM

bpf - Lancer une commande sur une mappe ou un programme BPF

SYNOPSIS

#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);

DESCRIPTION

L'appel système bpf() effectue une série d'opérations liées aux Berkeley Packet Filters étendus (« filtres de paquets Berkeley »). BPF étendu (ou eBPF) est identique au BPF « classique » originel (cBPF) utilisé pour filtrer les paquets réseau. Pour les programmes tant cBPF qu’eBPF, le noyau analyse de manière statique les programmes avant de les charger, afin de garantir qu'ils ne puissent pas mettre en danger le système en cours d’exécution.
eBPF étend cBPF de plusieurs manières, notamment par la possibilité d'appeler un ensemble fixé de fonctions d'aide du noyau (à l’aide de l'extension d’opcode BPF_CALL fournie par eBPF) et d'accéder aux structures de données partagées telles que les mappes eBPF.

Conception/architecture de BPF étendu

Les mappes eBPF sont des structures de données génériques pour stocker différents types de données. Les types de données sont généralement traités comme des blobs binaires, donc l'utilisateur indique seulement la taille de la clé et celle de la valeur au moment de la création de la mappe. En d'autres termes, la clé/valeur d'une mappe donnée peut avoir une structure arbitraire.
Un processus utilisateur peut créer plusieurs mappes (dont les paires clé/valeur sont des octets de données opaques) et y accéder par les descripteurs de fichier. Différents programmes eBPF peuvent accéder aux mêmes mappes en parallèle. Il appartient au processus utilisateur et au programme eBPF de décider ce qu'ils stockent dans leurs mappes.
Il existe un type de mappe spécial appelé un tableau de programmes (« program array »). Ce type de mappe stocke des descripteurs de fichiers qui renvoient à d'autres programmes eBPF. Quand une recherche est effectuée sur la mappe, le flux du programme est redirigé directement au début d'un autre programme eBPF et il ne renvoie rien au programme appelant. Le niveau de redirections est de 32 pour éviter de créer des boucles infinies. Au moment de l'exécution, les descripteurs de fichier du programme stockés dans la mappe peuvent être modifiés, donc la fonctionnalité de programme peut être modifiée sur la base d'exigences spécifiques. Tous les programmes auxquels renvoie une mappe tableau de programmes (program-array) doivent avoir été précédemment chargés dans le noyau avec bpf(). Si une recherche de mappe échoue, le programme en cours poursuit son exécution. Voir BPF_MAP_TYPE_PROG_ARRAY ci-dessous pour des détails.
Généralement, les programmes eBPF sont chargés par le processus de l'utilisateur et déchargés automatiquement quand le processus se termine. Dans certains cas, par exemple tc-bpf(8), le programme restera en vie dans le noyau même après que le processus qui l'a chargé est fini. Dans ce cas, le sous-système tc garde une référence au programme eBPF après que le descripteur de fichier est fermé par le programme de l'espace utilisateur. Ainsi, la survie d'un programme spécifique dans le noyau dépend de la manière dont il a été rattaché à un sous-système donné du noyau après qu'il a été chargé par bpf().
Chaque programme eBPF est un ensemble d'instructions qu'on peut exécuter en sécurité jusqu'à leur fin. Un vérificateur interne au noyau détermine de manière statique ce que le programme eBPF interrompt et s'il peut être exécuté en toute sécurité. Pendant la vérification, le noyau ajoute un numéro de référence de manière incrémentale pour chacune des mappes utilisées par le programme eBPF, si bien que les mappes qui y sont rattachées ne peuvent pas être supprimées avant que le programme soit déchargé.
Les programmes eBPF peuvent être rattachés à différents événements. Ces événements peuvent être l'arrivée de paquets réseaux, le traçage d'événements, la classification d'événements en disciplines de files d'attente réseau (pour les programmes eBPF rattachés à un classificateur tc(8)), et d'autres types qui pourront être ajoutés dans le futur. Un nouvel événement provoque l'exécution d'un programme eBPF, qui peut stocker des informations sur l’évènement dans des mappes eBPF. Par-delà les données stockées, les programmes eBPF peuvent appeler un ensemble fixé de fonctions d'aide internes au noyau.
Un même programme eBPF peut être rattaché à plusieurs événements (évt) et divers programmes eBPF peuvent accéder à la même mappe :

traçage     traçage    traçage    paquet       paquet      paquet
 évt A       évt B      évt C    sur eth0     sur eth1    sur eth2
  |             |         |          |           |          ^
  |             |         |          |           v           |
  --> traçage <--      traçage     socket    tc ingress   tc egress
       prog_1          prog_2      prog_3    classifieur   action
       |  |              |           |         prog_4      prog_5
    |---  -----|  |------|         mappe_3        |           |
 mappe_1     mappe_2                             --| mappe_4 |--

Arguments

L'opération à effectuer par l'appel système bpf() est déterminée par le paramètre cmd. Chaque opération prend un paramètre, fourni par attr, qui est un pointeur vers une union de type bpf_attr (voir ci-dessous). Les champs inutilisés et de remplissage doivent être mis à zéro avant l'appel. Le paramètre size est la taille de l'union vers laquelle pointe attr.
La valeur fournie dans cmd est une parmi :
BPF_MAP_CREATE
Créer une mappe et renvoyer un descripteur de fichier qui s'y rapporte. Le drapeau de descripteur de fichier close-on-exec (voir fcntl(2)) est automatiquement activé pour le nouveau descripteur de fichier.
BPF_MAP_LOOKUP_ELEM
Chercher un élément par clé dans une mappe spécifiée et renvoyer sa valeur.
BPF_MAP_UPDATE_ELEM
Créer ou mettre à jour un élément (paire clé/valeur) dans une mappe spécifiée.
BPF_MAP_DELETE_ELEM
Chercher et effacer un élément par clé dans une mappe spécifiée.
BPF_MAP_GET_NEXT_KEY
Chercher un élément par clé dans une mappe spécifiée et renvoyer la clé de l'élément suivant.
BPF_PROG_LOAD
Vérifier et charger un programme eBPF, en renvoyant un nouveau descripteur de fichier associé au programme. Le drapeau de descripteur de fichier close-on-exec (voir fcntl(2)) est activé automatiquement pour le nouveau descripteur de fichier.
L'union bpf_attr consiste dans diverses structures anonymes utilisées par différentes commandes bpf() :

union bpf_attr {
    struct {    /* Utilisé par BPF_MAP_CREATE */
        __u32         map_type;
        __u32         key_size;    /* taille de la clé en octets */
        __u32         value_size;  /* taille de la valeur en octets */
        __u32         max_entries; /* nombre maximal d'entrées
                                      dans une mappe */
    };
struct { /* Utilisé par les commandes BPF_MAP_*_ELEM et BPF_MAP_GET_NEXT_KEY */ __u32 map_fd; __aligned_u64 key; union { __aligned_u64 value; __aligned_u64 next_key; }; __u64 flags; };
struct { /* Used by BPF_PROG_LOAD */ __u32 prog_type; __u32 insn_cnt; __aligned_u64 insns; /* 'const struct bpf_insn *' */ __aligned_u64 license; /* 'const char *' */ __u32 log_level; /* niveau de bavardage du vérificateur */ __u32 log_size; /* taille du tampon utilisateur */ __aligned_u64 log_buf; /* l'utilisateur a fourni 'char *' de tampon */ __u32 kern_version; /* vérifier quand prog_type=kprobe (depuis Linux 4.1) */ }; } __attribute__((aligned(8)));

mappes eBPF

Les mappes sont des structures de données génériques pour stocker différents types de données. Elles permettent de partager des données entre des programmes eBPF du noyau, mais aussi entre les applications du noyau et de l'espace utilisateur.
Chaque type de mappe a les attributs suivants :
type
nombre maximal d'éléments
taille de la clé en octets
valeur de la clé en octets
Les fonctions enveloppe suivantes montrent la manière dont diverses commandes bpf() peuvent être utilisées pour accéder aux mappes. Les fonctions utilisent le paramètre cmd pour appeler différentes opérations.
BPF_MAP_CREATE
La commande BPF_MAP_CREATE crée une nouvelle mappe, renvoyant un nouveau descripteur de fichier qui s'y rapporte.

int
bpf_create_map(enum bpf_map_type map_type,
               unsigned int key_size,
               unsigned int value_size,
               unsigned int max_entries)
{
    union bpf_attr attr = {
        .map_type    = map_type,
        .key_size    = key_size,
        .value_size  = value_size,
        .max_entries = max_entries
    };
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); }

La nouvelle mappe possède le type indiqué avec map_type et les attributs indiqués dans key_size, value_size et max_entries. En cas de succès, cette opération renvoie un descripteur de fichier. En cas d'erreur, -1 est renvoyé et errno est positionné sur EINVAL, EPERM ou ENOMEM.
Les attributs key_size et value_size seront utilisés par le vérificateur lors du chargement du programme pour vérifier que le programme appelle les fonctions d'aide bpf_map_*_elem() avec une key correctement initialisée et pour vérifier que le programme n'accède pas à une value de l'élément de la mappe au-delà de la value_size indiquée. Par exemple, quand une mappe est créée avec key_size de 8 et que le programme eBPF appelle un

bpf_map_lookup_elem(map_fd, fp - 4)
    

le programme sera rejeté, puisque la fonction d'aide du noyau

bpf_map_lookup_elem(map_fd, void *key)
    

s'attend à lire 8 octets à l'endroit où pointe key, mais l'adresse de départ fp - 4 (où fp est le haut de la pile) crée un accès de la pile hors limites.
De même, lorsqu'une mappe est créée avec une value_size de 1 et que le programme eBPF contient

value = bpf_map_lookup_elem(...);
*(u32 *) value = 1;
    

le programme sera rejeté puisqu'il accède au pointeur value au-delà de la la limite value_size d’un octet spécifiée.
Actuellement, les valeurs suivantes sont prises en charge par map_type :

enum bpf_map_type {
    BPF_MAP_TYPE_UNSPEC,  /* Réserver 0 comme type de mappe non valable */
    BPF_MAP_TYPE_HASH,
    BPF_MAP_TYPE_ARRAY,
    BPF_MAP_TYPE_PROG_ARRAY,
    BPF_MAP_TYPE_PERF_EVENT_ARRAY,
    BPF_MAP_TYPE_PERCPU_HASH,
    BPF_MAP_TYPE_PERCPU_ARRAY,
    BPF_MAP_TYPE_STACK_TRACE,
    BPF_MAP_TYPE_CGROUP_ARRAY,
    BPF_MAP_TYPE_LRU_HASH,
    BPF_MAP_TYPE_LRU_PERCPU_HASH,
    BPF_MAP_TYPE_LPM_TRIE,
    BPF_MAP_TYPE_ARRAY_OF_MAPS,
    BPF_MAP_TYPE_HASH_OF_MAPS,
    BPF_MAP_TYPE_DEVMAP,
    BPF_MAP_TYPE_SOCKMAP,
    BPF_MAP_TYPE_CPUMAP,
    BPF_MAP_TYPE_XSKMAP,
    BPF_MAP_TYPE_SOCKHASH,
    BPF_MAP_TYPE_CGROUP_STORAGE,
    BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
    BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
    BPF_MAP_TYPE_QUEUE,
    BPF_MAP_TYPE_STACK,
    /* Voir /usr/include/linux/bpf.h pour la liste complète. */
};
    

map_type sélectionne une des implémentations de mappe disponibles dans le noyau. Pour tous les types de mappe, les programmes eBPF accèdent aux mappes avec les mêmes fonctions d'aide bpf_map_lookup_elem() et bpf_map_update_elem(). Vous trouverez ci-dessous plus de détails sur les différents types de mappes.
BPF_MAP_LOOKUP_ELEM
La commande BPF_MAP_LOOKUP_ELEM cherche un élément avec une key donnée dans la mappe à laquelle se rapporte le descripteur de fichier fd.

int
bpf_lookup_elem(int fd, const void *key, void *value)
{
    union bpf_attr attr = {
        .map_fd = fd,
        .key    = ptr_to_u64(key),
        .value  = ptr_to_u64(value),
    };
return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); }

Si un élément est trouvé, l'opération renvoie zéro et stocke la valeur de l'élément dans value, qui doit pointer vers un tampon de value_size octets.
Si aucun élément n'est trouvé, l'opération renvoie -1 et errno est positionné sur ENOENT.
BPF_MAP_UPDATE_ELEM
La commande BPF_MAP_UPDATE_ELEM crée ou met à jour un élément avec une key/value donnée dans la mappe à laquelle se rapporte le descripteur de fichier fd.

int
bpf_update_elem(int fd, const void *key, const void *value,
                uint64_t flags)
{
    union bpf_attr attr = {
        .map_fd = fd,
        .key    = ptr_to_u64(key),
        .value  = ptr_to_u64(value),
        .flags  = flags,
    };
return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); }

Le paramètre flags devrait être formé d'une des manières suivantes :
BPF_ANY
Créer un nouvel élément ou mettre à jour un élément existant.
BPF_NOEXIST
Créer un nouvel élément seulement s'il n'existe pas.
BPF_EXIST
Mettre à jour un élément existant.
En cas de succès, l'opération renvoie zéro. En cas d'erreur, -1 est renvoyé et errno est positionné sur EINVAL, EPERM, ENOMEM ou E2BIG. E2BIG indique que le nombre d'éléments de la mappe a atteint la limite max_entries spécifiée au moment de la création de la mappe. EEXIST sera renvoyé si flags spécifie BPF_NOEXIST et si l'élément contenant key existe déjà sur la mappe. ENOENT sera renvoyé si flags spécifie BPF_EXIST et si l'élément contenant key n'existe pas sur la mappe.
BPF_MAP_DELETE_ELEM
La commande BPF_MAP_DELETE_ELEM efface l'élément dont la clé est key sur la mappe à laquelle se rapporte le descripteur de fichier fd.

int
bpf_delete_elem(int fd, const void *key)
{
    union bpf_attr attr = {
        .map_fd = fd,
        .key    = ptr_to_u64(key),
    };
return bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); }

S'il réussit, cet appel système renvoie 0. Si l'élément n'est pas trouvé, -1 est renvoyé et errno est positionné sur ENOENT.
BPF_MAP_GET_NEXT_KEY
La commande BPF_MAP_GET_NEXT_KEY recherche un élément par key sur la mappe à laquelle se réfère le descripteur de fichier fd et elle définit le pointeur next_key vers la clé du prochain élément.

int
bpf_get_next_key(int fd, const void *key, void *next_key)
{
    union bpf_attr attr = {
        .map_fd   = fd,
        .key      = ptr_to_u64(key),
        .next_key = ptr_to_u64(next_key),
    };
return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr)); }

Si key est trouvée, l'opération renvoie zéro et next_key pointe vers la clé de l'élément suivant. Si key n'est pas trouvée, l'opération renvoie zéro et next_key pointe vers la clé du premier élément. Si key est le dernier élément, -1 est renvoyé et errno est positionné sur ENOENT. Les autres valeurs possibles de errno sont ENOMEM, EFAULT, EPERM et EINVAL. Cette méthode peut être utilisée pour itérer entre tous les éléments d'une mappe.
close(map_fd)
Effacer la mappe à laquelle se réfère le descripteur de fichier map_fd. Quand le programme de l'espace utilisateur ayant créé la mappe se termine, toutes les mappes sont effacées automatiquement (mais voir REMARQUES).

Types de mappe eBPF

Les types de mappe suivants sont pris en charge :
BPF_MAP_TYPE_HASH
Les mappes table de hachage (hash-table) présentent les caractéristiques suivantes :
Les mappes sont créées et détruites par les programmes dans l'espace utilisateur. Tant les programmes eBPF que ceux de l'espace utilisateur peuvent effectuer des opérations de recherche, de mise à jour et d'effacement.
Le noyau se charge d'allouer et de libérer les paires clé/valeur.
L'aide map_update_elem() échouera si vous insérez un nouvel élément quand la limite max_entries est atteinte (cela garantit que les programmes eBPF ne peuvent pas épuiser la mémoire).
map_update_elem() remplace atomiquement les éléments existants.
Les mappes table de hachage (hash-table) sont optimisées pour accélérer la recherche.
BPF_MAP_TYPE_ARRAY
Les mappes tableau (array) présentent les caractéristiques suivantes :
Elles sont optimisées pour une recherche plus rapide. À l'avenir, le compilateur du vérificateur/JIT pourrait reconnaître les opérations lookup() qui utilisent une clé constante et l'optimiser dans un pointeur constant. Il est également possible d'optimiser une clé non constante dans un pointeur arithmétique direct, car les pointeurs et les value_size sont constants durant toute la vie des programmes eBPF. En d'autres termes, array_map_lookup_elem() peut être mise « inline » par le compilateur du vérificateur/JIT tout en préservant l'accès concurrent à cette mappe à partir de l'espace utilisateur.
Tous les éléments du tableau sont préalloués et initialisés à zéro au moment de l'initialisation
La clé est un indice de tableau et doit être exactement de quatre octets.
map_delete_elem() échoue avec l'erreur EINVAL, car les éléments ne peuvent pas être effacés.
map_update_elem() remplace les éléments de manière non atomique ; pour des mises à jour atomiques, vous devriez plutôt utiliser une mappe table de hachage (hash-table). Toutefois, il existe un cas particulier qui peut aussi être utilisé avec les tableaux : le __sync_fetch_and_add() interne atomique peut être utilisé sur des compteurs atomiques en 32 ou 64 bits. Par exemple, il peut s'appliquer sur la valeur entière si elle représente un compteur unique ou, si une structure contient plusieurs compteurs, il pourrait être utilisé sur des compteurs individuels. Cela est très souvent utile pour agréger et compter des événements.
Voici quelques cas d'usage des mappes tableau (array) :
Sous forme de variables eBPF « globales » : un tableau d’un élément dont la clé (indice) est 0 et dont la valeur est un ensemble de variables « globales » que les programmes eBPF peuvent utiliser pour conserver leur état entre les événements.
Agrégation d'événements de traçage dans un ensemble fixe de « buckets ».
Comptabilité des événements réseaux, par exemple le nombre de paquets et leur taille.
BPF_MAP_TYPE_PROG_ARRAY (depuis Linux 4.2)
Une mappe tableau de programmes est un type spécial de mappe tableau dont les valeurs ne contiennent que des descripteurs de fichier qui se rapportent à d'autres programmes eBPF. Ainsi, tant key_size que value_size doivent être d'exactement quatre octets. Cette mappe est utilisée en association avec l'aide bpf_tail_call().
Cela signifie qu'un programme eBPF auquel est rattaché un tableau de programmes (program array) peut appeler à partir du noyau

void bpf_tail_call(void *context, void *prog_map,
                   unsigned int index);
    

et donc remplacer le flux de son propre programme par celui du programme sur la tranche du tableau de programmes donné s'il y en a un. Vous pouvez considérer cela comme un saut de tableau vers un autre programme eBPF. Le programme appelé réutilisera ensuite la même pile. Quand un saut vers un nouveau programme a été fait, il ne renverra plus à l'ancien programme.
Si aucun programme eBPF n'est trouvé sur l'indice donné du tableau de programmes (car la tranche de la mappe ne contient pas de descripteur de fichier de programme valable, la recherche d'indice/clé indiquée dépasse la plage ou la limite de 32 appels en interne a été dépassée), l'exécution continue avec le programme eBPF actuel. Cela peut être utilisé comme solution de repli pour les cas par défaut.
Une mappe tableau de programmes sert, par exemple, à tracer ou mettre en réseau, à gérer des appels système individuels ou des protocoles dans leurs propres sous-programmes et à utiliser leurs identifiants comme identifiant individuel de mappe. Cette approche peut apporter des gains de performance et permet de dépasser la limite du nombre d'instructions d'un programme eBPF. Dans des environnements dynamiques, un démon de l'espace utilisateur pourrait remplacer de manière atomique des sous-programmes au moment de leur exécution par de nouvelles versions, pour modifier le comportement général d'un programme, par exemple, si les règles globales changent.

Programmes eBPF

La commande BPF_PROG_LOAD est utilisée pour charger un programme eBPF dans le noyau. Le code de retour de cette commande est un nouveau descripteur de fichier associé à ce programme eBPF.

char bpf_log_buf[LOG_BUF_SIZE];
int bpf_prog_load(enum bpf_prog_type type, const struct bpf_insn *insns, int insn_cnt, const char *license) { union bpf_attr attr = { .prog_type = type, .insns = ptr_to_u64(insns), .insn_cnt = insn_cnt, .license = ptr_to_u64(license), .log_buf = ptr_to_u64(bpf_log_buf), .log_size = LOG_BUF_SIZE, .log_level = 1, };
return bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); }

prog_type est un des types de programme suivants :

enum bpf_prog_type {
    BPF_PROG_TYPE_UNSPEC,        /* Réserver 0 comme type de programme
                                    non valable */
    BPF_PROG_TYPE_SOCKET_FILTER,
    BPF_PROG_TYPE_KPROBE,
    BPF_PROG_TYPE_SCHED_CLS,
    BPF_PROG_TYPE_SCHED_ACT,
    BPF_PROG_TYPE_TRACEPOINT,
    BPF_PROG_TYPE_XDP,
    BPF_PROG_TYPE_PERF_EVENT,
    BPF_PROG_TYPE_CGROUP_SKB,
    BPF_PROG_TYPE_CGROUP_SOCK,
    BPF_PROG_TYPE_LWT_IN,
    BPF_PROG_TYPE_LWT_OUT,
    BPF_PROG_TYPE_LWT_XMIT,
    BPF_PROG_TYPE_SOCK_OPS,
    BPF_PROG_TYPE_SK_SKB,
    BPF_PROG_TYPE_CGROUP_DEVICE,
    BPF_PROG_TYPE_SK_MSG,
    BPF_PROG_TYPE_RAW_TRACEPOINT,
    BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
    BPF_PROG_TYPE_LWT_SEG6LOCAL,
    BPF_PROG_TYPE_LIRC_MODE2,
    BPF_PROG_TYPE_SK_REUSEPORT,
    BPF_PROG_TYPE_FLOW_DISSECTOR,
    /* Voir /usr/include/linux/bpf.h pour la liste complète. */
};
    

Pour plus de détails sur le type de programme eBPF, voir ci-dessous.
Les autres champs de bpf_attr sont définis comme suit :
insns est un tableau d'instructions struct bpf_insn.
insn_cnt est le nombre d'instructions du programme auquel se rapporte insns.
license est une chaîne de licence, qui doit être compatible GPL pour appeler les fonctions d'aide marquées comme gpl_only (les règles de licence sont les mêmes que celles pour les modules du noyau, pour que même des licences duales, telles que « Dual BSD/GPL », puissent être utilisées).
log_buf est un pointeur vers un tampon alloué à l’appelant (caller-allocated) où le vérificateur du noyau peut stocker le journal de sa vérification. Ce journal est une chaîne de plusieurs lignes qui peut être vérifiée par l'auteur du programme pour comprendre la manière par laquelle le vérificateur est arrivé à la conclusion que le programme eBPF n'est pas sûr. Le format de sortie peut changer n'importe quand puisque le vérificateur évolue.
log_size dimensionne le tampon vers lequel pointe log_buf. Si la taille du tampon n'est pas assez grande pour stocker tous les messages du vérificateur, -1 est renvoyé et errno est positionné sur ENOSPC.
Le niveau de précisions log_level du vérificateur. Une valeur de zéro signifie que le vérificateur ne génèrera aucun journal ; dans ce cas log_buf doit être un pointeur NULL et log_size doit valoir zéro.
Le fait d'appliquer close(2) au descripteur de fichier renvoyé par BPF_PROG_LOAD déchargera le programme eBPF (mais voir les REMARQUES).
Les mappes sont accessibles à partir des programmes eBPF et elles sont utilisées pour échanger des données entre des programmes eBPF et entre des programmes eBPF et d'autres de l'espace utilisateur. Par exemple, des programmes eBPF peuvent traiter divers événements (comme kprobe, packets) et stocker leurs données dans une mappe, et les programmes de l'espace utilisateur peuvent alors récupérer ces données dans la mappe. Inversement, des programmes de l'espace utilisateur peuvent utiliser une mappe en tant que mécanisme de configuration, la mappe étant peuplée par des valeurs vérifiées par le programme eBPF qui modifie ensuite son comportement à la volée en fonction de ces valeurs.

Types de programme eBPF

Le type de programme eBPF ( prog_type) détermine le sous-ensemble de fonctions d'aide du noyau que peut appeler le programme. Le type de programme détermine également le format d'entrée du programme (contexte) – le format de struct bpf_context (qui est le blob de données passé au programme eBPF en tant que premier paramètre).
Par exemple, un programme de traçage n'a pas exactement le même sous-jeu de fonctions d'aide qu'un programme de filtrage de socket (bien qu'ils peuvent en avoir en commun). De même l'entrée (le contexte) d'un programme de traçage est un jeu de valeurs de registre, alors que ce sera un paquet réseau pour le filtrage de socket.
Le jeu de fonctions disponibles pour les programmes eBPF d'un type donné pourra augmenter dans le futur.
Les types de programmes suivants sont pris en charge :
BPF_PROG_TYPE_SOCKET_FILTER (depuis Linux 3.19)
Actuellement, le jeu de fonctions pour BPF_PROG_TYPE_SOCKET_FILTER est :

bpf_map_lookup_elem(map_fd, void *key)
                    /* rechercher la clé dans une map_fd */
bpf_map_update_elem(map_fd, void *key, void *value)
                    /* mettre à jour la clé/valeur */
bpf_map_delete_elem(map_fd, void *key)
                    /* effacer la clé d'une map_fd */
    

Le paramètre bpf_context est un pointeur vers une struct __sk_buff.
BPF_PROG_TYPE_KPROBE (depuis Linux 4.1)
[À documenter]
BPF_PROG_TYPE_SCHED_CLS (depuis Linux 4.1)
[À documenter]
BPF_PROG_TYPE_SCHED_ACT (depuis Linux 4.1)
[À documenter]

Événements

Une fois qu'un programme est chargé, il peut être rattaché à un événement. Divers sous-systèmes du noyau ont plusieurs manières de le faire.
Depuis Linux 3.19, l'appel suivant rattachera le programme prog_fd au socket sockfd, qui a été précédemment créé par un appel socket(2) :

setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_BPF,
           &prog_fd, sizeof(prog_fd));

Depuis Linux 4.1, l'appel suivant peut être utilisé pour rattacher un programme eBPF auquel se rapporte le descripteur de fichier prog_fd à un descripteur de fichier d'événement perf, event_fd, créé par un appel précédent à perf_event_open(2) :

ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);

VALEUR RENVOYÉE

Pour qu'un appel réussisse, le code de retour dépend de l'opération :
BPF_MAP_CREATE
Le nouveau descripteur de fichier associé à la mappe eBPF.
BPF_PROG_LOAD
Le nouveau descripteur de fichier associé au programme eBPF.
Toutes les autres commandes :
Zéro.
En cas d'erreur, la valeur de retour est -1 et errno est définie pour préciser l'erreur.

ERREURS

E2BIG
Le programme eBPF est trop grand ou une mappe a atteint la limite max_entries (nombre maximal d'éléments).
EACCES
Pour BPF_PROG_LOAD, même si toutes les instructions du programme sont valables, le programme a été rejeté car il a été considéré comme non sûr. Cela est possible s'il a eu un accès à une zone de la mémoire interdite ou à une pile ou un registre non initialisé, ou parce que les contraintes de la fonction ne correspondent pas aux types réels, ou qu'il y a eu un accès mémoire non aligné. Dans ce cas, il est recommandé d'appeler bpf() à nouveau, avec log_level = 1 et d'examiner le log_buf pour connaître la raison exacte fournie par le vérificateur.
EBADF
fd n'est pas un descripteur de fichier ouvert.
EFAULT
Un des pointeurs (key ou value ou log_buf ou insns) dépasse l'espace d'adressage accessible.
EINVAL
La valeur indiquée dans cmd n'est pas reconnue par ce noyau.
EINVAL
Pour BPF_MAP_CREATE, soit map_type, soit les attributs ne sont pas autorisés.
EINVAL
Pour des commandes BPF_MAP_*_ELEM, certains champs de union bpf_attr non utilisés par cette commande n'ont pas été positionnés sur zéro.
EINVAL
Pour BPF_PROG_LOAD, indique une tentative de charger un programme non valable. Les programmes eBPF peuvent être jugés non valables du fait d'instructions non reconnues, de l'utilisation de champs réservés, de dépassements de plage, de boucles infinies ou d'appels à des fonctions inconnues.
ENOENT
Pour BPF_MAP_LOOKUP_ELEM ou BPF_MAP_DELETE_ELEM, indique qu'un élément avec la key donnée n'a pas été trouvé.
ENOMEM
Ne peut pas allouer suffisamment de mémoire.
EPERM
L'appel a été fait sans privilèges suffisants (sans la capacité CAP_SYS_ADMIN).

VERSIONS

L'appel système bpf() est apparu pour la première fois dans Linux 3.18.

STANDARDS

L'appel système bpf() est spécifique à Linux.

NOTES

Avant Linux 4.4, toutes les commandes bpf() exigeaient que l'appelant ait la capacité CAP_SYS_ADMIN. Depuis Linux 4.4 jusqu'à présent, un utilisateur non privilégié peut créer des programmes limités de type BPF_PROG_TYPE_SOCKET_FILTER et mappes associées. Toutefois, ils ne peuvent pas stocker des pointeurs du noyau dans les mappes et ils sont actuellement limités aux fonctions d'aide suivantes :
get_random
get_smp_processor_id
tail_call
ktime_get_ns
Un accès sans privilèges peut être bloqué en écrivant la valeur 1 dans le fichier /proc/sys/kernel/unprivileged_bpf_disabled.
Les objets eBPF (les mappes et les programmes) peuvent être partagés entre les processus. Par exemple, après fork(2), l'enfant récupère les descripteurs de fichier qui se rapportent aux mêmes objets eBPF. De plus, les descripteurs de fichier qui se rapportent aux objets eBPF peuvent être transférés à travers des sockets de domaine UNIX. Les descripteurs de fichier qui se rapportent aux objets eBPF peuvent être dupliqués de la manière habituelle, en utilisant dup(2) ou des appels similaires. Un objet eBPF n'est désalloué qu'après que tous les descripteurs de fichier qui se rapportent à l'objet sont fermés.
Les programmes eBPF peuvent être écrits en C restreint compilé en bytecode eBPF (en utilisant le compilateur clang). Diverses fonctionnalités sont absentes de ce C restreint, telles que les boucles, les variables globales, les fonctions variadiques, les nombres décimaux et le passage de structures comme paramètres d'une fonction. Vous pouvez trouver des exemples dans les fichiers samples/bpf/*_kern.c de l'arborescence des sources du noyau.
Le noyau contient un compilateur « just-in-time (JIT) » qui traduit du bytecode eBPF en langage machine natif pour de meilleures performances. Avant Linux 4.15, le compilateur JIT est désactivé par défaut, mais ce qu'il fait peut être contrôlé en écrivant une des chaînes suivantes d’entiers dans le fichier /proc/sys/net/core/bpf_jit_enable :
0
Désactiver la compilation JIT (par défaut).
1
Compilation normale.
2
Mode débogage. Les opcodes générés sont écrits en hexadécimal dans le journal du noyau. Ces opcodes peuvent alors être désassemblés avec le programme tools/net/bpf_jit_disasm.c fourni dans l'arborescence des sources du noyau.
Depuis Linux 4.15, le noyau peut être configuré avec l'option CONFIG_BPF_JIT_ALWAYS_ON. Dans ce cas, le compilateur JIT est toujours activé et bpf_jit_enable est positionné sur 1 et immuable (cette option de configuration du noyau est fournie pour contrer une des attaques Spectre contre l'interpréteur BPF).
Le compilateur JIT pour eBPF est actuellement disponible pour les architectures suivantes :
x86-64 (depuis Linux 3.18 ; cBPF depuis Linux 3.0) ;
ARM32 (depuis Linux 3.18 ; cBPF depuis Linux 3.4) ;
SPARC 32 (depuis Linux 3.18 ; cBPF depuis Linux 3.5) ;
ARM-64 (depuis Linux 3.18) ;;
s390 (depuis Linux 4.1 ; cBPF depuis Linux 3.7) ;
PowerPC 64 (depuis Linux 4.8 ; cBPF depuis Linux 3.1) ;
SPARC 64 (depuis Linux 4.12) ;
x86-32 (depuis Linux 4.18) ;
MIPS 64 (depuis Linux 4.18 ; cBPF depuis Linux 3.16) ;
riscv (depuis Linux 5.1).

EXEMPLES

/* Exemple de bpf+sockets :
 * 1. Créer une mappe tableau de 256 éléments
 * 2. Charger le programme qui compte le nombre de paquets reçus
 *    r0 = skb->data[ETH_HLEN + offsetof(struct iphdr, protocol)]
 *    map[r0]++
 * 3. Rattacher prog_fd à la socket brut à l’aide de setsockopt()
 * 4. Afficher le nombre de paquets TCP/UDP reçus toutes les secondes
 */
int
main(int argc, char *argv[])
{
    int sock, map_fd, prog_fd, key;
    long long value = 0, tcp_cnt, udp_cnt;
map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 256); if (map_fd < 0) { printf("impossible de créer la projection '%s'\n", strerror(errno)); /* probablement non lancé en tant que root */ return 1; }
struct bpf_insn prog[] = { BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), /* r6 = r1 */ BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol)), /* r0 = ip->proto */ BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), /* r2 = fp */ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = r2 - 4 */ BPF_LD_MAP_FD(BPF_REG_1, map_fd), /* r1 = map_fd */ BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem), /* r0 = map_lookup(r1, r2) */ BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2), /* if (r0 == 0) goto pc+2 */ BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */ BPF_XADD(BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* lock *(u64 *) r0 += r1 */ BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */ BPF_EXIT_INSN(), /* return r0 */ };
prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog, sizeof(prog) / sizeof(prog[0]), "GPL");
sock = open_raw_sock("lo");
assert(setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd)) == 0);
for (;;) { key = IPPROTO_TCP; assert(bpf_lookup_elem(map_fd, &key, &tcp_cnt) == 0); key = IPPROTO_UDP; assert(bpf_lookup_elem(map_fd, &key, &udp_cnt) == 0); printf("TCP %lld UDP %lld packets\n", tcp_cnt, udp_cnt); sleep(1); }
return 0; }
Vous pouvez trouvez du code complet opérationnel dans le répertoire samples/bpf de l'arborescence des sources du noyau.

VOIR AUSSI

seccomp(2), bpf-helpers(7), socket(7), tc(8), tc-bpf(8)
Les BPF classique et étendu sont expliqués dans le fichier Documentation/networking/filter.txt des sources du noyau.

TRADUCTION

La traduction française de cette page de manuel a été créée par Christophe Blaess <https://www.blaess.fr/christophe/>, Stéphan Rafin <[email protected]>, Thierry Vignaud <[email protected]>, François Micaux, Alain Portal <[email protected]>, Jean-Philippe Guérard <[email protected]>, Jean-Luc Coulon (f5ibh) <[email protected]>, Julien Cristau <[email protected]>, Thomas Huriaux <[email protected]>, Nicolas François <[email protected]>, Florentin Duneau <[email protected]>, Simon Paillard <[email protected]>, Denis Barbier <[email protected]>, David Prévot <[email protected]>, Cédric Boutillier <[email protected]>, Frédéric Hantrais <[email protected]> et Jean-Philippe MENGUAL <[email protected]>
Cette traduction est une documentation libre ; veuillez vous reporter à la GNU General Public License version 3 concernant les conditions de copie et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.
Si vous découvrez un bogue dans la traduction de cette page de manuel, veuillez envoyer un message à [email protected]