EBPF 程序加载失败,没有验证程序日志

EBPF program load fails without verifier log

我正在尝试编写一个 EBPF 程序,但在某些地方我卡住了。在文档中说,由您定义的函数是绝对有效和可调用的,但是,即使在这个最简单的示例中(尽管使用 map),我在使用 tc 将程序加载到接口时也会出错,并且验证者绝对不会发出任何东西。

这是我的示例程序,我还插入了必要的BPF-helpers,以便立即编译:

#include <linux/bpf.h>
#include <linux/pkt_cls.h>

#ifndef __BPF_HELPERS_H
#define __BPF_HELPERS_H

/* helper macro to place programs, maps, license in
 * different sections in elf_bpf file. Section names
 * are interpreted by elf_bpf loader
 */
#define SEC(NAME) __attribute__((section(NAME), used))

/* a helper structure used by eBPF C program
 * to describe map attributes to elf_bpf loader
 */
struct bpf_map_def {
    unsigned int type;
    unsigned int key_size;
    unsigned int value_size;
    unsigned int max_entries;
    unsigned int map_flags;
    unsigned int inner_map_idx;
    unsigned int numa_node;
};

/* helper functions called from eBPF programs written in C */
static void *(*bpf_map_lookup_elem)(void *map, const void *key) =
(void *) BPF_FUNC_map_lookup_elem;
static int (*bpf_clone_redirect)(void *ctx, int ifindex, int flags) =
(void *) BPF_FUNC_clone_redirect;

#endif

struct bpf_map_def SEC("maps") my_map = {
    .type        = BPF_MAP_TYPE_ARRAY,
    .key_size    = sizeof(__u32),
    .value_size  = sizeof(int),
    .max_entries = 256,
};

static int foo(int value) { return value == 1; }

SEC("action")
int filter_and_redirect(struct __sk_buff* skb)
{
    int key = 0;
    int *value = (int*)bpf_map_lookup_elem(&my_map, &key);
    if (!value) return TC_ACT_SHOT;

    if (foo(*value)) bpf_clone_redirect(skb, 1, 1);
    // if (*value == 1) bpf_clone_redirect(skb, 1, 1);
    return TC_ACT_SHOT;
}

SEC("classifier")
int cls_main(struct __sk_buff* skb) { return -1; }

我用下面的命令编译的,很标准

clang -O2 -fno-inline-functions -emit-llvm -c example.c -o - | llc -march=bpf -filetype=obj -o example.o

然后通过

加载到界面
sudo tc filter add dev foo0 parent ffff: bpf obj example.o sec classifier flowid ffff:1 action bpf obj example.o sec action ok

如果我用函数调用注释行并取消注释下一个,它会完美运行。但是用原程序加载的结果是:

Error fetching program/map!
bad action parsing
parse_action: bad value (6:bpf)!
Illegal "action"

应该有一些日志,具体说明为什么我的程序对验证程序无效,但没有,所以我真的被卡住了。为什么它不允许我使用该功能?谢谢大家的建议!

如果您想查看 llvm-objdump -S 命令的结果,可以找到 here.

[编辑]

从接受的答案看来,tc 中有一个错误(?),它基本上不允许不使用地图的功能。这就是为什么作为临时解决方法,我将示例中的函数更改为:

struct bpf_map_def SEC("maps") dummy_map = {
    .type        = BPF_MAP_TYPE_ARRAY,
    .key_size    = sizeof(int),
    .value_size  = sizeof(int),
    .max_entries = 1,
};

int foo(int value) { 
    int key = 0;
    int *v = (int*)bpf_map_lookup_elem(&dummy_map, &key);

    return value == 1; 
}

现在它可以正常工作,但是需要额外的地图操作成本,这不是必需的,所以我期待有关此问题的更多消息。

验证器没有日志,也不应该有任何日志,因为验证器永远没有机会检查您的程序。您收到的错误 来自 tc,它无法将字节码整形为内核程序,并且没有尝试将程序加载到内核中。

您可以检查 strace -e bpf tc filter ...,没有 bpf(BPF_PROG_LOAD, ...) 被调用。

它失败是因为函数调用,而且看起来像是 tc 中的错误。

Tc(和 iproute2)在 commit b5cb33aec65c 中获得了对 eBPF 到 eBPF 函数调用的支持(“bpf:实现 bpf 到 bpf 调用支持”)。提交日志提到,为了添加支持,

First step is processing of map related relocation entries
for .text section

这转化为:

@@ -2120,10 +2192,18 @@ static int bpf_fetch_prog_relo(struct bpf_elf_ctx *ctx, const char *section,
 static int bpf_fetch_prog_sec(struct bpf_elf_ctx *ctx, const char *section)
 {
    bool lderr = false, sseen = false;
+   struct bpf_elf_prog prog;
    int ret = -1;
 
-   if (bpf_has_map_data(ctx))
-       ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen);
+   if (bpf_has_call_data(ctx)) {
+       ret = bpf_fetch_prog_relo(ctx, ".text", &lderr, NULL,
+                     &ctx->prog_text);
+       if (ret < 0)
+           return ret;
+   }
+
+   if (bpf_has_map_data(ctx) || bpf_has_call_data(ctx))
+       ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen, &prog);
    if (ret < 0 && !lderr)
        ret = bpf_fetch_prog(ctx, section, &sseen);
    if (ret < 0 && !sseen)

也就是说调用bpf_fetch_prog_relo()函数对.text段进行了重定位操作,你的函数foo所在的段。这样做是为了插入函数 (foo) 使用的映射的文件描述符(如果有的话)。如果有的话?好吧,不,事实上它似乎一直被称为 ,即使 .text 函数不使用映射并且不需要重定位。但是如果找到重定位信息失败(如果没有重定位就是这种情况,因为专用部分将丢失),那么我们将退出并向上传播一个错误。回溯:

bpf_fetch_prog_relo()
bpf_fetch_prog_sec()
bpf_obj_open()
bpf_do_load()
bpf_load_common()
bpf_parse_and_load_common()
bpf_parse_opt()
parse_action()
bpf_parse_opt()
tc_filter_modify()
do_filter()
do_cmd()
main()

这最终使 tc 命令失败,parse_action() 等函数和其他一些函数会打印您看到的错误消息。同样,与内核验证程序无关。

如何解决?如果我是正确的并且这个 iproute2 中的一个错误,这应该在上游修复,我会和作者一起看看他是怎么想的。您可以修补 iproute2 或找到一种在您调用的函数中使用地图的方法。我使用以下命令成功加载了您的程序:

static int foo(int value)
{
    int key = 0;
    int *beep;

    beep = (int*)bpf_map_lookup_elem(&my_map, &key);
    if (!beep)
        return 0;
    return value == *beep;
}

(顺便感谢独立复制器,非常感谢。)这个例子不是很有用,但它似乎证实了 .text.

的重定位是强制性的