bpf(2) | System Calls Manual | bpf(2) |
bpf - Lancer une commande sur une mappe ou un programme BPF
#include <linux/bpf.h>
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
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.
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 |--
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 :
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)));
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 :
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.
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)); }
bpf_map_lookup_elem(map_fd, fp - 4)
bpf_map_lookup_elem(map_fd, void *key)
value = bpf_map_lookup_elem(...); *(u32 *) value = 1;
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. */ };
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)); }
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)); }
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)); }
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)); }
Les types de mappe suivants sont pris en charge :
void bpf_tail_call(void *context, void *prog_map, unsigned int index);
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 :
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.
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_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 */
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);
Pour qu'un appel réussisse, le code de retour dépend de l'opération :
En cas d'erreur, la valeur de retour est -1 et errno est définie pour préciser l'erreur.
L'appel système bpf() est apparu pour la première fois dans Linux 3.18.
L'appel système bpf() est spécifique à Linux.
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 :
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 :
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 :
/* 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.
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.
La traduction française de cette page de manuel a été créée par Christophe Blaess <https://www.blaess.fr/christophe/>, Stéphan Rafin <stephan.rafin@laposte.net>, Thierry Vignaud <tvignaud@mandriva.com>, François Micaux, Alain Portal <aportal@univ-montp2.fr>, Jean-Philippe Guérard <fevrier@tigreraye.org>, Jean-Luc Coulon (f5ibh) <jean-luc.coulon@wanadoo.fr>, Julien Cristau <jcristau@debian.org>, Thomas Huriaux <thomas.huriaux@gmail.com>, Nicolas François <nicolas.francois@centraliens.net>, Florentin Duneau <fduneau@gmail.com>, Simon Paillard <simon.paillard@resel.enst-bretagne.fr>, Denis Barbier <barbier@debian.org>, David Prévot <david@tilapin.org>, Cédric Boutillier <cedric.boutillier@gmail.com>, Frédéric Hantrais <fhantrais@gmail.com> et Jean-Philippe MENGUAL <jpmengual@debian.org>
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 à debian-l10n-french@lists.debian.org.
5 février 2023 | Pages du manuel de Linux 6.03 |