【问题标题】:EBPF program load fails without verifier logEBPF 程序加载失败,没有验证程序日志
【发布时间】:2020-11-16 15:45:46
【问题描述】:

我正在尝试编写一个 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; 
}

现在它可以工作了,但是需要额外的地图操作,这不是必需的,所以我期待更多关于这个问题的消息。

【问题讨论】:

    标签: c linux ebpf


    【解决方案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的重定位是强制性的。

    【讨论】:

    • 非常感谢您的详细回复!但是,我认为这有点不正确,或者我有什么问题 - 如果我用 if 替换函数调用 - 取消注释下一行并用注释注释行 - 一切都很好,即使它有两者classifieraction
    • 另外,我刚刚尝试了您的方法-将代码移动到classifier 并使用direct-action,就像您在帖子中写的一样,它也不起作用,错误Error fetching program/map! Unable to load program ,所以看起来事情就在那个函数调用中
    • 好吧,你是对的,我得到了同样的结果。 Tc 在一种情况下抱怨,但在另一种情况下没有,让我进一步看看。
    • 我还没有找到导致拒绝的确切代码路径,但我的猜测是它与函数调用本身有关。我不记得 tc 是否支持常规函数调用,我的猜测是情况并非如此(尽管内核确实支持它,但在涉及函数调用时,tc 可能只是无法生成相关的字节码)。如果您强制它内联 (inline __attribute__((always_inline))),您的程序将加载该函数。
    • 嗯,根据这两个链接,或多或少现代的EBPF支持函数调用,我有内核5.4,所以它们肯定是:stackoverflow.com/questions/57688344/…lwn.net/Articles/741773
    猜你喜欢
    • 2018-06-28
    • 1970-01-01
    • 1970-01-01
    • 2016-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    相关资源
    最近更新 更多