龙蜥社区开源coolbpf,BPF程序开发效率提升百倍

龙蜥社区OpenAnolis
发布于 2022-7-4 11:13
浏览
0收藏

龙蜥社区开源coolbpf,BPF程序开发效率提升百倍-鸿蒙开发者社区
文/系统运维 SIG (特别兴趣小组)

::: hljs-center

引言

:::

BPF 是一种新的动态跟踪技术,我们目前这项技术正在影响着着生产和生活。BPF 在四大应用场景中表现出巨大的作用:

系统故障诊断:它可以动态插桩透视内核。
网络性能优化它可以对接收和发送的网络包做修改和转向。
系统安全:它可以文件打开和监控协议制定监控安全决策等。
监控:它可以查看性能性能时间点。

BPF 技术也是随着 Linux 内核的发展而发展的,Linux 内核版本经历了 3.x 到 4.x 到 5.x 演进,eBPF 技术的支持也是从 4.​​x 开始完善起来的,特别是 5.x 3.10 内核上的服务器有eBPF的版本,为了让我们现有的eB工具在这些存储量的机器上也增加了大量的运行量,将BPF移植到我们的底层内核,基于 libbpf 的 CO-RE 能力,保证一个工具可以运行在 3.x/4.x/5.x 的低、中、高内核版本。

BPF的开发方式有很多,目前比较热门的有:

1)纯libbpf应用开发:使用libbpf库加载B程序到内核的方式:这种开发方式效率低,没有基础库的驱动,所有必备步骤和基础程序都需要自己摸索。

2)配合BCC等开源代码:开发效率高、可修改移植好,并且支持动态内核项目部分,非常灵活。但存在部署依赖Clang/LLVM等库;每次运行都要执行Clang/LLVM编译,严重CPU、内存等资源,容易与其他服务争抢消耗。

coolbpf 项目,CORE (Compile Once Run Everywhere)为实现,了占用资源、可移植性等优点,还具有 BCC 动态编译特性,适合在生产环境部署的基础上——以低部署的方式部署资源bpf 并提供了一个新的远程利用编译器的 BPF 程序,用户把视线的服务器提供给用户,让用户把视线的服务器返回到。 ,,,然后全量安装内核运行。在简单的用户自己的开发中,使用一种新的关注等库(LLVM、python)、环境安全加载功能,给 BPF 用户提供的探索和实践。

::: hljs-center

一、BPF开发方式对比

:::

BPF 历来的 setockopt 使用了传统的sock filter 报文,到现在使用 libbpf C O-RE 方式进行诊断功能的开发,是和 eBPF 过滤与监控集和开源方式结合的优秀指令能力及 libbpf 通用库的开放分不的,让我们一起回顾一下 BPF 的方式,并在此基础上推出基于构建思想为核心的酷bpf,它站在了巨人的姿态上,进行了资源优化、编程和效率提升。

1、原始阶段

在BPF报文过滤(cPFB)的时候,它通过sock filter的BPF指令,利用setsockopt加载具体的内核,通过setsockopt加载到内核,通过在packet_rcv调用runfilter运行程序来进行报文这种方式,BPF 字节的生成非常原始,类似于人工编写的程序,过程是非常安慰的。

static struct sock_filter filter[6] = {
 { OP_LDH, 0, 0, 12          }, // ldh [12]
 { OP_JEQ, 0, 2, ETH_P_IP    }, // jeq #0x800, L2, L5
 { OP_LDB, 0, 0, 23          }, // ldb [23]
 { OP_JEQ, 0, 1, IPPROTO_TCP }, // jeq #0x6, L4, L5
 { OP_RET, 0, 0, 0           }, // ret #0x0
 { OP_RET, 0, 0, -1,         }, // ret #0xffffffff
};
int main(int argc, char **argv)
{
…
 struct sock_fprog prog = { 6, filter };
 …
 sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
 …
 if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))) {
  return 1;
 }
…
 }

2、保守阶段

例子为samples/bpf下面的samples/bpf的sockex1_kern.c和sockex1_userc,代码分为两部分,通常命名为xxx_en.c和xxx_user.c,前面加载到中间空间中执行,最终在用户空间中执行。BPF程序编写示例完成后就通过 Clang/LLVM 进行编译,xxx_user.c 里显式的去加载生成的 xxx_kernel.o 文件。这种方式虽然使用了编译器支持自动生成 BPF 字节码,但代码组织和 BPF 加载方式比较器,用户代码需要写非常多的重复。

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, u32);
    __type(value, long);
    __uint(max_entries, 256);
} my_map SEC(".maps");

