bpf(2)

НАИМЕНОВАНИЕ
bpf - выполнение команды с расширенной картой или программой BPF

ОБЗОР
#include <linux/bpf.h>

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

ОПИСАНИЕ
Системный вызов bpf() выполняет ряд операций, связанных с дополнительными пакетными фильтрами Berkeley. Дополнительный BPF (или eBPF) аналогичен оригинальному ("классическому") BPF, используемому для фильтрации сетевых пакетов. Как для программ cBPF, так и для eBPF ядро статически анализирует программы перед их загрузкой, чтобы убедиться, что они не могут нанести вред работающей системе.

eBPF дополняет cBPF несколькими способами, включая возможность вызова фиксированного набора вспомогательных функций в ядре (с помощью дополнительного кода операции BPF_CALL, предоставляемого eBPF) и доступа к общим структурам данных, таким как карты eBPF.

  Дополнительная конструкция/архитектура BPF
Карты eBPF - это универсальная структура данных для хранения различных типов данных. Типы данных обычно обрабатываются как большие двоичные, поэтому пользователь просто указывает размер ключа и размер значения во время создания карты. Другими словами, ключ/значение для данной карты могут иметь произвольную структуру.

Пользовательский процесс может создавать несколько карт (с парами ключ/значение, представляющими собой непрозрачные байты данных) и получать к ним доступ через файловые дескрипторы. Различные программы eBPF могут параллельно получать доступ к одним и тем же картам.

Существует один особый тип карт, называемый программным массивом. Этот тип карты хранит файловые дескрипторы, относящиеся к другим программам eBPF. Когда выполняется поиск по карте, поток выполнения программы перенаправляется на месте к началу другой программы eBPF и не возвращается обратно к вызывающей программе. Уровень вложенности имеет фиксированное ограничение в 32, так что бесконечные циклы не могут быть созданы. Во время выполнения дескрипторы программных файлов, хранящиеся на карте, могут быть изменены, поэтому функциональность программы может быть изменена в зависимости от конкретных требований. Все программы, упомянутые в карте программных массивов, должны быть предварительно загружены в ядро с помощью функции bpf(). Если поиск по карте завершается неудачей, текущая программа продолжает свое выполнение. Смотрите ниже BPF_MAP_TYPE_PROG_ARRAY для получения более подробной информации.

В основном программы eBPF загружаются пользовательским процессом и автоматически выгружаются при завершении процесса. В некоторых случаях, например, tc-bpf(8), программа будет продолжать работать внутри ядра даже после завершения процесса, который загрузил программу. В этом случае подсистема tc хранит ссылку на программу eBPF после того, как файловый дескриптор был закрыт программой пользовательского пространства. Таким образом, будет ли конкретная программа продолжать работать в ядре, зависит от того, как она будет подключена к данной подсистеме ядра после того, как она была загружена через bpf().

Каждая программа eBPF представляет собой набор инструкций, которые безопасны для выполнения до ее завершения. Встроенный в ядро верификатор статически определяет, что программа eBPF остановлена и безопасна для выполнения. Во время проверки ядро увеличивает количество ссылок для каждой из карт, используемых программой eBPF, так что прикрепленные карты не могут быть удалены до тех пор, пока программа не будет выгружена.

Программы eBPF могут быть привязаны к различным событиям. Этими событиями могут быть прибытие сетевых пакетов, события трассировки, события классификации по правилам сетевого массового обслуживания (для программ eBPF, подключенных к классификатору tc(8)), и другие типы, которые могут быть добавлены в будущем. Новое событие запускает выполнение программы eBPF, которая может сохранять информацию об этом событии в картах eBPF. Помимо хранения данных, программы eBPF могут вызывать фиксированный набор встроенных в ядро вспомогательных функций.

