HIDS-ebpf-0

背景

简单介绍ebpf最早就是从 tcpdump 中用作网络包过滤的经典 cbpf,到成为通用 Linux 内核技术的 eBPF,已经完成华丽蜕变,为应用与神奇的内核打造了一座桥梁,在系统跟踪、观测、性能调优、安全和网络等领域发挥重要的角色。很多技术都有用到ebpf; 如: Cilium,Faclo, DataDog等。

功能点

ebpf可以用来做什么呢?可以分两类:
网络运维:

  • 网络负载均衡,加速网络(阿里有出过博客,详情见参考链接)
  • 网络分流,其它还等个人脑洞

网络安全:

  • 网络微隔离acl
  • 防ddos
  • 可以做hids(这个美团有人已经在弄啦,并已开源)
  • sdp网关(个人感觉可行)

注意:不同linux内核版本,开放的ebpf功能特性也不一样,可查看官网对应每个linux内核版本的功能,5.x以上基本算全的。

开发

纯c方式

安装依赖

apt-get update
apt install -y bison build-essential cmake flex git libedit-dev pkg-config libmnl-dev \
   python zlib1g-dev libssl-dev libelf-dev libcap-dev libfl-dev \
   gcc-multilib luajit libluajit-5.1-dev libncurses5-dev vim wget llvm clang libclang-dev clang-tools

编译安装内核

cd /usr/src/
wget https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/snapshot/linux-5.10.9.tar.gz
tar zxvf linux-5.10.9.tar.gz -C /usr/src/

# asm报错
cd /usr/include
ln -s ./x86_64-linux-gnu/asm asm
# 缺少libbpf, 源码安装
$ git clone https://github.com/libbpf/libbpf.git
$ cd libbpf/src
$ make -j8 && make install
# 缺少libbpf, apt安装
apt install rsync libbpfcc-dev -y

# 在源码根目录下使用
make defconfig    #生成.config文件,可进行配置

### 或者另一种安装方式,如下:
apt install linux-tool-$(uname -r) linux-head-$(uname -r)
# 查看已安装的内核源码
cd /usr/src/

# 注解: 
# 关联内核头文件:平时我们在编译应用程序的时候,不可避免的会使用内核头文件,比如v4l2,字符驱动等等。此时直接的使用内核源码中include目录下的头文件,可能就会有警告;
# make headers_install ARCH=arm INSTALL_HDR_PATH=/usr/include  其中ARCH指定要产生哪种体系结构的头文件,INSTALL_HDR_PATH指定要导出头文件的目录。
# 如果只是执行make headers_install ,则导出所有体系结构的头文件到默认目录”./usr/include“

make headers_install
# 报错:modpost: not found
make modules_prepare

# 编译内核bpf样例
make M=samples/bpf
可以在samples/bpf/文件夹中看到已经生成了bpf的可执行文件

最后在samples/bpf文件夹写自己的bpf程序

cd samples/bpf
vim hello_kern.c

hello_kern.c

#include <linux/bpf.h>
#include "bpf_helpers.h"
#include "trace_helpers.h"
#define SEC(NAME) __attribute__((section(NAME), used))

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx){
	char msg[] = "JinRong's first bpf program!\n";
	bpf_trace_printk(msg, sizeof(msg));
	return 0;
}

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

hello_user.c

#include <stdio.h>
#include "bpf_load.h"

int main(int argc, char **argv){
	if(load_bpf_file("hello_kern.o")!=0){
		printf("The kernel didn't load BPF program\n");
		return -1;
	}

	read_trace_pipe();
	return 0;
}

vim Makefile

# 在hostprogs-y += hbm下,增加:
tprogs-y += hello
# 在hbm-objs := bpf_load.o hbm.o $(CGROUP_HELPERS)下,增加:
hello-objs := bpf_load.o hello_user.o  $(TRACE_HELPERS)
# 在hbm-objs := bpf_load.o hbm.o $(CGROUP_HELPERS)下,增加:
# 在always-y += xdpsock_kern.o下,增加:
always-y += hello_kern.o

切换到内核源码目录, 编译内核bpf样例

make M=samples/bpf
# V=1 查看详细编译输出
make M=samples/bpf V=1
./hello

BCC方式