SEC("socket1")
int bpf_prog1(struct __sk_buff *skb)
{
    int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
    long *value;

    if (skb->pkt_type != PACKET_OUTGOING)
        return 0;

    value = bpf_map_lookup_elem(&my_map, &index);
    if (value)
        __sync_fetch_and_add(value, skb->len);

    return 0;
}
char _license[] SEC("license") = "GPL";
int main(int ac, char **argv)
{
    struct bpf_object *obj;
    struct bpf_program *prog;
    int map_fd, prog_fd;
    char filename[256];
    int i, sock, err;
    FILE *f;

    snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

    obj = bpf_object__open_file(filename, NULL);
    if (libbpf_get_error(obj))
        return 1;

    prog = bpf_object__next_program(obj, NULL);
    bpf_program__set_type(prog, BPF_PROG_TYPE_SOCKET_FILTER);

    err = bpf_object__load(obj);
    if (err)
        return 1;

    prog_fd = bpf_program__fd(prog);
    map_fd = bpf_object__find_map_fd_by_name(obj, "my_map");
    ...
 }

3、BCC最初阶段

BCC 的打破了维护的开发,出色的运行和库包封装能力,极大地帮助开发了妹子的时候,还需要开始攻城略地,类似资本的快速基础出现城略。用户只在Python 程序里附上一段 prog ,然后进行数据分析和处理,重点是必须在生产上 Clang 和 python,运行时有 CPU 资源库资源瞬时冲高,导致环境加载 BPF 程序后安装不复现的可能。

int trace_connect_v4_entry(struct pt_regs *ctx, struct sock *sk)
{
  if (container_should_be_filtered()) {
    return 0;
  }

  u64 pid = bpf_get_current_pid_tgid();
  ##FILTER_PID##
  u16 family = sk->__sk_common.skc_family;
  ##FILTER_FAMILY##

  // stash the sock ptr for lookup on return
  connectsock.update(&pid, &sk);

  return 0;
}
# initialize BPF
b = BPF(text=bpf_text)
if args.ipv4:
    b.attach_kprobe(event="tcp_v4_connect", fn_name="trace_connect_v4_entry")
    b.attach_kretprobe(event="tcp_v4_connect", fn_name="trace_connect_v4_return")
b.attach_kprobe(event="tcp_close", fn_name="trace_close_entry")
b.attach_kretprobe(event="inet_csk_accept", fn_name="trace_accept_return")

4、BCC高级阶段

BCC 风靡一时,时代lib 更多在开发者。由于横出世及CO-RE 思想盛行,BCC 自己重来,同BTF 采用BTF 的方式来支持,同仁采用的方式。但是,某个程序体在不同的内核版本上,或者成员的名字变了,或者成员的 Linux 变了各种不同的结构,就需要任何程序处理了(就需要任何程序处理了)在 4.x 等中版本内核上,还需要通过 debuginfo 生成独立的 BTF 文件,过程还是非常复杂的。

SEC("kprobe/inet_listen")
int BPF_KPROBE(inet_listen_entry, struct socket *sock, int backlog)
{
    __u64 pid_tgid = bpf_get_current_pid_tgid();
    __u32 pid = pid_tgid >> 32;
    __u32 tid = (__u32)pid_tgid;
    struct event event = {};

    if (target_pid && target_pid != pid)
        return 0;

    fill_event(&event, sock);
    event.pid = pid;
    event.backlog = backlog;
    bpf_map_update_elem(&values, &tid, &event, BPF_ANY);
    return 0;
}
#include "solisten.skel.h"
...
int main(int argc, char **argv)
{
    ...
    libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
    libbpf_set_print(libbpf_print_fn);

    obj = solisten_bpf__open();
    obj->rodata->target_pid = target_pid;
    err = solisten_bpf__load(obj);
    err = solisten_bpf__attach(obj);
    pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
                  handle_event, handle_lost_events, NULL, NULL);
    ...
}

五、资源共享阶段

CC也有-RE,但是仍然存在相对固定的问题,无法解决问题,同时B服务器还需要在线编写一个CO配置项目。很酷的资源共享服务器还提供了一个远程编译能力,共享远程资源组件。 Python 的 Go 语言、Rust C 语言,程序只在初始化的时候加载进行这些。