Одна и та же программа eBPF может быть привязана к нескольким событиям и разные программы eBPF могут получить доступ к одной и той же карте:

    трассировка    трассировка    трассировка    пакет      пакет     пакет
    событие A     событие B    событие C    на eth0     на eth1    на eth2
     |             |         |          |           |          ^
     |             |         |          |           v          |
     --> трассировка <--     трассировка      сокет    вход tc   вход tc
          программа_1          программа_2      программа_3    классификатор    действие
          |  |              |           |         программа_4      программа_5
       |---  -----|  |------|          карта_3        |           |
     карта_1       карта_2                              --| карта_4 |--

  Аргументы
Операция, которая должна быть выполнена системным вызовом bpf(), определяется аргументом cmd. Каждая операция принимает сопутствующий аргумент, предоставляемый через attr, который является указателем на объединение типа bpf_attr (см. ниже). Неиспользуемые поля и отступы должны быть обнулены перед вызовом. Аргумент size - это размер объединения, на которое указывает attr.

Значение, указанное в cmd, может быть одним из следующих:

BPF_MAP_CREATE
    Создать карту и вернуть файловый дескриптор, который ссылается на карту. Флаг дескриптора файла close-on-exec (см. fcntl(2)) автоматически включается для нового дескриптора файла.

BPF_MAP_LOOKUP_ELEM
    Найти элемент по ключу на указанной карте и вернуть его значение.

BPF_MAP_UPDATE_ELEM
    Создать или обновить элемент (пару ключ/значение) на указанной карте.

BPF_MAP_DELETE_ELEM
    Найти и удалить элемент по ключу на указанной карте.

BPF_MAP_GET_NEXT_KEY
    Найти элемент по ключу в указанной карте и вернуть ключ следующего элемента.

BPF_PROG_LOAD
    Проверить и загрузить программу eBPF, которая вернет новый файловый дескриптор, связанный с программой. Флаг дескриптора файла close-on-exec (см. fcntl(2)) автоматически включается для нового дескриптора файла.

    Объединение bpf_attr состоит из различных анонимных структур, которые используются различными командами bpf():

    union bpf_attr {
        struct {    /* Used by BPF_MAP_CREATE */
            __u32         map_type;
            __u32         key_size;    /* size of key in bytes */
            __u32         value_size;  /* size of value in bytes */
            __u32         max_entries; /* maximum number of entries
                                          in a map */
        };

        struct {    /* Used by BPF_MAP_*_ELEM and BPF_MAP_GET_NEXT_KEY
                       commands */
            __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;  /* verbosity level of verifier */
            __u32         log_size;   /* size of user buffer */
            __aligned_u64 log_buf;    /* user supplied 'char *'
                                         buffer */
            __u32         kern_version;
                                      /* checked when prog_type=kprobe
                                         (since Linux 4.1) */
        };
    } __attribute__((aligned(8)));

  eBPF maps
Карты представляют собой универсальную структуру данных для хранения различных типов данных. Они позволяют обмениваться данными между программами ядра eBPF, а также между ядром и пользовательскими приложениями.

Каждый тип карты имеет следующие атрибуты:

•   тип

•   максимальное количество элементов

•   размер ключа в байтах

•   размер значения в байтах

Следующие функции-оболочки демонстрируют, как можно использовать различные команды bpf() для доступа к картам. Функции используют аргумент cmd для вызова различных операций.

