BPF - Linux手册页

时间:2019-08-20 17:58:35  来源:igfitidea点击:

Linux程序员手册 第2部分
更新日期: 2020-06-09

名称

bpf-在扩展的BPF映射或程序上执行命令

语法

#include <linux/bpf.h>

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

说明

bpf()系统调用执行与扩展的Berkeley数据包过滤器有关的一系列操作。扩展bpf(或eBPF)类似于用于过滤网络数据包的原始("经典")bpf(cBPF)。对于cBPF和eBPF程序,内核在加载程序之前都会对其进行静态分析,以确保它们不会损害正在运行的系统。

eBPF以多种方式扩展cBPF,包括调用一组固定的内核内辅助函数(通过eBPF提供的BPF_CALL操作码扩展)和访问共享数据结构(如eBPF映射)的能力。

Extended BPF Design/Architecture

eBPF映射是用于存储不同数据类型的通用数据结构。数据类型通常被视为二进制Blob,因此用户只需在映射创建时指定键的大小和值的大小即可。换句话说,给定映射的键/值可以具有任意结构。

用户进程可以创建多个映射(键/值对是不透明的数据字节),并可以通过文件描述符访问它们。不同的eBPF程序可以并行访问相同的地图。由用户流程和eBPF程序决定它们在地图中存储的内容。

有一种特殊的映射类型,称为程序数组。这种类型的映射存储引用其他eBPF程序的文件描述符。当在映射中执行查找时,程序流就地重定向到另一个eBPF程序的开头,并且不返回到调用程序。嵌套级别的固定上限为32,因此无法制作无限循环。在运行时,可以修改映射中存储的程序文件描述符,因此可以根据特定要求更改程序功能。程序数组映射中引用的所有程序必须事先已通过bpf()加载到内核中。如果映射查找失败,则当前程序将继续执行。有关更多详细信息,请参见下面的BPF_MAP_TYPE_PROG_ARRAY。

通常,eBPF程序由用户进程加载,并在进程退出时自动卸载。在某些情况下,例如tc-bpf(8),即使在加载程序的进程退出后,程序仍将继续在内核中运行。在这种情况下,在用户空间程序关闭文件描述符后,tc子系统保留对eBPF程序的引用。因此,特定程序是否继续存在于内核中,取决于它通过bpf()加载后如何进一步附加到给定的内核子系统。

每个eBPF程序都是一组可以安全运行直到完成的指令。内核验证程序可以静态确定eBPF程序已终止并且可以安全执行。在验证过程中,内核会为eBPF程序使用的每个映射增加引用计数,以便在卸载程序之前无法删除附加的映射。

eBPF程序可以附加到不同的事件。这些事件可以是网络数据包的到达,跟踪事件,网络排队规则的分类事件(对于附加到tc(8)分类器的eBPF程序),以及将来可能添加的其他类型。新事件触发eBPF程序的执行,该程序可以在eBPF映射中存储有关事件的信息。除了存储数据,eBPF程序还可以调用一组固定的内核内帮助函数。

可以将同一eBPF程序附加到多个事件,并且不同的eBPF程序可以访问同一地图:

tracing     tracing    tracing    packet      packet     packet
event A     event B    event C    on eth0     on eth1    on eth2
 |             |         |          |           |          ha
 |             |         |          |           v          |
 --> tracing <--     tracing      socket    tc ingress   tc egress
      prog_1          prog_2      prog_3    classifier    action
      |  |              |           |         prog_4      prog_5
   |---  -----|  |------|          map_3        |           |
 map_1       map_2                              --| map_4 |--

Arguments

bpf()系统调用要执行的操作由cmd参数确定。每个操作都带有一个通过attr提供的自变量,该自变量是指向bpf_attr类型的并集的指针(请参见下文)。 size参数是attr指向的并集的大小。

cmd中提供的值为以下之一:

BPF_MAP_CREATE
创建一个映射并返回引用该映射的文件描述符。新文件描述符会自动启用"执行时关闭"文件描述符标志(请参阅fcntl(2))。
BPF_MAP_LOOKUP_ELEM
通过键在指定映射中查找元素并返回其值。
BPF_MAP_UPDATE_ELEM
在指定映射中创建或更新元素(键/值对)。
BPF_MAP_DELETE_ELEM
在指定地图中通过键查找和删除元素。
BPF_MAP_GET_NEXT_KEY
通过键在指定映射中查找元素,然后返回下一个元素的键。
BPF_PROG_LOAD
验证并加载eBPF程序,并返回与该程序关联的新文件描述符。新文件描述符会自动启用"执行时关闭"文件描述符标志(请参阅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()帮助函数,并检查程序是否未访问超出指定范围的地图元素值value_size。例如,当创建一个key_size为8的地图并且eBPF程序调用时
bpf_map_lookup_elem(map_fd, fp - 4)
该程序将被拒绝,因为内核内的辅助功能
bpf_map_lookup_elem(map_fd, void *key)
期望从key指向的位置读取8个字节,但是fp-4(其中fp是堆栈的顶部)的起始地址将导致越界堆栈访问。
同样,当创建一个value_size为1的地图并且eBPF程序包含
value = bpf_map_lookup_elem(...);
*(u32 *) value = 1;
该程序将被访问,因为它访问的值指针超出了指定的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命令在文件描述符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必须指向value_size个字节的缓冲区。
如果未找到任何元素,则该操作返回-1并将errno设置为ENOENT。
BPF_MAP_UPDATE_ELEM
BPF_MAP_UPDATE_ELEM命令在文件描述符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));
}
The flags

