1.源码阅读忆丛(51)eBPF
2.为什么k8s管理员要懂eBPF
3.用trace工具 trace trace工具
4.eBPF 入门实践教程二十:使用 eBPF 进行 tc 流量控制
5.sysctl 参数防篡改 - 基于 ebpf 的源码实现 [一]
6.eBPF使用libbpf开发eBPF程序
源码阅读忆丛(51)eBPF
eBPF:革新内核的瑞士军刀
eBPF的发展如火如荼,其势头正盛,分析似乎有潜力彻底重塑Linux内核的源码可能。初识eBPF,分析源于对复杂源码的源码渴望,Hotspot、分析冰雪版本源码V8等大型项目让人望而却步,源码于是分析选择了一款小巧且充满潜力的eBPF来探索。深入学习后发现,源码eBPF的分析内容丰富多样,不仅提供了强大的源码调试工具,还能深入探测性能,分析勾起了我浓厚的源码兴趣。
通过百度和阅读电子书《BPF之巅-洞悉Linux系统和应用性能》,分析我对eBPF的源码原理有了初步了解。书中的前五章着重介绍了eBPF的原理和技术,而后续章节则详细阐述了其工具的使用方法。这些工具的功能确实强大,但更多是在调试器层面的延展。我尤其对性能探测工具感到好奇,这促使我进一步深入研究。
对eBPF原理的兴趣驱使我追溯其发展脉络。从年eBPF的早期版本开始,我发现其基础架构已足够强大,足以替代iptables。从年到年,这个领域似乎并未取得显著进展,这可能是因为它被忽视了。
随着深入研究Linux 4.1版本(年发行),我浏览了samples/bpf和kernel/bpf目录下的源代码,重点分析了libbpf.c、bpf_load.c、明星涨停指标源码core.c、syscall.c、verifier.c等关键文件。这些代码揭示了eBPF的加载和编译机制,包括在用户态标记并记录映射和函数调用,然后在内核态通过verifier.c的bpf_check(...)函数实现映射地址或函数地址的真实替换。至于代码的动态编译和优化,我选择跳过,因为涉及到的JIT等技术我已经较为熟悉。
在理解eBPF动态插桩和静态插桩技术的基础上,我回顾了Linux 2.6.版本(年)的trace静态插桩技术。这个版本的trace功能较为基础,主要记录函数调用地址,但提供快速写入功能,即使数据来不及读取也会被覆盖。然而,读取数据时需要比较所有CPU的环形缓冲区记录,找到最久的记录。虽然功能有限,但trace静态插桩在内核重要函数的调用跟踪中发挥了作用。
此外,我还研究了Linux 2.6.版本的kprobes动态插桩技术。kprobes提供了一种动态跟踪函数调用的方法,主要通过kernel/kprobes.c和arch/x/kernel/kprobes.c文件实现。reenter_kprobe函数处理调试中断时的重入问题,而kretprobe则将第二个CPU核单步执行,避免冲突。jprobe则通过插入代码改变程序流程,理论上避免了重入问题。
在回顾了这些源码后,我发现它们的网址源码哪个好难度并不高,结合网络资源,我能够顺利阅读并理解。我仅记录了当时重点思考的部分,这些部分涉及了源码的关键功能和实现细节。
为什么k8s管理员要懂eBPF
eBPF的引入为容器和Kubernetes集群的观测性带来了革命性的变化。它允许程序直接在Linux内核空间中运行,提供了高效、安全且功能强大的方式来控制、过滤和监控流量。eBPF的崛起并非偶然,而是与Docker和Kubernetes的发展紧密相关。本文旨在深入探讨eBPF的来龙去脉,以及为什么它在Kubernetes可观测性中变得如此重要。
eBPF,全称为"extended Berkeley Packet Filter",是对老式伯克利包过滤器BPF的扩展。BPF最初于年引入,用于为Linux内核提供可编程、高效的虚拟机,以控制和过滤流量。然而,随着容器技术的兴起,特别是Docker的普及,BPF的原始架构变得越来越局限。eBPF于年引入,通过提供在Linux内核空间中运行的工具,极大地扩展了BPF的原始架构。这使得eBPF在容器和Kubernetes环境中变得尤为重要。
在内核空间中运行程序意味着代码能以更高的效率执行,并且能访问底层内核资源,否则从用户空间访问这些资源会非常复杂且昂贵。长生诀源码教程此外,eBPF允许你观察用户空间中运行的任何程序,这为容器世界提供了一种无价的技术能力。使用eBPF,你可以在磁盘I/O等事件上运行小程序,这些事件在内核的安全虚拟机中运行。
在使用内核模块之前,eBPF提供了更简单、更高效、更安全的解决方案。内核模块的部署复杂,存在依赖关系,并且可能不安全。与之相比,eBPF允许自定义程序在独立的内核级虚拟机中运行,无需处理内核模块依赖,接触内核源代码,甚至无需拥有root权限。这使得eBPF成为一种灵活且易于部署的解决方案。
一旦部署了eBPF程序,你就可以监视你附加的任何代码流,无论是在内核空间、用户空间还是两者都有。eBPF映射或预定义的文件描述符允许你访问程序的输入和输出,从而实现对网络流量的细粒度可见性。在云原生世界中,eBPF为可观测性解锁了无限可能性。
为了部署eBPF程序,你需要完成以下步骤:编写和编译代码(通常使用限制性C语言),然后将字节码传递给内核检验器以确保程序的可靠性。加载并验证后,黑马到底指标源码程序就可以执行,监视数据包的进入或发送,然后映射到主机上运行的进程或容器。这提供了对网络流量的深入洞察。
eBPF的生态系统正在快速发展,包括工具链、编译器和与eBPF程序交互的项目,如Python、Golang和Rust。这些工具简化了eBPF的使用,降低了开发门槛,使得即使是缺乏动力的开发者也能轻松编写和加载eBPF程序。
尽管eBPF带来了许多优势,但也有其局限性。编写符合内核检验器的eBPF程序可能具有挑战性,且风险在于程序在不同内核版本之间可能不一致。eBPF程序在堆栈空间上受到限制,需要高效地编写代码。此外,不同Linux发行版之间的内核版本和自定义可能导致eBPF程序的可移植性问题。
展望未来,eBPF的生态系统正变得更加成熟和组织化,得到了来自大型科技公司的支持。随着eBPF基金会等组织的推动,可以预见eBPF将在Kubernetes可观测性中发挥关键作用。因此,Kubernetes管理员和开发人员应开始学习使用eBPF,以充分利用其在监控和理解容器内部事件方面的潜力。
用trace工具 trace trace工具
深入探讨了使用trace工具理解eBPF(eBPF)和trace工具的方法。首先,理解了使用eBPF工具进行调试以及trace工具理解trace原理的两种方式:从代码细节入手,或是先勾画大概,再深入细节。在复杂系统中,直接查看所有代码变得困难,尤其是在云环境中,此现象普遍。接下来,以`reallocarray`为例,创建了一个uprobe。
在探究如何通过trace-bpfcc生成uprobe时,通过strace工具发现使用了`perf_event_open`进行注入。进一步关注`perf_event_open`内部参数`struct perf_event_attr`,了解了`config1`和`config2`的作用:`config1`类似uprobe的路径名,而`config2`是特定偏移量。通过尝试不同方法,最终确认`config1`指向`libc.so`文件路径,`config2`为`reallocarray`在`libc-2..so`中的偏移。
创建uprobe后,编写了小程序来触发其执行。eBPF与uprobe的关联通过`trace trace-bpfcc`实现,最终调用`__uprobe_register`。对于`__uprobe_register`的实现,通过进一步查找代码获取信息。`mymem`触发uprobe的机制大致为程序加载或执行过程中会触发先前创建的uprobe,通过`ftrace`的`function_graph`功能筛选并打印调用函数链。
通过分析uprobe_mmap的调用栈,可以了解到在操作vma时会触发uprobe_mmap。uprobe_mmap内部的关键调用有助于理解其工作流程。总结以上trace分析,得出理解uprobe的实现和工作原理,主要通过trace和源码分析相结合的方式,掌握工具和方法是关键。
通过trace过程演示了使用trace工具的能力和方法,更多关于uprobe的实现细节,可以通过进一步的trace或阅读源码进行深入探索。这一过程展示了如何利用trace工具理解复杂系统中的特定功能和行为,为深入学习和调试提供了一条有效路径。
eBPF 入门实践教程二十:使用 eBPF 进行 tc 流量控制
eBPF,即扩展的伯克利数据包过滤器,是Linux内核的强大网络和性能分析工具,允许开发者动态加载、更新并运行用户自定义代码,无需重启内核或修改内核源代码。Linux的流量控制子系统(Traffic Control, tc)自内核早期存在,与iptables和netfilter并存,主要用于控制数据包的发送和接收速率与顺序。从Linux 4.1开始,tc添加了新挂载点,并支持将eBPF程序作为过滤器加载到这些挂载点上。
tc位于协议栈的链路层,其工作位置在sk_buff分配之后,晚于xdp。其核心功能通过队列结构实现,即qdisc(队列纪律),该结构对外提供数据包入队和出队的回调接口,内部隐藏具体的排队算法实现。通过filter和class,tc可以构建复杂的树形结构,用于数据包的分类和处理。
现有的tc为eBPF提供了直接动作模式,允许eBPF程序返回tc动作返回值,而不只是将数据包分类到特定的class。这意味着eBPF程序可以直接在特定的qdisc上运行,执行数据包分类和处理。
以下是一个简单的eBPF程序示例,用于捕获IPv4协议的数据包,并打印出数据包的总长度和Time-To-Live(TTL)字段的值。程序中使用了BPF库函数进行网络字节序和主机字节序之间的转换,以及注释提供了TC的附加点和选项信息。程序通过容器编译、ecc编译,并使用ecli运行。
总结,本文阐述了如何将eBPF过滤器应用于TC流量控制子系统,以实现链路层数据包的排队处理。通过eunomia-bpf提供的参数传递方案,可以将自定义的tc BPF程序以特定选项加载到目标网络设备上,并利用内核的sk_buff结构进行数据包过滤。欲深入了解eBPF知识与实践,可访问教程代码仓库github.com/eunomia-bpf/...获取更多示例和教程。
sysctl 参数防篡改 - 基于 ebpf 的实现 [一]
系统调用参数防篡改 - 基于ebpf的实现
本文基于内核代码版本5..0进行讨论。
ebpf能够修改某些函数的返回值,但仅限于允许错误注入的函数,这限制了其应用范围。系统tap能够作用于几乎任何函数,但由于内核API的不稳定,它在不同内核上可能无法运行。
ebpf的优势在于其与内核交互的API保持稳定,特别是用于“系统调用参数防篡改”的一组helper函数。在阅读代码实现时,发现中段插入了一个“BPF_CGROUP_RUN_PROG_SYSCTL”。
这一功能源于v5.2-rc1的commit,旨在限制容器对sysctl的错误写入,要求内核版本不低于5.2,且配置项包含“CONFIG_CGROUP_BPF”。
配套的4个helper函数记录在内核文档“Documentation/bpf/prog_cgroup_sysctl.rst”中,用于读取sysctl参数名称和值、在参数修改时获取写入的值以及覆盖准备写入的值。这些helper与内核原生路径中的过滤函数交互。
使用示例
通过Linux内核源码中的“tools/testing/selftests/bpf”目录下的测试用例可以学习ebpf的使用。在源码根目录下执行make命令编译。
针对sysctl部分,测试用例主体为“test_sysctl.c”,用于将ebpf程序加载至内核,并在对应的点位上附加。ebpf程序可以是直接以ebpf汇编语法写的,也可以是C文件编译成.o二进制文件的形式。
当判断为write操作时,返回0,内核源码中决定sysctl参数读写结果的点位返回“-EPERM”,使得修改不成功。
ebpf prog源文件中的“SEC”宏定义用于指示编译器将函数/变量放在特定的section中,便于用户态loader查找和解析。
为了实现加载和附加程序,使用了“sysctl_write_deny_prog.o”作为附加程序,类型为“BPF_CGROUP_SYSCTL”,方式为“BPF_F_ALLOW_OVERRIDE”。借助“fd”这样的整形数字,用户态程序可以深入内核态获取对应的结构体实例。
最终通过libbpf封装系统调用接口,用户态程序可以通过“bpf”系统调用入口与内核交互。使用strace工具可以追踪这一过程。
了解ebpf helper函数的使用,可以借助现成的工具进行学习,更多详情请参考后续文章。
eBPF使用libbpf开发eBPF程序
libbpf是内核提供的功能库,学习它有助于理解如bcc/bpftrace等工具。eBPF程序的运行流程包括生成字节码、加载字节码到内核中,并将其attach到特定事件或函数。此外,创建map实现内核态与用户态间的数据交互。当事件或TP点触发时,调用attach的eBPF字节码执行其功能。
本文示例为统计一段时间内syscall调用次数,包含如下项目文件结构:
在字节码生成阶段,有多种方式实现。本文采用clang进行编译,创建eBPF程序。在编译过程中,需注意几个关键点:内核源码的使用、单独构建的libbpf库和bpftool,以及使用-g -O2选项以避免加载时的错误。
使用libbpf库加载eBPF程序的步骤如下:需要内核头文件支持,从内核源码中安装至当前目录。整个工程目录应包含libbpf库依赖的libelf和libz库,因此需要进行交叉编译相关库。编译用户态eBPF加载程序时,需链接之前编译好的依赖库。在内核开启相关功能的情况下,加载程序后,即可启动虚拟机并运行,以开始调试内核bpf模块功能。