eBPF入门系列:学习资料及概念

学习资料

  • https://www.ebpf.top/

  • https://github.com/DavadDi/bpf_study

  • https://github.com/nevermosby/linux-bpf-learning

  • https://davidlovezoe.club/wordpress/archives/tag/bpf

  • https://github.com/Asphaltt/learn-by-example

  • cilium文档:BPF和XDP参考指南

  • XDP教程:https://github.com/xdp-project

  • eBPF和GO: https://networkop.co.uk/post/2021-03-ebpf-intro/

eBPF介绍

使用LLVM/clang编译bpf程序为bpf字节码;

可以通过bcc/bpftrace开发bpf程序;

BPF Maps

BPF Maps可以被内核和用户空间同时访问,可用于用户态程序读取内核的数据,也可以从用户态向内核态bpf程序传配置;

源码定义:https://elixir.bootlin.com/linux/v5.15.86/source/include/uapi/linux/bpf.h#L878

内核文档定义:https://docs.kernel.org/bpf/maps.html

BPF Program

bpf程序类型:kprobe(性能低、灵活性高)、tracepoints(性能高、灵活性低)

helper函数:https://man7.org/linux/man-pages/man7/bpf-helpers.7.html

bpf系统调用函数:https://man7.org/linux/man-pages/man2/bpf.2.html

编译

注意点

1、clang编译器可以通过 -g 参数生成CO-RE需要的内核数据结构BTF文件;

2、gcc 12版本编译 BPF 程序支持 CO-RE 特性;

检查 eBPF 对象文件

$ file hello.bpf.o
hello.bpf.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), with debug_info, not stripped
$ llvm-objdump -S hello.bpf.o
hello.bpf.o: file format elf64-bpf 
Disassembly of section xdp: 
0000000000000000 <hello>: 
; bpf_printk("Hello World %d", counter"); 
 0: 18 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r6 = 0
ll
 2: 61 63 00 00 00 00 00 00 r3 = *(u32 *)(r6 + 0)
 3: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0
ll
 5: b7 02 00 00 0f 00 00 00 r2 = 15
 6: 85 00 00 00 06 00 00 00 call 6
; counter++; 
 7: 61 61 00 00 00 00 00 00 r1 = *(u32 *)(r6 + 0)
 8: 07 01 00 00 01 00 00 00 r1 += 1
 9: 63 16 00 00 00 00 00 00 *(u32 *)(r6 + 0) = r1
; return XDP_PASS; 
 10: b7 00 00 00 02 00 00 00 r0 = 2
 11: 95 00 00 00 00 00 00 00 exit

加载并挂载程序

// xdp类型程序
$ ip link set dev eth0 xdp obj hello.bpf.o sec xdp
$ ip link set dev eth0 xdp off

BPF程序和挂载类型

BPF程序类型、挂载类型和ELF Sections 对应关系 libbpf 文档

Tracing(跟踪类型)

  • Kprobes 和 Kretprobes

    kprobes 用于挂载到内核函数入口点,也可以通过指令挂载到入口点偏移量位置(不稳定);kretprobes 用于挂载到内核函数退出点。

    bpf程序参数类型根据挂载点类型而不同,函数第一个参数为函数名。

    # 挂载kprobe程序到execve系统调用入口
    SEC("ksyscall/execve")
    int BPF_KPROBE_SYSCALL(kprobe_sys_execve, char *pathname)
      
    # 挂载kprobe程序到内核函数do_execve入口
    SEC("kprobe/do_execve")
    int BPF_KPROBE(kprobe_do_execve, struct filename *filename)
  • Fentry 和 Fexit

    内核版本5.5引入的更高效的挂载到内核函数入口点和出口点的程序类型。

    kprobe类型程序代码可以无缝迁移到fentry。

    fexit类型程序还可以访问函数入参,而kretprobes不能

    # 挂载kentry程序到内核函数do_execve入口
    SEC("fentry/do_execve")
    int BPF_PROG(fentry_execve, struct filename *filename)
     
    # kretprobe和fexit挂载相同内核函数do_unlinkat,fexit可以拿到函数入参
    SEC("kretprobe/do_unlinkat")
    int BPF_KRETPROBE(do_unlinkat_exit, long ret)
     
    SEC("fexit/do_unlinkat")
    int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)
  • Tracepoints

    tracepoints 是跟踪内核中被标记的代码位置。tracepoints在不同内核版本相对稳定

    查看当前版本内核可用的子系统跟踪点通过文件 /sys/kernel/tracing/available_events

    查看跟踪点函数参数方式,以execve函数入口点系统调用为例:cat /sys/kernel/tracing/events/syscalls/sys_enter_execve/format

    Raw_tracepoints类型程序相对于tracepoints可以获得更好的性能,通过修改 raw_tp/raw_tracepoint代替tp。

    # tracepoints类型程序SEC配置格式
    SEC("tp/tracingsubsystem/tracepoint name")
    
    SEC("tp/syscalls/sys_enter_execve")
    
  • BTF-Enabled Tracepoints

    启动BTD的tracepoints,解决内核数据接口在不同版本定义发生变化问题。

    # SEC定义格式:
    SEC("tp_btf/*tracepoint name*") 
    
    # 示例
    SEC("tp_btf/sched_process_exec")
    int handle_exec(struct trace_event_raw_sched_process_exec *ctx)

    函数第一个参数结构体名字格式:trace_event_raw_+跟踪点名

  • Uprobes and Uretprobes

    uprobes和uretprobes挂载用户空间函数入口点和退出点,USDTs (user statically defined tracepoints)挂载特殊的tracepoints到用户空间的应用代码/库文件。

    复用 kprobes 程序类型定义。

    SEC("uprobe/usr/lib/aarch64-linux-gnu/libssl.so.3/SSL_write")

    在检测⽤⼾空间代码时需要注意⼀些问题:...

  • LSM

    BPF_PROG_TYPE_LSM类型程序用于挂载到Linux安全模块API,