bcc 这里采用docker安装方式,dockerfile如下:

FROM docker/for-desktop-kernel:5.10.76-505289bcc85427a04d8d797e06cbca92eee291f4 AS ksrc

FROM ubuntu:impish AS bpftrace
COPY --from=ksrc /kernel-dev.tar /
RUN tar xf kernel-dev.tar && rm kernel-dev.tar
# Use Alibaba Cloud mirror for ubuntu
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/' /etc/apt/sources.list
# Install LLVM 11.0.0
RUN apt-get update && apt install -y wget lsb-release software-properties-common
RUN apt install -y llvm-11-dev libdebuginfod-dev
ENV PATH "$PATH:/usr/lib/llvm-11/bin"

# Build/Install bpftrace
RUN apt-get install -y bpftrace
# libbpf-dev 

# Build/Install bcc
WORKDIR /root
RUN DEBIAN_FRONTEND="noninteractive" apt install -y bpfcc-tools libbpfcc-dev libbpf-dev kmod vim bison build-essential cmake flex git libedit-dev \
    libcap-dev zlib1g-dev libelf-dev libfl-dev python3.9 python3-pip python3.9-dev clang libclang-dev libclang-11-dev arping netperf iperf libluajit-5.1-2 libluajit-5.1-dev && \
    ln -s $(which python3) /usr/bin/python
RUN git clone https://github.com/iovisor/bcc.git && \
    mkdir bcc/build && \
    cd bcc/build && \
    cmake .. && make && make install && cmake -DPYTHON_CMD=python3 .. && cd src/python/ && make && make install && \
    sed -i "s/self._syscall_prefixes\[0\]/self._syscall_prefixes\[1\]/g" /usr/lib/python3/dist-packages/bcc/__init__.py

CMD mount -t debugfs debugfs /sys/kernel/debug && /bin/bash

# 注意,eBPF 程序的执行也依赖于调试文件系统。如果你的系统没有自动挂载它,那么我推荐你把它加入到系统开机启动脚本里面,这样机器重启后 eBPF 程序也可以正常运行。
测试demo

新建一个 hello.c 文件

int hello_world(void *ctx)
{
   bpf_trace_printk("Hello, World!");
   return 0;
}

创建一个 hello.py 文件

 #!/usr/bin/env python3
# 1) import bcc library
from bcc import BPF

# 2) load BPF program
b = BPF(src_file="hello.c")

# 3) attach kprobe
b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")

# 4) read and print /sys/kernel/debug/tracing/trace_pipe
b.trace_print()

运行程序

python3 hello.py

解释下吧:
第 1) 处导入了 BCC  库的 BPF 模块,以便接下来调用;
第 2) 处调用 BPF() 加载第一步开发的 BPF 源代码;
第 3) 处将 BPF 程序挂载到内核探针(简称 kprobe),其中 do_sys_openat2() 是系统调用 openat() 在内核中的实现;
第 4) 处则是读取内核调试文件 /sys/kernel/debug/tracing/trace_pipe 的内容,并打印到标准输出中。在运行的时候,BCC 会调用 LLVM,把 BPF 源代码编译为字节码,再加载到内核中运行。

输出的格式可由  /sys/kernel/debug/tracing/trace_options  来修改。比如前面这个默认的输出中,每个字段的含义如下所示:
cat-10656 表示进程的名字和 
PID;[006] 表示 CPU 编号;
d… 表示一系列的选项;
2348.114455 表示时间戳;
bpf_trace_printk 表示函数名;
最后的 “Hello, World!” 就是调用  bpf_trace_printk()  传入的字符串。

demo2太多,我就直接贴代码和解释:

#------------------- c代码
#include <uapi/linux/openat2.h>
#include <linux/sched.h>

struct data_t {
  u32 pid;
  u64 ts;
  char comm[TASK_COMM_LEN];
  char fname[NAME_MAX];
};

BPF_PERF_OUTPUT(events);

// 定义kprobe处理函数
int hello_world(struct pt_regs *ctx, int dfd, const char __user * filename, struct open_how *how)
{
  struct data_t data = { };

  // 获取PID和时间
  data.pid = bpf_get_current_pid_tgid();
  data.ts = bpf_ktime_get_ns();

  // 获取进程名
  if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0)
  {
    bpf_probe_read(&data.fname, sizeof(data.fname), (void *)filename);
  }

  // 提交性能事件
  events.perf_submit(ctx, &data, sizeof(data));
  return 0;
}