BPF_MAP_CREATE
    Команда BPF_MAP_CREATE создает новую карту и возвращает новый файловый дескриптор, который ссылается на карту.

        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));
        }

    Новая карта имеет тип, указанный в параметре map_type и атрибуты, указанные в параметрах key_size, value_size и max_entries. В случае успеха эта операция возвращает файловый дескриптор. В случае ошибки возвращается значение -1, а для параметра errno устанавливается значение EINVAL, EPERM или ENOMEM.

    Атрибуты key_size и value_size будут использоваться верификатором во время загрузки программы для проверки того, что программа вызывает вспомогательные функции bpf_map_*_elem() с правильно инициализированным key и чтобы проверить, что программа не обращается к элементу карты c value за пределами размера, указанного в value_size. Например, когда создается карта с key_size равным 8 и программа eBPF вызывает

        bpf_map_lookup_elem(map_fd, fp - 4)

    программа будет отклонена, поскольку встроенная в ядро вспомогательная функция

        bpf_map_lookup_elem(map_fd, void *key)

    ожидает, что будет прочитано 8 байт из местоположения, на которое указывает key, но начальный адрес fp -4 (где fp - вершина стека) приведет к невозможности доступа к стеку.

    Аналогично, когда создается man с avalue_size равным 1 и программа bpf содержит

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

    программа будет отклонена, поскольку она обращается к указателю value за пределами указанного предела в 1 байт value_size.

    В настоящее время для map_type поддерживаются следующие значения:

        enum bpf_map_type {
            BPF_MAP_TYPE_UNSPEC,  /* Reserve 0 as invalid map type */
            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,
            /* See /usr/include/linux/bpf.h for the full list.  */
        };

    Параметр map_type выбирает одну из доступных реализаций карты в ядре. Для всех типов карт программы eBPF получают доступ к картам с помощью одних и тех же вспомогательных функций bpf_map_lookup_elem() и bpf_map_update_elem(). Более подробная информация о различных типах карт приведена ниже.

BPF_MAP_LOOKUP_ELEM
    Команда BPF_MAP_LOOKUP_ELEM выполняет поиск элемента с заданным значением key на карте, на которую ссылается файловый дескриптор 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));
        }

    Если элемент найден, операция возвращает ноль и сохраняет значение элемента в value, которое должно указывать на буфер с value_size байт.

    Если элемент не найден, операция возвращает значение -1 и присваивает errno значение ENOENT.

BPF_MAP_UPDATE_ELEM
    Команда BPF_MAP_UPDATE_ELEM создает или обновляет элемент с заданным значением key/value на карте, на которую ссылается файловый дескриптор 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));
        }

    Аргументу flags может быть присвоено одно из следующих значений:

    BPF_ANY
        Создать новый элемент или обновить существующий.

    BPF_NOEXIST
        Создать новый элемент только, если он не существует.

    BPF_EXIST
        Обновить существующий элемент.

    В случае успеха операция возвращает ноль. В случае ошибки возвращается значение -1, а для параметра errno устанавливаются значения EINVAL, EPERM, ENOMEM или E2BIG.

BPF_MAP_DELETE_ELEM
    Команда BPF_MAP_DELETE_ELEM удаляет элемент, ключом которого является key, с карты, на которую ссылается файловый дескриптор 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));
        }

    В случае успеха возвращается ноль. Если элемент не найден, возвращается значение -1, а errno присваивается значение ENOENT.

BPF_MAP_GET_NEXT_KEY
    Команда BPF_MAP_GET_NEXT_KEY выполняет поиск элемента по key на карте, на которую ссылается файловый дескриптор fd и устанавливает указатель next_key на ключ следующего элемента.

        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));
        }

    Если key найден, операция возвращает ноль и устанавливает указатель next_key на ключ следующего элемента. Если key не найден, операция возвращает ноль и устанавливает указатель next_key на ключ первого элемента. Если key является последним элементом, возвращается значение -1, а errno присваивается значение ENOENT. Другими возможными значениями errno являются ENOMEM, DEFAULT, EPERM и EINVAL. Этот метод можно использовать для перебора всех элементов на карте.

close(map_fd)
    Удаляет карту, на которую ссылается файловый дескриптор map_fd. При завершении работы программы пользовательского пространства, создавшей карту, все карты будут удалены автоматически (однако, смотрите ПРИМЕЧАНИЯ).

  eBPF map types
Поддерживаются следующие типы карт:

BPF_MAP_TYPE_HASH
    Карты хэш-таблиц, которые обладают следующими характеристиками:

    •   Карты создаются и уничтожаются программами пользовательского пространства. Как программы пользовательского пространства, так и программы eBPF могут выполнять операции поиска, обновления и удаления.

    •   Ядро заботится о выделении и освобождении пар key/value.

    •   Помощнику map_update_elem() не удастся вставить новый элемент, когда будет достигнут предел max_entries (это гарантирует, что программы eBPF не смогут исчерпать объем памяти).

    •   map_update_elem() - заменяет существующие элементы атомарно.

    Карты с хэш-таблицами оптимизированы для ускорения поиска.