Networking

需要的Linux capabilities: CAP_NET_ADMIN, CAP_BPF, CAP_SYS_ADMIN。

BPF程序类型在网络协议栈的钩子

使用场景:

1、通过 bpf 程序告诉内核如何处理数据包:重定向、丢弃、正常处理;

2、修改数据包或者socket参数;

  • Sockets

    程序类型:

    • BPF_PROG_TYPE_SOCKET_FILTER:过滤复制到可观察工具(tcpdump)sockets。

    • BPF_PROG_TYPE_SOCK_OPS:允许拦截或者操作socket,例如设置socket各种参数

    • BPF_PROG_TYPE_SK_SKB:与sockmap一起使用,用于重定向socket流量

  • Traffic Control

    eBPF程序可以挂载到自定义的filters或者classifiers,出入向都可以。

    使用tc命令操作ebpf 程序

    # 创建 clsact 类型的排队规则
    sudo tc qdisc add dev eth0 clsact
    # 加载接收方向的 eBPF 程序
    sudo tc filter add dev eth0 ingress bpf da obj tc-example.o sec ingress
    # 加载发送方向的 eBPF 程序
    sudo tc filter add dev eth0 egress bpf da obj tc-example.o sec egress
    # 查看eBPF程序
    tc filter show dev lxc17451654ad87 ingress
    tc filter show dev lxc17451654ad87 egress
  • XDP

    可以使用ip link命令操作XDP程序

    只能作用于ingress方向数据包,无法针对egress方向数据包

  • Flow Dissector(流解剖器)

    BPF_PROG_TYPE_FLOW_DISSECTOR: 可以自定义解剖数据包

  • Lightweight Tunnels

    BPF_PROG_TYPE_LWT_* 程序类型 : 实现数据包封装

  • Cgroups

    用于控制一个Cgroup中的进程组的行为,比如控制一个Cgroup的socket是否被请求或者传输

    程序类型:

    • BPF_CGROUP_SYSCTL:控制sysctl命令

    • BPF_PROG_TYPE_CGROUP_SOCK :

    • BPF_PROG_TYPE_CGROUP_SKB

  • Infrared Controllers

环境依赖

是BPF程序一次编译随处运行(CO-RE:Compile Once-Run Everywhere)的关键,解决了 eBPF 程序在不同内核版本之间可移植的问题.

# cat /boot/config-`uname -r` | grep CONFIG_DEBUG_INFO_BTF
CONFIG_DEBUG_INFO_BTF=y

Last updated