解释:
其中,以 bpf 开头的函数都是 eBPF 提供的辅助函数,比如:
bpf_get_current_pid_tgid 用于获取进程的 TGID 和 PID。因为这儿定义的 data.pid 数据类型为 u32,所以高 32 位舍弃掉后就是进程的 PID;
bpf_ktime_get_ns 用于获取系统自启动以来的时间,单位是纳秒;
bpf_get_current_comm 用于获取进程名,并把进程名复制到预定义的缓冲区中;
bpf_probe_read 用于从指定指针处读取固定大小的数据,这里则用于读取进程打开的文件名。
辅助函数参考:https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#output

#------------------- hello2.py 
from bcc import BPF

# 1) load BPF program
b = BPF(src_file="trace-open.c")
b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")

# 2) print header
print("%-18s %-16s %-6s %-16s" % ("TIME(s)", "COMM", "PID", "FILE"))

# 3) define the callback for perf event
start = 0
def print_event(cpu, data, size):
    global start
    event = b["events"].event(data)
    if start == 0:
            start = event.ts
    time_s = (float(event.ts - start)) / 1000000000
    print("%-18.9f %-16s %-6d %-16s" % (time_s, event.comm, event.pid, event.fname))

# 4) loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()



解释:
第 1) 处跟前面的 Hello World 一样,加载 eBPF 程序并挂载到内核探针上;
第 2) 处则是输出一行 Header 字符串表示数据的格式;
第 3) 处的 print_event 定义一个数据处理的回调函数,打印进程的名字、PID 以及它调用 openat 时打开的文件;
第 4) 处的 open_perf_buffer 定义了名为 “events” 的 Perf 事件映射,而后通过一个循环调用 perf_buffer_poll 读取映射的内容,并执行回调函数输出进程信息。

CO-RE方式

co-re实现(Compile once, run everywhere),借助 Linux kernel 提供的 BTF、bpftool 与 libbpf,能够将 eBPF 二进制与用户态控制程序封装至单个 ELF 中,实现 CO-RE(Compile once, run everywhere),不必再担心因 memory layout 的变更导致 eBPF 二进制不再可用。只要 kernel 的 eBPF 功能具备相应的 feature,它就能正常地运行在该 kernel 之上。并且借助 BTF,编译时不必需要 kernel header。使用 bcc 编写的 eBPF 代码运行时编译,不仅需要 kernel header,而且需携带 llvm/clang 相关的二进制。此外,因 kernel struct 的变更导致 memory layout 产生了变化,无法令编译生成的 eBPF 二进制运行在任意版本 Linux kernel 中,这就无法将 eBPF 二进制与用户态控制程序打包成二进制进行分发。

检查配置

有些版本高的已都直接开启(linux 内核4.x以上),不用配置; 低版本内核在使用 CO-RE 之前,内核需要开启 CONFIG_DEBUG_INFO_BTF=y 和 CONFIG_DEBUG_INFO=y 这两个编译选项。

# 检查内核配置
zcat /proc/config.gz

若检查内核配置选项与上述检查项不符,在/usr/src目录下使用 make menuconfig命令设置内核配置选项:
在这里插入图片描述
设置完成后需要编译内核。

安装依赖
apt-get install -y make clang llvm libelf-dev libbpf-dev bpfcc-tools libbpfcc-dev 

源码安装libbpf

$ git clone https://github.com/libbpf/libbpf.git
$ cd libbpf/src
$ make -j8 && make install

源码安装libbpf-tool

$ git clone https://github.com/iovisor/bcc.git
$ cd bcc/libbpf-tools
$ git submodule update --init --recursive
$ make -j8 && make install
# 编译libbpf-tool, 请检查配置:CONFIG_DEBUG_INFO_BTF=y和CONFIG_DEBUG_INFO=y
安装

使用 BPF CO-RE 构建基于 libbpf 的 BPF 应用程序包括几个步骤,如下:

  1. 导出当前运行的 kernel 定义的各类变量类型(v5.2 添加该 feature)
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h 