BPF_MAP_TYPE_ARRAY
    Карты массивов имеют следующие характеристики:

    •   Оптимизированы для максимально быстрого поиска. В будущем компилятор verifier/JIT сможет распознать операции lookup(), которые используют постоянный ключ и оптимизировать его в постоянный указатель. Можно также оптимизировать непостоянный ключ для прямой арифметики указателей, поскольку указатели и value_size являются постоянными в течение всего срока службы программы eBPF. Другими словами, array_map_lookup_elem() может быть "встроен" верификатором/JIT-компилятором, сохраняя одновременный доступ к этой карте из пространства пользователя.

    •   Все элементы массива предварительно распределены и инициализированы нулем во время инициализации.

    •   Ключ представляет собой индекс массива и должен содержать ровно четыре байта.

    •   map_delete_elem() завершается ошибкой EINVAL, поскольку элементы не могут быть удалены.

    •   map_update_elem() заменяет элементы в стиле nonatomic; для атомарных обновлений вместо этого следует использовать карту хэш-таблицы. Однако есть один особый случай, который также может быть использован с массивами: атомарные встроенные функции __sync_fetch_и_add() могут использоваться с 32-разрядными и 64-разрядными атомарными счетчиками. Например, они могут быть применен ко всему значению, если оно представляет собой один счетчик или, в случае структуры, содержащей несколько счетчиков, они могут быть использованы для отдельных счетчиков. Это довольно часто бывает полезно для агрегирования и учета событий.

    Среди применений для отображения массивов можно выделить следующие:

    •   Как "глобальные" переменные eBPF: массив из 1 элемента, ключ которого (индекс) равен 0, а значение представляет собой набор "глобальных" переменных, которые программы eBPF могут использовать для сохранения состояния между событиями.

    •   Объединение событий трассировки в фиксированный набор сегментов.

    •   Учет сетевых событий, например, количества пакетов и их размеров.

BPF_MAP_TYPE_PROG_ARRAY (since Linux 4.2)
    Программная карта массива - это особый вид карты массива, значения которой содержат только файловые дескрипторы, относящиеся к другим программам eBPF. Таким образом, как key_size, так и value_size должны составлять ровно четыре байта. Эта карта используется совместно с помощником bpf_tail_call().

    Это означает, что программа eBPF с привязанной к ней картой программного массива может вызываться со стороны ядра в

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

    и, следовательно, заменяет свой собственный программный поток на поток из программы в данном слоте массива программ, если таковой имеется. Это можно рассматривать как своего рода таблицу перехода к другой программе eBPF. Затем вызываемая программа повторно использует тот же стек. Когда будет выполнен переход к новой программе, она больше не вернется к старой программе.

    Если программа eBPF не найдена по заданному индексу массива программ (поскольку слот карты не содержит допустимого дескриптора файла программы, указанный индекс/ключ поиска находится за пределами допустимого значения или превышено ограничение в 32 вложенных вызова), выполнение продолжается с текущей программой eBPF. Это может быть использовано в качестве запасного варианта для случаев по умолчанию.

    Карта программного массива полезна, например, при трассировке или создании сетей для обработки отдельных системных вызовов или протоколов в их собственных подпрограммах и использования их идентификаторов в качестве индивидуального картографического индекса. Такой подход может привести к повышению производительности, а также позволяет преодолеть максимальное ограничение на количество команд в одной программе eBPF. В динамических средах демон пользовательского пространства может атомарно заменять отдельные подпрограммы во время выполнения более новыми версиями, чтобы изменить общее поведение программы, например, при изменении глобальных политик.

  Программы eBPF
Команда BPF_PROG_LOAD используется для загрузки программы eBPF в ядро. Возвращаемое значение этой команды - это новый файловый дескриптор, связанный с этой программой 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 это :

        enum bpf_prog_type {
            BPF_PROG_TYPE_UNSPEC,        /* Reserve 0 as invalid
                                            program type */
            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,
            /* See /usr/include/linux/bpf.h for the full list.  */
        };