argument should be specified as one of the following:

BPF_ANY
创建一个新元素或更新一个现有元素。
BPF_NOEXIST
仅当不存在时才创建一个新元素。
BPF_EXIST
更新现有元素。
成功后,该操作将返回零。发生错误时,将返回-1并将errno设置为EINVALEPERM,ENOMEM或E2BIG。 E2BIG表示地图中的元素数量已达到在地图创建时指定的max_entries限制。如果标志指定BPF_NOEXIST并且带有key的元素已经存在于映射中,则将返回EEXIST。如果标志指定BPF_EXIST并且映射中不存在带有key的元素,则将返回ENOENT。
BPF_MAP_DELETE_ELEM
BPF_MAP_DELETE_ELEM命令从文件描述符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命令在文件描述符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值是ENOMEMEFAULT,EPERM和EINVAL。此方法可用于遍历地图中的所有元素。
close(map_fd)
删除文件描述符map_fd引用的映射。当创建地图的用户空间程序退出时,所有地图将被自动删除(但请参阅"注意")。

eBPF map types

支持以下地图类型:

BPF_MAP_TYPE_HASH

Hash-table maps have the following characteristics:

*
地图是由用户空间程序创建和销毁的。用户空间程序和eBPF程序都可以执行查找,更新和删除操作。
*
内核负责分配和释放键/值对。
*
达到max_entries限制时,map_update_elem()帮助器将无法插入新元素。 (这确保eBPF程序不会耗尽内存。)
*
map_update_elem()原子替换现有元素。
哈希表映射针对查找速度进行了优化。
BPF_MAP_TYPE_ARRAY

Array maps have the following characteristics:

*
已针对可能的最快查询进行了优化。将来,验证程序/ JIT编译器可能会识别采用常量密钥的lookup()操作,并将其优化为常量指针。也可以将非常数键优化为直接指针算法,因为在eBPF程序的生命期内,指针和value_size是恒定的。换句话说,verifier / JIT编译器可以"内联" array_map_lookup_elem(),同时保留从用户空间对该映射的并发访问。
*
所有数组元素都已预先分配,并在初始化时初始化为零
*
密钥是数组索引,必须正好是四个字节。
*
map_delete_elem()失败,并显示错误EINVAL,因为无法删除元素。
*
map_update_elem()以非原子方式替换元素;对于原子更新,应改用哈希表映射。但是,还有一种特殊情况也可以与数组一起使用:原子内置的__sync_fetch_and_add()可以在32位和64位原子计数器上使用。例如,如果它代表单个计数器,则可以应用于整个值本身,或者在包含多个计数器的结构的情况下,可以将其应用于单个计数器。这对于事件的汇总和统计通常很有用。
Among the uses for array maps are the following:
*
作为"全局" 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 programs

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的其余字段设置如下:

*
insns是struct bpf_insn指令的数组。
*
insn_cnt是insns引用的程序中的指令数。
*
license是一个许可证字符串,必须与GPL兼容才能调用标记为gpl_only的帮助函数。 (许可规则与内核模块的许可规则相同,因此也可以使用双重许可,例如"双重BSD / GPL"。)
*
log_buf是指向分配给调用方的缓冲区的指针,内核内验证程序可以在其中存储验证日志。该日志是一个多行字符串,程序作者可以检查该字符串,以了解验证程序如何得出eBPF程序不安全的结论。随着验证程序的发展,输出格式可以随时更改。
*
log_size由log_buf指向的缓冲区的大小。如果缓冲区的大小不足以存储所有验证程序消息,则返回-1并将errno设置为ENOSPC。
*
验证程序的log_level详细级别。零值表示验证程序将不提供日志;在这种情况下,log_buf必须为NULL指针,并且log_size必须为零。

close(2)应用于BPF_PROG_LOAD返回的文件描述符将卸载eBPF程序(但请参见NOTES)。

可以从eBPF程序访问地图,并用于在eBPF程序之间以及eBPF程序和用户空间程序之间交换数据。例如,eBPF程序可以处理各种事件(例如kprobe,数据包)并将其数据存储到地图中,然后用户空间程序可以从地图中获取数据。相反,用户空间程序可以将映射用作配置机制,使用eBPF程序检查的值填充映射,然后根据这些值动态修改其行为。

eBPF program types

eBPF程序类型(prog_type)确定程序可以调用的内核帮助程序功能的子集。程序类型还确定程序输入(上下文)--- struct bpf_context的格式(这是作为第一个参数传递到eBPF程序中的数据blob)。

例如,跟踪程序没有与套接字过滤器程序完全相同的辅助函数子集(尽管它们可能有一些共同的辅助函数)。同样,跟踪程序的输入(上下文)是一组寄存器值,而套接字过滤器的输入(上下文)是网络数据包。