2.在 eBPF 代码中声明的头文件如下:

# include "vmlinux.h"
# include <bpf/bpf_helpers.h>
# include <bpf/bpf_core_read.h>
# include <bpf/bpf_tracing.h>
  1. 使用最新的 Clang(版本 10 或更高版本)将 BPF 程序源代码编译到.o目标文件中
  2. 从编译的 BPF 目标文件生成 BPF 骨架头文件;
bpftool gen skeleton <file> > <file>.skel.h
  1. 包括从用户空间代码中使用的生成的 BPF 骨架头;
#include "<file>.skel.h"
解决内存限制

BCC 无条件将此限制设置为无穷大,但 libbpf 不会自动执行此操作(按设计)。
根据自己的生产环境,可能会有更好和更首选的方法来执行此操作。为了快速实验或者如果没有更好的方法来做这件事,可以通过setrlimit(2)系统调用自己做,可以在程序的最开始被调用:

 #include <sys/resource.h>

    rlimit rlim = {
        .rlim_cur = 512UL << 20, /* 512 MBs */
        .rlim_max = 512UL << 20, /* 512 MBs */
    };

    err = setrlimit(RLIMIT_MEMLOCK, &rlim);
    if (err)
        /* handle error */
整体如下
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
$ clang -target bpf -nostdinc -isystem /usr/lib64/gcc/x86_64-suse-linux/10/include -I/usr/include -g -D__x86_64__ -O2 -emit-llvm -Xclang -disable-llvm-passes -c connect_kern.c -o - | opt -O2 -mtriple=bpf-pc-linux | llvm-dis | llc  -march=bpf -filetype=obj -o connect_kern.o
$ bpftool gen skeleton connect_kern.o > connect_kern.skel.h
$ clang -o connect connect.c -lbpf  -lelf
$ ldd connect
         linux-vdso.so.1 (0x00007ffcaa9ed000)
         libbpf.so.0 => /usr/lib64/libbpf.so.0 (0x00007f4f8f982000)
         libelf.so.1 => /usr/lib64/libelf.so.1 (0x00007f4f8f967000)
         libc.so.6 => /lib64/libc.so.6 (0x00007f4f8f798000)
         libz.so.1 => /usr/lib64/libz.so.1 (0x00007f4f8f77e000)
         /lib64/ld-linux-x86-64.so.2 (0x00007f4f8f9cd000)
libbpf-bootstrap

直接使用已有的框架, 安装好依赖libelf-dev和libz-dev,下载libbpf-bootstrap进行编写。
这里不多描述,直接参考:https://nakryiko.com/posts/libbpf-bootstrap/ ,
这也是最方便快捷的方式。

其它

bpftool使用

$ sudo bpftool prog list
# 89: kprobe  name hello_world  tag 38dd440716c4900f  gpl      loaded_at 2021-11-27T13:20:45+0000  uid 0      xlated 104B  jited 70B  memlock 4096B      btf_id 131      pids python3(152027

# 解释: 输出中,89 是这个 eBPF 程序的编号,kprobe 是程序的类型,而 hello_world 是程序的名字。

有了 eBPF 程序编号之后,执行下面的命令就可以导出这个 eBPF 程序的指令(注意把 89 替换成你查询到的编号):

$ sudo bpftool prog dump xlated id 89

你会看到如下:
int hello_world(void * ctx):
; int hello_world(void *ctx)
   0: (b7) r1 = 33                  /* ! */
; ({ char _fmt[] = "Hello, World!"; bpf_trace_printk_(_fmt, sizeof(_fmt)); });
   1: (6b) *(u16 *)(r10 -4) = r1
   2: (b7) r1 = 1684828783          /* dlro */
   3: (63) *(u32 *)(r10 -8) = r1
   4: (18) r1 = 0x57202c6f6c6c6548  /* W ,olleH */
   6: (7b) *(u64 *)(r10 -16) = r1
   7: (bf) r1 = r10
;
   8: (07) r1 += -16
; ({ char _fmt[] = "Hello, World!"; bpf_trace_printk_(_fmt, sizeof(_fmt)); });
   9: (b7) r2 = 14
  10: (85) call bpf_trace_printk#-61616
; return 0;
  11: (b7) r0 = 0
  12: (95) exit


