【问题标题】:eBPF implicit declaration of BPF HelperBPF Helper 的 eBPF 隐式声明
【发布时间】:2019-02-08 06:49:34
【问题描述】:

我在编译使用 TC 安装的 eBPF 程序时遇到问题。目前,它只执行一些基本的修改,这需要重新计算 IP 校验和。

我注意到在 BPF 助手中,有一个函数 bpf_l3_csum_replace 似乎是我想要的。但是,当我尝试使用使用 BPF_FUNC 宏映射的任何内置函数时,我得到一个隐式声明错误:

... 警告:“bpf_l3_csum_replace”的隐式声明在 C99。

随后是来自验证者的错误:

... 不支持调用全局函数“bpf_l3_csum_replace”。仅有的 允许调用预定义的 BPF 助手。

我的编译行:

clang -target bpf -nostdinc -I/usr/include -I/usr/lib64/clang/5.0.2/include -O2 -emit-llvm -c <file> -o - | llc -march=bpf -filetype=obj -o <output>

我应该注意,只要我不使用任何“预定义”的 bpf 助手,我就可以编译和安装 BPF 对象(使用 TC)。一旦我这样做,问题就会出现。

我在内核树之外执行此操作,FWIW。不确定这是否是一个问题。我包括了 bpf 标头 (linux/bpf.h),并且我使用了 iproute2 包中的 bpf_api.h 标头(对 bpf_helpers.h 标头没有太多运气)。

我对 eBPF 还是比较陌生,所以如果我遗漏了什么,我不会感到惊讶。任何帮助将不胜感激。

编辑:代码

#define KBUILD_NAME "testbpf"
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/tcp.h>
#include <linux/filter.h>
#include <linux/pkt_cls.h>
#include "bpf_api.h"


#define _htonl __builtin_bswap32

struct eth_hdr {
    unsigned char h_dest[ETH_ALEN];
    unsigned char h_source[ETH_ALEN];
    unsigned short h_proto;
};

#define IP_CSUM_OFFSET (ETH_HLEN + offsetof(struct iphdr, check))
#define TOS_OFF (ETH_HLEN + offsetof(struct iphdr, tos))
#define PROTO_OFF (ETH_HLEN + offsetof(struct iphdr, protocol))

__section("ingress") int bpf_prog(struct __sk_buff *skb)
{
    void *data = (void *) (long)skb->data;
    struct eth_hdr *eth = data;
    void *data_end = (void*) (long) skb->data_end;

    if (data+sizeof(*eth) > data_end)
        return BPF_H_DEFAULT;

    if (eth->h_proto == htons(ETH_P_ARP))
        return BPF_H_DEFAULT;

    // ipv4
    if (eth->h_proto == htons(ETH_P_IP))
    {
        struct iphdr *ip = data+sizeof(*eth);
        if (data+sizeof(*ip)+sizeof(*eth) > data_end)
            return BPF_H_DEFAULT;

        __u8 proto = ip->protocol;
        __u8 old_tos = ip->tos;
        // mangle our tos; not meant to achieve anything
        ip->tos = 0x04;
        if (proto == IPPROTO_ICMP)
            ip->tos = 0x00;
        if (proto == IPPROTO_TCP)
            ip->tos = 0x04;
        if (proto == IPPROTO_UDP)
            ip->tos = 0x08;
        // update our csum and return
        bpf_l3_csum_replace(skb, IP_CSUM_OFFSET, old_tos, ip->tos, 2); // ISSUE here
        return BPF_H_DEFAULT;
    }

    return BPF_H_DEFAULT;
}

char __license[] __section("license") = "GPL";

【问题讨论】:

  • bpf_api.h 应该足够了。你能发布程序或MCVE吗?
  • @pchaigno 所以我通过手动将函数原型添加到 bpf_api.h 标头来编译它,但我不确定为什么需要它
  • 让我们试着弄清楚。我猜您将bpf_api.h 复制到与源文件相同的目录中?它究竟来自哪里?哪个版本的 iproute2?
  • 是的,没错。它来自最新版本的 iproute2(来自 git.kernel.org)

标签: c linux-kernel clang bpf ebpf


【解决方案1】:

编译器抛出该错误是因为 bpf_api.h 将此帮助器定义为:

static int l3_csum_replace(struct __sk_buff *skb, uint32_t off, uint32_t from, uint32_t to, uint32_t flags);

而不是bpf_l3_csum_replace。如果您从代码中的帮助程序名称中删除 bpf_ 前缀,它会按预期进行编译,而无需手动添加原型。


细节和调试

如果您遵循bpf_api.h 中的原型定义,您会看到它使用了BPF_FUNC,而BPF_FUNC 本身使用了__BPF_FUNC

#ifndef __BPF_FUNC
# define __BPF_FUNC(NAME, ...)                      \
    (* NAME)(__VA_ARGS__) __maybe_unused
#endif

#ifndef BPF_FUNC
# define BPF_FUNC(NAME, ...)                        \
    __BPF_FUNC(NAME, __VA_ARGS__) = (void *) BPF_FUNC_##NAME
#else
#endif

根据这个sn-p的代码,任何形式的helper定义:

static return_type BPF_FUNC(name, ... args ...);

将扩展为:

static return_type name(... args ...);

所以没有添加任何 bpf_ 前缀。

【讨论】:

  • 我确实遵循了这些宏,但不确定验证程序 (?) 是否会抱怨不属于 API 的“全局函数”。我现在不在我的电脑附近进行测试,但会在早上这样做!为迄今为止的帮助欢呼
  • 哦,那是编译器抛出错误,而不是内核的验证器(参见the LLVM commit that added that)。
  • 那行得通;谢谢您的帮助!是的,我不认为它是验证者,但不确定它是什么
  • 很高兴能帮上忙!
猜你喜欢
  • 2019-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-17
  • 2016-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多