Более подробную информацию о типах программ eBPF смотрите ниже.

Остальные поля bpf_attr задаются следующим образом:

•   ins - это массив инструкций struct bpf_insn.

•   insn_cnt - это количество инструкций в программе, на которые ссылается ins.

•   license - это строка лицензии, которая должна быть совместима с GPL для вызова вспомогательных функций с пометкой gpl_only. (Правила лицензирования те же, что и для модулей ядра, так что могут использоваться и двойные лицензии, такие как "Dual BSD/GPL").

•   log_buf - это указатель на выделенный вызывающим пользователем буфер, в котором встроенный в ядро верификатор может хранить журнал проверки. Этот журнал представляет собой многострочную строку, которую автор программы может проверить, чтобы понять, как верификатор пришел к выводу, что программа eBPF небезопасна. Формат выходных данных может измениться в любое время по мере развития верификатора.

•   log_size - размер буфера, на который указывает параметр log_buf. Если размер буфера недостаточно велик для хранения всех сообщений средства проверки, возвращается значение -1, а для параметра errno устанавливается значение ENOSPC.

•   log_level - уровень детализации верификатора. Нулевое значение означает, что верификатор не будет обеспечивать журналирование; в этом случае log_buf должен быть нулевым указателем, а log_size должен быть равен нулю.

Применение close(2) к файловому дескриптору, возвращаемому BPF_PROG_LOAD, приведет к выгрузке программы eBPF (однако, смотрите ПРИМЕЧАНИЯ).

Карты доступны из программ eBPF и используются для обмена данными между программами eBPF, а также между программами eBPF и программами пользовательского пространства. Например, программы eBPF могут обрабатывать различные события (например, kprobe, пакеты) и сохранять их данные на карте, а программы пользовательского пространства могут затем извлекать данные с карты. И наоборот, пользовательские программы могут использовать карту в качестве механизма настройки, заполняя карту значениями, проверенными программой eBPF, которая затем изменяет свое поведение на лету в соответствии с этими значениями.

  Типы программ eBPF
Тип программы eBPF (prog_type) определяет подмножество вспомогательных функций ядра, которые может вызывать программа. Тип программы также определяет вводимые программой данные (содержимое) в формате struct bpf_context, который представляет собой большой двоичный объект данных, передаваемый в программу eBPF в качестве первого аргумента.

Например, программа трассировки не имеет точно такого же набора вспомогательных функций, как программа фильтрации сокетов (хотя у них могут быть некоторые общие вспомогательные функции). Аналогично, входными данными (контекстом) для программы трассировки является набор значений регистров, в то время как для фильтра сокетов это сетевой пакет.

В будущем набор функций, доступных для программ eBPF данного типа, может быть расширен.

Поддерживаются следующие типы программ:

BPF_PROG_TYPE_SOCKET_FILTER (начиная с версии Linux 3.19)
    В настоящее время набор функций для BPF_PROG_TYPE_SOCKET_FILTER содержит:

        bpf_map_lookup_elem(map_fd, void *key)
                            /* look up key in a map_fd */
        bpf_map_update_elem(map_fd, void *key, void *value)
                            /* update key/value */
        bpf_map_delete_elem(map_fd, void *key)
                            /* delete key in a map_fd */

    Аргумент bpf_context является указателем на struct __sk_buff.

BPF_PROG_TYPE_KPROBE (начиная с версии Linux 4.1)
    [Будет описано]

BPF_PROG_TYPE_SCHED_CLS (начиная с версии Linux 4.1)
    [Будет описано]

BPF_PROG_TYPE_SCHED_ACT (начиная с версии Linux 4.1)
    [Будет описано]

  События
Как только программа загружена, ее можно привязать к событию. В различных подсистемах ядра это делается по-разному.

Начиная с Linux 3.19, следующий вызов присоединяет программу prog_fd к сокету sockfd, который был создан предыдущим вызовом socket(2).:

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