其中,分号开头的部分,正是我们前面写的 C 代码,而其他行则是具体的 BPF 指令。具体每一行的 BPF 指令又分为三部分:
第一部分,冒号前面的数字 0-12 ,代表 BPF 指令行数;
第二部分,括号中的 16 进制数值,表示 BPF 指令码。它的具体含义你可以参考 IOVisor BPF 文档,比如第 0 行的 0xb7 表示为 64 位寄存器赋值。
第三部分,括号后面的部分,就是 BPF 指令的伪代码。
结合前面讲述的各个寄存器的作用,不难理解这些 BPF 指令的含义:
第 0-8 行,借助 R10 寄存器从栈中把字符串 “Hello, World!” 读出来,并放入 R1 寄存器中;
第 9 行,向 R2 寄存器写入字符串的长度 14(即代码注释里面的 sizeof(_fmt) );
第 10 行,调用 BPF 辅助函数 bpf_trace_printk 输出字符串;
第 11 行,向 R0 寄存器写入 0,表示程序的返回值是 0;最后一行,程序执行成功退出。总结起来,这些指令先通过 R1 和 R2 寄存器设置了 bpf_trace_printk 的参数,然后调用 bpf_trace_printk 函数输出字符串,最后再通过 R0 寄存器返回成功。

实际上,你也可以通过类似的 BPF 指令(https://man7.org/linux/man-pages/man2/bpf.2.html#EXAMPLES)来开发 eBPF 程序(具体指令的定义,请参考include/uapi/linux/bpf_common.h[https://elixir.bootlin.com/linux/v5.4/source/include/uapi/linux/bpf_common.h] 以及 include/uapi/linux/bpf.h),不过通常并不推荐你这么做。跟一开始的 C 程序相比,你会发现 BPF 指令的可读性和可维护性明显要差得多。所以,我建议你还是使用 C 语言来开发 eBPF 程序,而只把  BPF 指令作为排查 eBPF 程序疑难杂症时的参考。

查询当前系统支持的辅助函数列表

$ bpftool feature probe 

参考

官方文档
https://ebpf.io/
网络策略.
https://www.bilibili.com/video/BV1By4y1x7HE?from=search&seid=8091379188102042415&spm_id_from=333.337.0.0
eBPF 和 Wasm:探索服务网格数据平面的未来
https://mp.weixin.qq.com/s/LpCDeMT1Omqzf_DV9ypyJA
cilium中文目录文档
https://www.jianshu.com/p/96e5f77a3409
ebpf&go
https://www.ebpf.top/post/ebpf_go_translation/

https://mozillazg.com/2021/04/ebpf-gobpf-dev-env-and-hello-first-program.html
https://blog.csdn.net/weixin_43144516/article/details/117871024
https://github.com/pathtofile/bad-bpf

ebpf系列视频
https://www.bilibili.com/video/BV1rX4y1G7qd?p=7
https://nakryiko.com/posts/bcc-to-libbpf-howto-guide/

HOWTO: BCC to libbpf conversion
https://facebookmicrosites.github.io/bpf/blog/2020/02/20/bcc-to-libbpf-howto-guide.html
美团基于ebpf
https://mp.weixin.qq.com/s/-1GiCncNTqtfO_grQT7cGw
https://www.cnxct.com/lessons-using-ebpf-accelerating-cloud-native-zh/?f=github#i-3
比较全的笔记:
https://blog.gmem.cc/ebpf

阿里的基于ebpf优化网络的文章,后续继续补上,忘记笔记放哪里啦!

其它资源

linux系统内核地址
https://mirrors.aliyun.com/linux-kernel/?spm=a2c6h.13651104.0.0.72a537383exZoL
linux在线系统源码地址
https://elixir.bootlin.com/linux/latest/source
linux系统apt阿里源地址
https://mirrors.aliyun.com/ubuntu/dists/
ubunu的apt依赖包搜索
https://linux-packages.com/search-page?p=libdebuginfod-dev&st=contain&d%5B%5D=1
https://packages.ubuntu.com/search?keywords=libdebuginfod-dev
容器镜像
https://hub.docker.com/r/docker/for-desktop-kernel/tags?spm=a2c6h.12873639.0.0.3e805798fvG1h3

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值