coolbpf 把 BTF 制作、编译、处理功能集一身,生产有效的测试代码,使 BTF 开发进入一个更优雅的状态:

  • 开箱:提供完整的内核,提供完整的内核侧编译工程。
  • 复用编译成果:本地无编译过程,不存在库依赖和CPU、内存等资源消耗问题。
  • 不同版本不同:更适合在不同版本内核共存的场景。
  • 先在本地安装coolbpf,里面带的命令会把xx.bpf.c发送到编译服务器编译。
pip安装coolbpf命令,它会把xx.bpf.c发送到编译服务器编译。
pip install coolbpf

...
import time
from pylcc.lbcBase import ClbcBase

bpfPog = r"""
#include "lbc.h"

SEC("kprobe/wake_up_new_task")
int j_wake_up_new_task(struct pt_regs *ctx)
{
struct task_struct* parent = (struct task_struct *)PT_REGS_PARM1(ctx);

bpf_printk("hello lcc, parent: %d\n", _(parent->tgid));
return 0;
}

char _license[] SEC("license") = "GPL";
"""

class Chello(ClbcBase):
    def __init__(self):
        super(Chello, self).__init__("hello", bpf_str=bpfPog)
        while True:
            time.sleep(1)
            
            if __name__ == "__main__":
                hello = Chello()
    pass

::: hljs-center

二、coolbpf 功能及架构

:::

龙蜥社区开源coolbpf,BPF程序开发效率提升百倍-鸿蒙开发者社区
前面分析了 BPF 的开发方式,coolbpf 一下同时把开发和编译这个进一步的过程优化,总结了它当前包含的 6 大功能:

1)本地服务,基础库包:使用本地和数据库编译程序,提供通用的客户服务程序。
本地本地同样和常用的容器,把容器编译时直接到容器里面。

镜像:
registry.cn-hangzhou.aliyuncs.com/alinux/coolbpf:latest

用户可以通过这个镜像进行本地编译,一些常用的库和省内提供的镜像就已经包含在里面,通过工具制造环境的繁杂。

2)远程编译服务:接收bpf.c,生成bpf.so或bpf.o,提供给高级语言进行加载,用户只专注自己的功能开发,不用关心底层库安装、环境搭建;

远程编译服务,目前用户开发代码时只需要pip install coolbpf,程序就会自动到我们的编译服务器进行编译。 你也可以参考 compile/remote-compile/lbc/自己搭建编译服务器(我们后面会陆续开源这个编译服务器源码),过程可能会比较复杂。这样搭建好的服务器,你可以个人使用或者在公司提供给大家一起使用。

3)高版本特性通过kernel module方式补齐到低版本,如ring buffer特性,或者backport BPF特性到3.10内核;
由于存量3.10内核的服务器依然很多,为了让同一个BPF程序也能运行在低版本内核,为了维护方便且不用修改程序代码,只需要install一个ko,就可以支持BPF,让低版本也享受到了BPF的红利。

4)BTF的自动生成和全网最新内核版本爬虫;自动发现最新的centos,ubuntu,Anolis等内核版本,自动生成对应的BTF;
要具备一次编译多处运行CO-RE能力,没有BTF是行不通的。coolbpf不仅提供一个制作BTF的工具,还会自动发现和制作最新内核版本的BTF,以供大家下载和使用。

5)各内核版本功能测试自动化,工具编写后自动进行安装测试,保障用户功能在生产环境运行前预测试;
没有上线运行过的BPF程序和工具,一定概率上是存在风险的。coolbpf提供一套自动化测试流程,在大部分内核环境都预先进行基本的功能测试,保证工具真正运行在生产环境时不会出大问题。

6)python、rust、go、c等高级语言支持。

目前coolbpf项目支持使用python、rust、go及c语言的用户程序开发,不同语言开发者都能在自己最擅长的领域发挥最大的优势。
总之,coolbpf使得BPF程序和应用程序开发在一个平台上闭环解决了,有效提升了生产力,覆盖了当前主流的开发语言,适合更多的BPF爱好者入门学习,也适合系统运维人员高效开发监控和诊断程序。

下图为coolbpf的功能和工具支持情况,欢迎更多优秀BPF工具加入:
龙蜥社区开源coolbpf,BPF程序开发效率提升百倍-鸿蒙开发者社区