Начиная с Linux 4.1, следующий вызов может использоваться для присоединения программы eBPF, на которую ссылается файловый дескриптор prog_fd, к файловому дескриптору события perf, event_fd, который был создан предыдущим вызовом perf_event_open(2):

    ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
При успешном выполнении возвращаемое значение зависит от используемой команды:

BPF_MAP_CREATE
    Новый файловый дескриптор, связанный с картой eBPF.

BPF_PROG_LOAD
    Новый файловый дескриптор, связанный с программой eBPF.

Все остальные команды
    Ноль.

В случае ошибки возвращается -1, а errno устанавливается в значение ошибки.

ОШИБКИ

E2BIG
    Программа eBPF слишком велика, или карта достигла предела max_entries (максимальное количество элементов).

EACCES
    Для BPF_PROG_LOAD, несмотря на то, что все инструкции программы верны, программа была отклонена, поскольку была признана небезопасной. Это может быть связано с тем, что она может получить доступ к запрещенной области памяти или к не инициализированному стеку/регистру, или с тем, что ограничения функции не соответствуют фактическим типам, или с тем, что доступ к памяти был несогласован. В этом случае рекомендуется снова вызвать bpf() с log_level = 1 и проверить log_buf о конкретной причине, указанной верификатором.

EAGAIN
    Для параметра BPF_PROG_LOAD указывает, что необходимые ресурсы заблокированы. Это происходит, когда верификатор обнаруживает ожидающие отправки сигналы во время проверки работоспособности программы bpf. В этом случае просто вызовите bpf() еще раз с теми же параметрами.

EBADF
    fd не является открытым файловым дескриптором.

EFAULT
    Один из указателей (key или value, или log_buf, или ins) находится за пределами доступного адресного пространства.

EINVAL
    Значение cmd не распознано ядром.

EINVAL
    Для BPF_MAP_CREATE либо map_type, либо атрибуты недопустимы.

EINVAL
    Для команд BPF_MAP_*_ELEM некоторые поля union bpf_attr, которые не используются этой командой, не имеют нулевого значения.

EINVAL
    Для BPF_PROG_LOAD указывает на попытку загрузки недопустимой программы. Программы eBPF могут быть признаны недействительными из-за нераспознанных инструкций, использования зарезервированных полей, выхода за пределы диапазона, бесконечных циклов или вызовов неизвестных функций.

ENOENT
    Для BPF_MAP_LOOKUP_ELEM или BPF_MAP_DELETE_ELEM указывает, что элемент с заданным значением key не найден.

ENOMEM
    Не удается выделить достаточно памяти.

EPERM
    Вызов был выполнен без достаточных привилегий (без возможности CAP_SYS_ADMIN).

СТАНДАРТЫ
Linux.

ИСТОРИЯ
Linux 3.18.

ПРИМЕЧАНИЯ
До Linux 4.4 все команды bpf() требовали, чтобы вызывающий объект обладал возможностями CAP_SYS_ADMIN. Начиная с Linux 4.4, непривилегированный пользователь может создавать ограниченные программы типа BPF_PROG_TYPE_SOCKET_FILTER и связанные с ними карты. Однако они не могут хранить указатели ядра в картах и в настоящее время ограничены следующими вспомогательными функциями:

•   get_random
•   get_smp_processor_id
•   tail_call
•   ktime_get_ns

Непривилегированный доступ может быть заблокирован путем записи значения 1 в файл /proc/sys/kernel/unprivileged_bpf_disabled.

Объекты eBPF (карты и программы) могут совместно использоваться процессами. Например, после fork(2) дочерний процесс наследует файловые дескрипторы, относящиеся к тем же объектам eBPF. Кроме того, файловые дескрипторы, ссылающиеся на объекты eBPF, могут передаваться через доменные сокеты UNIX. Файловые дескрипторы, ссылающиеся на объекты eBPF, можно дублировать обычным способом, используя dup(2) и аналогичные вызовы. Объект eBPF освобождается только после того, как все файловые дескрипторы, ссылающиеся на этот объект, будут закрыты.