给定类型的eBPF程序可用的功能集将来可能会增加。

支持以下程序类型:

BPF_PROG_TYPE_SOCKET_FILTER(since 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参数是指向__sk_buff结构的指针。
BPF_PROG_TYPE_KPROBE(since Linux 4.1)
[待记录]
BPF_PROG_TYPE_SCHED_CLS(since Linux 4.1)
[待记录]
BPF_PROG_TYPE_SCHED_ACT(since Linux 4.1)
[待记录]

Events

加载程序后,可以将其附加到事件中。各种内核子系统具有不同的方法。

从Linux 3.19开始,以下调用会将程序prog_fd附加到套接字sockfd,该套接字是由先前对socket(2)的调用创建的:

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

从Linux 4.1开始,可以使用以下调用将文件描述符prog_fd引用的eBPF程序附加到由对perf_event_open(2)的先前调用创建的perf事件文件描述符event_fd

ioctl(event_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);

示例

/* 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,
                            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;
}

可以在内核源代码树的samples / bpf目录中找到一些完整的工作代码。

返回值

对于成功的调用,返回值取决于以下操作:

BPF_MAP_CREATE
与eBPF映射关联的新文件描述符。
BPF_PROG_LOAD
与eBPF程序关联的新文件描述符。
All other commands
零。

如果出错,则返回-1,并正确设置errno。

错误说明

E2BIG
eBPF程序太大,或者映射已达到max_entries限制(元素的最大数量)。
EACCES
对于BPF_PROG_LOAD,即使所有程序指令均有效,该程序也被拒绝,因为它被认为是不安全的。这可能是因为它可能访问了不允许的内存区域或未初始化的堆栈/寄存器,或者由于函数约束与实际类型不匹配,或者由于存在未对齐的内存访问。在这种情况下,建议再次使用log_level = 1调用bpf(),并根据验证者提供的特定原因检查log_buf。
EBADF
fd不是打开的文件描述符。
EFAULT
指针之一(键或值或log_buf或insns)在可访问的地址空间之外。
EINVAL
此内核无法识别在cmd中指定的值。
EINVAL
对于BPF_MAP_CREATE,map_type或属性均无效。
EINVAL
对于BPF_MAP _ * _ ELEM命令,此命令未使用的并集bpf_attr的某些字段未设置为零。
EINVAL
对于BPF_PROG_LOAD,表示尝试加载无效程序。由于无法识别的指令,保留字段的使用,跳出范围,无限循环或未知函数的调用,eBPF程序可被视为无效。
ENOENT
对于BPF_MAP_LOOKUP_ELEM或BPF_MAP_DELETE_ELEM,表示未找到具有给定键的元素。
ENOMEM
无法分配足够的内存。
EPERM
进行该调用时没有足够的特权(没有CAP_SYS_ADMIN功能)。

版本

bpf()系统调用首先出现在Linux 3.18中。

遵循规范

bpf()系统调用是特定于Linux的。

备注

在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

可以通过设置sysctl / proc / sys / kernel / unprivileged_bpf_disabled来阻止非特权访问。

eBPF对象(映射和程序)可以在进程之间共享。例如,在fork(2)之后,子代继承了引用相同eBPF对象的文件描述符。此外,可以通过UNIX域套接字传输引用eBPF对象的文件描述符。可以使用dup(2)和类似的调用,以通常的方式复制引用eBPF对象的文件描述符。仅在关闭所有引用该对象的文件描述符之后,才释放eBPF对象。

可以在受限的C语言中编写eBPF程序,然后将其编译(使用clang编译器)为eBPF字节码。此受限的C中省略了各种功能,例如循环,全局变量,可变参数函数,浮点数以及作为函数参数的传递结构。可以在内核源代码树的samples / bpf / * _ kern.c文件中找到一些示例。

内核包含一个实时(JIT)编译器,该编译器将eBPF字节码转换为本机代码以提高性能。在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并且是不可变的。 (提供此内核配置选项是为了缓解针对BPF解释器的一种Spectre攻击。)

当前,eBPF的JIT编译器可用于以下体系结构:

*
x86-64(自Linux 3.18起;自Linux 3.0起为cBPF);
*
ARM32(自Linux 3.18起;自Linux 3.4起为cBPF);
*
SPARC 32(自Linux 3.18起;自Linux 3.5起为cBPF);
*
ARM-64(自Linux 3.18起);
*
s390(从Linux 4.1开始;从Linux 3.7开始为cBPF);
*
PowerPC 64(自Linux 4.8起;自Linux 3.1起为cBPF);
*
SPARC 64(自Linux 4.12起);
*
x86-32(自Linux 4.18起);
*
MIPS 64(自Linux 4.18起;自Linux 3.16起为cBPF);
*
riscv(从Linux 5.1开始)。

另外参见

seccomp(2),bpf-helpers(7),socket(7),tc(8),tc-bpf(8)

内核源文件Documentation / networking / filter.txt中解释了经典BPF和扩展BPF。

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/