更高,更快,更强
0x0 前情提要
毕业工作后,eBPF技术是我在工作中学习并成功使用的一项技术,成功使用这个技术让我非常有成就感。我认为这项技术大有可为,在此与大家分享。
eBPF并不是一个很简单的东西,学习起来会遇到坑,会吃力,但是我觉得,当一个人离开他的舒适区的时候,就意味着他的舒适区变大了。
本文作为eBPF系列文章的开头,主要简单给大家介绍eBPF技术,相关内容也是取自网上。
0x1 什么是eBPF
eBPF全称是extended Berkeley Packet Filter,它的前身是cBPF(classic BPF),诞生于伯克利大学。在1992年时,Steven McCanne和Van Jacobson在一篇论文中提出了BPF技术,这种技术可以在Unix内核中实现网络数据包过滤,并且其性能比当时最快的数据包过滤技术还快20倍,我们工作中常用的tcpdump工具中的libpcap就是基于BPF技术的。
cBPF可以在内核协议栈中进行高效的数据包处理操作。由于运行在内核中,避免了用户空间和内核空间的频繁切换,从而提升了性能。
但是由于BPF指令集的限制和功能的局限性,它无法满足更加复杂和灵活的数据包处理的需要。
eBPF的概念应该是在Linux 3.15 ~ 3.18版本引入的,由于网上有说3.15也有说3.18,我也不知道哪个是正确的。
eBPF的到来是BPF技术的一个转折点,以往BPF只能针对网络数据包进行处理,而eBPF变成了内核里的一个非常强大的子系统,可以实现包括网络监控、安全过滤、性能分析等功能。
并且,eBPF的出现让内核功能扩展变得更加的方便。
以往如果需要给内核添加功能,往往需要完全重新编译内核代码。而eBPF技术,允许用户将需要添加的功能编译为bytecode,由eBPF的加载器通过JIT翻译成机器语言加载到内核中,在不用重新编译内核代码的情况下,将功能添加到内核中。
0x2 eBPF有什么实际应用
- 网络性能优化:eBPF可以实现自定义的TCP拥塞控制,网络流量压缩等功能,基于eBPF的XDP技术提供了超高性能的网络通道
- 网络监控:在内核态收集并分析网络数据包,可以帮助分析网络流量
- 安全审计:eBPF可以监控Linux内核函数的调用,检测并记录恶意软件或攻击行为
- …
0x3 学习和使用eBPF技术需要什么
对于个人来说:
- 对于计算机操作系统知识要有一定的了解
- 需要了解eBPF技术的基本概念和原理,了解eBPF运行的流程
- 必须会使用C语言,可选go、python
- 不要怕失败,主动在网上搜寻答案的心
对于使用eBPF,需要满足包括但不限于以下的硬件环境
- 支持eBPF技术的Linux内核版本(一般来说现在发行版的Linux内核都是默认开启该功能的支持的)
- Ubuntu 20.10+
- Fedora 31+
- RHEL 8.2+
- Debian 11+
- root权限,一般情况下,加载eBPF程序进入内核需要root权限
- 开发过程中,你可能会需要使用到内核头文件
0x4 eBPF技术架构
这张图总体是比较直观的,对于开发人员来说,首先我们需要用C语言编写一份eBPF的程序,程序代码经过Clang/LLVM 编译器后生成 eBPF bytecode字节码,然后我们需要用root权限,将eBPF bytecode加载进内核,一般来说,会有一个 verifier验证器 存在,这个验证器主要是为了验证我们编写的程序是否有非法的内存访问,防止我们的程序把内核搞崩了。
如果验证器觉得我们编写的应用没有毛病,就会通过JIT编译器将eBPF字节码转换为我们机器可以执行的代码。
图中的MAPS是用于存放数据的,这是一个内核态和用户态都可以访问的内存空间,最终用户态的程序可以从MAPS里获取到内核中eBPF程序运行的数据,并进行结果输出。
0x5 eBPF CO-RE特性
eBPF技术紧密与内核相关,但是eBPF技术的开发者们不希望eBPF像cBPF一样受限于内核版本,因为Linux的API不稳定,不同版本之间可能会有很大的差别。希望eBPF开发编译一次后,能在不同的内核版本上直接运行。
所以eBPF提出了CO-RE的概念(Compile Once - Run Everywhere),要使用CO-RE特性需要Linux内核支持BTF 特性,从而eBPF程序在运行并访问内核中的结构体时,可以根据BTF提供的信息自动实现内存地址的偏移映射,从而实现一个eBPF程序可以在多个不同内核版本的机器上正常运行。
这个特性我暂时没有成功实践,目前大概知道需要以下的一些前提条件
- Linux内核编译时配置支持暴露BTF格式的数据结构
- Clang编译器编译eBPF程序时,将对内核数据结构的访问记录以及重定位信息保存在ELF文件的section中
- BPF Loader程序,可以在加载的时候通过读取内核BTF,和eBPF的重定位信息来修正访问的信息,完成最终的重定位。
- libbpf支持对eBPF暴露Kconfig或者配置
struct flavor
机制来兼容不同的内核数据结构改名或含义不同的情况
0x6 eBPF开发框架推荐
eBPF技术的学习曲线确实比较陡峭,个人推荐两个库,可以让学习eBPF更轻松
1. BCC (BPF Compiler Collection)
项目地址:https://github.com/iovisor/bcc
这套工具集合了eBPF开发需要的很多工具包,并且支持通过Python开发。下面放一段简单的代码,每当系统内核调用execve
函数时,就会在控制台打印Hello World
1 | from bcc import BPF |
这段代码中,首先通过bcc工具引入BPF
1 | program = """ |
然后program定义为了一串字符串,里面的内容就是c语言的eBPF代码,然后通过BCC库中BPF方法,将C代码即时编译并加载进内核,attach_kprobe方法指定了自定义eBPF程序在内核中挂载的位置,对于execve系统调用事件来说,每当这个系统函数被执行的时候,我们自定义挂载上去的eBPF程序也会被顺带一起执行。
ebpf-go库
项目地址:https://github.com/cilium/ebpf
这个库是纯go语言的,对于本身熟悉go语言的人来说会更友好。
不知道今天的内容能不能让大家对eBPF技术有一个简单的了解,后续我会基于一些实际的应用场景和我踩过的坑推出具体的分享。