::: hljs-center

三、实践说明

:::

coolbpf 目前包含 pylcc、rlcc、golcc 和 clcc,以及 glcc 目录子,分别是高级语言 Python、Rust 和 Go 语言支持远程和本地编译的能力,glcc(g generic)是代表将高版本的 BPF 特性移植到低版本,通过内核模块的方式在低版本上运行。下面我们分别简单介绍它的使用。

1、pylcc(基于Python的LCC )
pylcc 在 libbpf 基础上进行封装,将复杂的编译工程交由容器执行。

龙蜥社区开源coolbpf,BPF程序开发效率提升百倍-鸿蒙开发者社区

编写代码非常简单,只需要三步即可完成,pyLCC技术关键点:

1)执行 pip install coolbpf 安装

2)xx.bpf.c 的编写:

bpfPog = r""" 
#include "lbc.h"
LBC_PERF_OUTPUT(e_out, struct data_t, 128);
LBC_HASH(pid_cnt, u32, u32, 1024);
LBC_STACK(call_stack,32);

3)xx.py编写,只需要这一步,程序就可以运行起来了。用户就可以关注内核的数据进行分析:

importtimefrompylcc.lbcBaseimportClbcBase
classPingtrace(ClbcBase):def__init__(self):super(Pingtrace, self).__init__("pingtrace")

bpfc里需要主动包含lbc.h,它告诉远程服务器的行为,本地不需要有这个文件。其内容如下:

#include "vmlinux.h"
#include <linux/types.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_tracing.h>

2、rlcc(基于Rust的LCC)
Rust 语言支持远程编译和编译的能力。通过在 makefile 中使用 coolbpf 的命令把 bpf.c 发送到服务端,服务端返回 .o,这个与 Python 和 C 返回 .so 有很大区别,Rust 自己处理通用的加载、附加的过程。其他类似Python的开发,不再赘述。

编译example流程:
SKEL_RS=1 cargo build --release 生成 rust skel 文件;
SKEL_RS=0 cargo build --release 无需在生成 rust skel 文件;
默认 SKEL_RS 为 1.

编译rexample流程:
rexample 使用了远程编译功能,具体编译流程如下:
运行命令 mkdir build & cd build 创建编译目录;
运行命令 cmake .. 生成 Makefile 文件;
运行命令 make rexample;
运行 example 程序: ../lcc/rlcc/rexample/target/release/rexample.
fn main() -> Result<()>{
    let opts = Command::from_args();
    let mut skel_builder = ExampleSkelBuilder::default();
    if opts.verbose {
        skel_builder.obj_builder.debug(true);
    }
    
    bump_memlock_rlimit()?;
    let mut open_skel = skel_builder.open()?;
    let mut skel = open_skel.load()?;
    skel.attach()?;
    let perf = PerfBufferBuilder::new(skel.maps_mut().events())
    .sample_cb(handle_event)
    .lost_cb(handle_lost_events)
    .build()?;
    
    loop {
        perf.poll(Duration::from_millis(100))?;
    }
}

3、glcc(generic LCC,高版本特性移植到低版本)

背景:

  • 基于eBPF编写的内核只能在高版本(目前支持eBPF内核的功能)上运行,无法在不支持eBPF程序的内核上运行。
  • 线上有很多 Alios 或者 CentOS 低版本内核需要维护。
  • 存 BPF 工具或项目,希望不做修改能跨内核运行的量。

为此提出了一种我们在内核上运行任何修改程序的方法,在企业内部B程序不支持BPF的内核上运行。下面从内核上运行,BPF的底层内核。

龙蜥社区开源coolbpf,BPF程序开发效率提升百倍-鸿蒙开发者社区

Hook 是一个动态库,由于低版本内核不支持 bpf() 的系统调用,原来在创建地图、创建 prog 以及 helper 函数(如 bpf_提供_elem 等)将不能运行,Hook 一个动态机制,把系统运行调用转成成的命令,设置到一个叫ebpfdriver的内核模块,通过模拟他进行创建一些数据结构的map和prog,同时注册kprobe和tracepoint的handler。这样有数据到来的时候,先注册在kprobe和tracepoint的说。

运行机制见下图:

龙蜥社区开源coolbpf,BPF程序开发效率提升百倍-鸿蒙开发者社区

利用 Hook 程序将 BPF 的 syscall 转换成 ioctl 形式,将系统调用参数传递给 eBPF 驱动,包含以下功能:

#define IOCTL_BPF_MAP_CREATE _IOW(';', 0, union bpf_attr *)
#define IOCTL_BPF_MAP_LOOKUP_ELEM _IOWR(';', 1, union bpf_attr *)
#define IOCTL_BPF_MAP_UPDATE_ELEM _IOW(';', 2, union bpf_attr *)
#define IOCTL_BPF_MAP_DELETE_ELEM _IOW(';', 3, union bpf_attr *)
#define IOCTL_BPF_MAP_GET_NEXT_KEY _IOW(';', 4, union bpf_attr *)
#define IOCTL_BPF_PROG_LOAD _IOW(';', 5, union bpf_attr *)
#define IOCTL_BPF_PROG_ATTACH _IOW(';', 6, __u32)
#define IOCTL_BPF_PROG_FUNCNAME _IOW(';', 7, char *)
#define IOCTL_BPF_OBJ_GET_INFO_BY_FD _IOWR(';', 8, union bpf_attr *)


eBPF 驱动收到 Ioctl 请求,会根据 cmd 来进行相应的操作,例如:
A. IOCTL_BPF_MAP_CREATE:创建地图。
B. IOCTL_BPF_PROG_LOAD:加载eBPF字节码,进行字节码的安全验证和jit生成机器码。
C. I registerbe_register_PROG_ATTACH:我注册OCBPF程序附加到指定的内核函数,利用eBPF程序的附加功能完成eBPF_kprobe和tracepoint_probe

另外参考一下,高版本的一些特性,比如 ringbuff,也可以通过 ko 等方式用在低版本。 clcc 和 golcc 的使用方式,请参考coolbpf的github链接(见文末),这里不在赘述。

::: hljs-center

四、总结

:::

现在coolbpf,它的目的是为了更好地开发和构建程序,让用户更关心自己的开发功能,揭开BPF的功能,让我们快速编写自己的环境问题功能。今天把快速编组系统开源,为更多人服务,以提升他们的生产力,促进社会进步,让更多人参与到这个项目建设中来,形成一股合力,成就卓越技术。

我们的编译器,解决产品生产的问题进来共同提高,让云计算产业和企业服务的兄弟姐妹们全面享受到 BPF 技术的红利。
粗体
龙蜥社区系统维维SIG(特别兴趣小组)致力于打造一个集主机管理、配置部署、监控项目报警、诊断、安全审计等异常运维功能的自动化运维平台,coolbpf 是社区的一个子项目,目标是提供一个编译和开发平台,解决BPF在不同系统平台的运行和生产效率提升问题。

欢迎更多开发者加入系统运维 SIG:
网址:https://openanolis.cn/sig/sysom
邮件列表:sysom@lists.openanolis.cn
coolbpf 链接:git@github.com:aliyun/coolbpf.git

::: hljs-center

——完——
加入龙蜥社群

:::

加入微信群添加助理社区-龙蜥社区小龙(微信:openanolis_assis),【龙蜥】与你同在;加入钉钉:批注:标注栏钉钉群二维码。欢迎开发者/用户加入龙社区(OpenAnolis,共同健康推动龙蜥社区的发展,共同打造一个活跃的、开源的开源生态系统!

关于龙蜥社区

龙蜥(Open Anolis)由企事业、高等院校、科研、性别组织、个人等单位在社区的自主、平等、开源、合作上组成的非性开源社区。龙蜥成立于2020年9 月 9 日,公开发布、公开发布的 Linux 公开版和创新平台。

龙社区成立的短期目标是开发龙系列操作系统(Anolis OS)的目标方案,打造一个C之后开发的目标是作为一个C系列的目标,作为未来的龙头社区发布的目标。 ,建立统一的开源运营,开展创新开源项目,开创生态生态。

目前,Anolis OS 8.6已发布,更多龙蜥自研特性,支持 X86_64 、RISC-V、Arm64、LoongArch 架构,优化适配英特尔、发布兆鹏、鲲、龙栈芯芯等芯片,并提供全国密支持。

欢迎下载:
https://openanolis.cn/download

加入我们,一起打造未来的开源操作系统!
https://openanolis.cn

1
收藏
回复
举报
回复
    相关推荐