Программы eBPF могут быть написаны на ограниченном языке С, который компилируется (с помощью компилятора clang) в байт-код bpf. В этом ограниченном C отсутствуют различные функции, такие как циклы, глобальные переменные, функции с переменным числом, числа с плавающей запятой и передача структур в качестве аргументов функций. Некоторые примеры можно найти в файлах samples/bpf/*_kern.c в дереве исходных текстов ядра.

Ядро содержит JIT-компилятор, который преобразует байт-код bpf в машинный код для повышения производительности. До Linux 4.15 JIT-компилятор по умолчанию отключен, но его работой можно управлять, записав одно из следующих целых чисел в файл /proc/sys/net/core/bpf_jit_enable:

0   Отключить JIT-компиляцию (по умолчанию).

1   Обычная компиляция.

2   Режим отладки. Сгенерированные коды операций в шестнадцатеричном формате записываются в журнал ядра. Затем эти коды операций можно разобрать с помощью программы tools/net/bpf_jit_disasm.c, представленной в дереве исходных текстов ядра.

Начиная с Linux 4.15, ядро может быть настроено с помощью параметра CONFIG_BPF_JIT_ALWAYS_ON. В этом случае JIT-компилятор всегда включен, а параметр bpf_jit_enable инициализируется значением 1 и является неизменяемым. Этот параметр конфигурации ядра был предоставлен в качестве средства защиты от одной из атак Spectre на интерпретатор BPF.

JIT-компилятор для bpf в настоящее время доступен для следующих архитектур:

•   x86-64 (начиная с Linux 3.18; cBPF начиная с Linux 3.0);
•   ARM32 (начиная с Linux 3.18; cBPF начиная с Linux 3.4);
•   SPARC 32 (начиная с Linux 3.18; cBPF начиная с Linux 3.5);
•   ARM-64 (начиная с Linux 3.18);
•   s390 (начиная с Linux 4.1; cBPF начиная с Linux 3.7);
•   PowerPC 64 (начиная с Linux 4.8; cBPF начиная с Linux 3.1);
•   SPARC 64 (начиная с Linux 4.12);
•   x86-32 (начиная с Linux 4.18);
•   MIPS 64 (начиная с Linux 4.18; cBPF начиная с Linux 3.16);
•   riscv (начиная с Linux 5.1).

ПРИМЕРЫ
    #define NITEMS(a)  (sizeof(a) / sizeof(*(a)))

    /* bpf+sockets example:
     * 1.  create array map of 256 elements
     * 2.  load program that counts number of packets received
     *    r0 = skb->data[ETH_HLEN + offsetof(struct iphdr, protocol)]
     *    map[r0]++
     * 3.  attach prog_fd to raw socket via setsockopt()
     * 4.  print number of received TCP/UDP packets every second
     */
    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("failed to create map '%s'\n", strerror(errno));
            /* likely not run as 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,
                                NITEMS(prog), "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;
    }

Другой подробный рабочий код можно найти в каталоге samples/bpf в дереве исходных текстов ядра.

СМОТРИТЕ ТАКЖЕ
seccomp(2), bpf-helpers(7), socket(7), tc(8), tc-bpf(8)

Как классический, так и расширенный BPF описаны в исходном файле ядра Documentation/networking/filter.txt.

ПЕРЕВОД
Русский перевод этой страницы руководства разработал(и) Aleksandr Felda <isk8da@gmail.com>

Этот перевод является свободной программной документацией; он распространяется на условиях общедоступной лицензии GNU (GNU General Public License - GPL, https://www.gnu.org/licenses/gpl-3.0.html версии 3 или более поздней) в отношении авторского права, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ.

Если вы обнаружите какие-либо ошибки в переводе этой страницы руководства, пожалуйста, сообщите об этом разработчику(ам) по его(их) адресу(ам) электронной почты или по адресу списка рассылки русских переводчиков (<debian-l10n-russian@lists.debian.org>).