/* Note that tracing related programs such as
* BPF_PROG_TYPE_{KPROBE,TRACEPOINT,PERF_EVENT,RAW_TRACEPOINT}
* are not subject to a stable API since kernel internal data
* structures can change from release to release and may
* therefore break existing tracing BPF programs. Tracing BPF
* programs correspond to /a/ specific kernel which is to be
* analyzed, and not /a/ specific kernel /and/ all future ones.
*/
enum bpf_prog_type {
BPF_PROG_TYPE_UNSPEC,
BPF_PROG_TYPE_SOCKET_FILTER,
BPF_PROG_TYPE_KPROBE,
BPF_PROG_TYPE_SCHED_CLS,
BPF_PROG_TYPE_SCHED_ACT,
BPF_PROG_TYPE_TRACEPOINT,
BPF_PROG_TYPE_XDP,
BPF_PROG_TYPE_PERF_EVENT,
BPF_PROG_TYPE_CGROUP_SKB,
BPF_PROG_TYPE_CGROUP_SOCK,
BPF_PROG_TYPE_LWT_IN,
BPF_PROG_TYPE_LWT_OUT,
BPF_PROG_TYPE_LWT_XMIT,
BPF_PROG_TYPE_SOCK_OPS,
BPF_PROG_TYPE_SK_SKB,
BPF_PROG_TYPE_CGROUP_DEVICE,
BPF_PROG_TYPE_SK_MSG,
BPF_PROG_TYPE_RAW_TRACEPOINT,
BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
BPF_PROG_TYPE_LWT_SEG6LOCAL,
BPF_PROG_TYPE_LIRC_MODE2,
BPF_PROG_TYPE_SK_REUSEPORT,
BPF_PROG_TYPE_FLOW_DISSECTOR,
BPF_PROG_TYPE_CGROUP_SYSCTL,
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
BPF_PROG_TYPE_CGROUP_SOCKOPT,
BPF_PROG_TYPE_TRACING,
BPF_PROG_TYPE_STRUCT_OPS,
BPF_PROG_TYPE_EXT,
BPF_PROG_TYPE_LSM,
BPF_PROG_TYPE_SK_LOOKUP,
BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
};
概述
BPF是什么?
BPF
全称是伯克利包过滤器(Berkeley Packet Filter),最早是伯克利大学发明用于内核实现网络数据包过滤的。 因设计理念新和性能高,发展到现在名称升级为eBPF
(extended Berkeley Packet Filter),同时功能也支持的更多,不再仅仅是网络分析,可以基于eBPF
开发性能分析、系统追踪、网络优化等。eBPF 由 执行字节码指令、存储对象 和 辅助函数 组成。
BPF有什么作用?
有时候需要改动Linux内核实现一些功能,首先要考虑 “安全性”,不能允许不可信的代码运行在内核中,其次还要考虑“高性能”和“持续交付”。 我们可以通过开发Linux模块实现,但是需要有一定的Linux内核基础,并且随着Linux版本迭代,开发的Linux模块可能需要改动才能运行,搞不好就把Linux内核搞挂了。而BPF很好的解决了上述问题:
强安全:BPF验证器(verifier)会保证每个程序能够安全运行,它会去检查将要运行到内核空间的程序的每一行是否安全可靠,如果检查不通过,它将拒绝这个程序被加载到内核中去,从而保证内核本身不会崩溃,这是不同于开发内核模块的。
高性能:一旦通过了BPF验证器,那么它就会进入JIT编译阶段,利用Just-In-Time编译器,编译生成的是通用的字节码,它是完全可移植的,可以在x86和ARM等任意球CPU架构上加载这个字节码,这样我们能获得本地编译后的程序运行速度,而且是安全可靠的。
持续交付:通过JIT编译后,就会把编译后的程序附加到内核中各种系统调用的钩子(hook)上,而且可以在不影响系统运行的情况下,实时在线地替换这些运行在Linux内核中的BPF程序。
BPF是怎么工作的?
eBPF 程序在事件触发时由内核运行,所以可以被看作是一种函数挂钩或事件驱动的编程形式。 事件可由 kprobes/uprobes、tracepoints、dtrace probes、socket 等产生。 这允许在内核和用户进程的指令中钩住(hook)和检查任何函数的内存、拦截文件操作、检查特定的网络数据包等等。
运行 eBPF 程序的步骤:
eBPF 程序有哪些组件?
eBPF虚拟机
eBPF 是一个 RISC 寄存器机,共有 11 个 64 位寄存器,一个程序计数器和 512 字节的固定大小的栈。9 个寄存器是通用读写的,1 个是只读栈指针,程序计数器是隐式的。 eBPF 指令固定大小的 64 位编码,目前大约有 100 条指令,被分组8类指令 eBPF 程序类型决定了哪些内核函数的子集可以被调用
eBPF 字节码指令数组:
安装
参考: https://github.com/fbs/el7-bpf-specs/blob/master/README.md#repository
安装好以后,输出hello world 测试一下。
如果有上述报错,可能需要安装 kernel-headers ,
yum install kernel-headers
。在ubuntu22.04安装
如果有报“ERROR: Could not resolve symbol: /proc/self/exe:BEGIN_trigger”则需要安装“bpftrace-dbgsym”:
正确安装后测试结果:
// 输出当前系统调用跟踪点
bpftrace -l 'tracepoint:syscalls:*'
// 查看对应系统调用的参数
bpftrace -lv 'tracepoint:syscalls:sys_exit_open'
下面是个简单的例子,跟踪系统调用
accept*
、connect
、bind
、socket*
和 内核函数recvmsg
、sendmsg
每3s输出 那些命令调用这些系统调用和内核函数以及调用的次数。下面的例子是调用
connect
和accept
accept4
的命令和对应的次数。// 跟踪connect,本机主动连接 // tcpconnect.sh
跟踪syn重传
基础
BPF 程序类型
// include/uapi/linux/bpf.h
// linux/include/linux/bpf.h
BPF attach 类型
通过
socket()
系统调用将 BPF 程序 attach 到 hook 点时用到,Socket 相关类型
BPF_PROG_TYPE_SOCKET_FILTER
err = sk_filter(sk, skb); if (err) return err;
return __sock_queue_rcv_skb(sk, skb); }
XDP(eXpress Data Path)程序
BPF Map
// linux/include/linux/bpf.h
linux/include/linux/bpf_types.h
参考
https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md https://arthurchiao.art/articles-zh/ https://github.com/iovisor/bpf-docs/blob/master/eBPF.md