ebpf 开发入门之核心概念篇

我是 LEE,老李,一个在 IT 行业摸爬滚打 16 年的技术老兵。

事件背景

在完成了第一章的编写《ebpf 开发入门之 helloworld》后,继续往下写多少怎么写都是我最近思考的问题。跟周边的小伙伴一起沟通后,认为 epbf 还在继续发展,重点应该关注它的核心概念,而不是重点关注它底层的内部实现,毕竟我的环境是对 epbf 最大程度的使用,而不是深入开发。所以经过一段时间的思考,觉得为了使用 cilium 深入 ebpf 研究无可厚非,但是过分深入则可能不太合适。

有了上面的观点,那么我就再写一个文章输出 epbf 的核心概念,后续深入的小伙伴可以根据自己实际需要找资料深入。

世界观

在讲解具体概念之前,我们先科普下 epbf 的整体世界观。

eBPF 世界观

Hook

中文名

钩子

大白话

在 epbf 的世界里看 Linux 内核所有核心调用都可以 Hook,可以理解成为万物皆可挂钩子做 Callback。

具体解释

eBPF 程序都是事件驱动的,它们会在内核或者应用程序经过某个确定的 Hook 点的时候运行,这些 Hook 点都是提前定义的,包括系统调用、函数进入/退出、内核 tracepoints、网络事件等。

eBPF 钩子

如果针对某个特定需求的 Hook 点不存在,可以通过 kprobe 或者 uprobe 来在内核或者用户程序的几乎所有地方挂载 eBPF 程序。

随意的钩子

Verifier

中文名

验证器

大白话

生成应用内核层的 bytescode 要想进入到内核中去运行,必然要有个“安全检查员”对这个 bytescode 的安全和合法性进行检测。

具体解释

每一个 eBPF 程序加载到内核都要经过 Verification,用来保证 eBPF 程序的安全性,主要包括:

  • 要保证 加载 eBPF 程序的进程有必要的特权级,除非节点开启了 unpriviledged 特性,只有特权级的程序才能够加载 eBPF 程序

    1. 内核提供了一个配置项 /proc/sys/kernel/unprivileged_bpf_disabled 来禁止非 特权用户使用 bpf(2) 系统调用,可以通过 sysctl 命令修改

    2. 比较特殊的一点是,这个配置项特意设计为一次性开关(one-time kill switch), 这 意味着一旦将它设为 1,就没有办法再改为 0 了,除非重启内核

    3. 一旦设置为 1 之后,只有初始命名空间中有 CAP_SYS_ADMIN 特权的进程才可以调用 bpf(2) 系统调用 。Cilium 启动后也会将这个配置项设为 1

       # echo 1 > /proc/sys/kernel/unprivileged_bpf_disabled
      
  • 要保证 eBPF 程序不会崩溃或者使得系统出故障

  • 要保证 eBPF 程序不能陷入死循环,能够 runs to completion

  • 要保证 eBPF 程序必须满足系统要求的大小,过大的 eBPF 程序不允许被加载进内核

  • 要保证 eBPF 程序的复杂度有限,Verifier 将会评估 eBPF 程序所有可能的执行路径,必须能够在有限时间内完成 eBPF 程序复杂度分析

JIT Compiler

中文名

JIT 编译器

大白话

跟 java 的 JVM 有点类似,就是把 bytescode 编译成本机能够运行的二进制代码。

具体解释

Just-In-Time(JIT) 编译用来将通用的 eBPF 字节码翻译成与机器相关的指令集,从而极大加速 BPF 程序的执行:

  • 与解释器相比,它们可以降低每个指令的开销。通常,指令可以 1:1 映射到底层架构的原生指令
  • 这也会减少生成的可执行镜像的大小,因此对 CPU 的指令缓存更友好
  • 特别地,对于 CISC 指令集(例如 x86),JIT 做了很多特殊优化,目的是为给定的指令产生可能的最短操作码,以降低程序翻译过程所需的空间

概念讲解

有了世界观的上的认识,同时这篇文章是作为入门,核心概念不应该说的太细太深,这样容易劝退很多小伙伴。所以这里采用“一句话”说明白的方式解释和介绍 epbf 的核心概念。

Helper Functions

中文名

辅助函数

大白话

应用在用户层不能直接访问内核层的数据,那么就需要一个代理人帮忙去执行,等待执行完毕后获得返回结果。

具体解释

eBPF 程序不能够随意调用内核函数,如果这么做的话会导致 eBPF 程序与特定的内核版本绑定,相反它内核定义的一系列 Helper functions。Helper functions 使得 BPF 能够通过一组内核定义的稳定的函数调用来从内核中查询数据,或者将数据推送到内核。所有的 BPF 辅助函数都是核心内核的一部分,无法通过内核模块来扩展或添加。

辅助函数

不同类型的 BPF 程序能够使用的辅助函数可能是不同的,例如:

  • 与 attach 到 tc 层的 BPF 程序相比,attach 到 socket 的 BPF 程序只能够调用前者可以调用的辅助函数的一个子集
  • lightweight tunneling 使用的封装和解封装辅助函数,只能被更低的 tc 层使用;而推送通知到用户态所使用的事件输出辅助函数,既可以被 tc 程序使用也可以被 XDP 程序使用

Maps

中文名

映射存储

大白话

ebpf Map 是驻留在内核空间中的高效 Key/Value store,包含多种类型的 Map,由内核实现其功能。用来作为用户层和内核层之间数据交换的媒介,同时可以在不同程序之间共享数据。

具体解释

epbf Map

BPF Map 的交互场景有以下几种:

  • BPF 程序和用户态程序的交互:BPF 程序运行完,得到的结果存储到 map 中,供用户态程序通过文件描述符访问
  • BPF 程序和内核态程序的交互:和 BPF 程序以外的内核程序交互,也可以使用 map 作为中介
  • BPF 程序间交互:如果 BPF 程序内部需要用全局变量来交互,但是由于安全原因 BPF 程序不允许访问全局变量,可以使用 map 来充当全局变量
  • BPF Tail call:Tail call 是一个 BPF 程序跳转到另一 BPF 程序,BPF 程序首先通过 BPF_MAP_TYPE_PROG_ARRAY 类型的 map 来知道另一个 BPF 程序的指针,然后调用 tail_call() 的 helper function 来执行 Tail call
  • 共享 map 的 BPF 程序不要求是相同的程序类型,例如 tracing 程序可以和网络程序共享 map,单个 BPF 程序目前最多可直接访问 64 个不同 map。
ebpf Map 共享数据

内核中的 通用 map 有

  • BPF_MAP_TYPE_HASH
  • BPF_MAP_TYPE_ARRAY
  • BPF_MAP_TYPE_PERCPU_HASH
  • BPF_MAP_TYPE_PERCPU_ARRAY
  • BPF_MAP_TYPE_LRU_HASH
  • BPF_MAP_TYPE_LRU_PERCPU_HASH
  • BPF_MAP_TYPE_LPM_TRIE

内核中的 非通用 map 有

  • BPF_MAP_TYPE_PROG_ARRAY:一个数组 map,用于 hold 其他的 BPF 程序
  • BPF_MAP_TYPE_PERF_EVENT_ARRAY
  • BPF_MAP_TYPE_CGROUP_ARRAY:用于检查 skb 中的 cgroup2 成员信息
  • BPF_MAP_TYPE_STACK_TRACE:用于存储栈跟踪的 MAP
  • BPF_MAP_TYPE_ARRAY_OF_MAPS:持有(hold) 其他 map 的指针,这样整个 map 就可以在运行时实现原子替换
  • BPF_MAP_TYPE_HASH_OF_MAPS:持有(hold) 其他 map 的指针,这样整个 map 就可以在运行时实现原子替换

Object Pinning

中文名

钉住对象 (非常奇怪的翻译,但是看源代码,翻译 pin 为固定和被钉在那里还是满合适的)

大白话

ebpf map 和程序作为内核资源只能通过文件描述符访问(fd),这个映射实际就是 fd 到内存对象的属性路径的一个映射,这个映射过程叫 pin。

具体解释

(★)ebpf map 和程序作为内核资源只能通过文件描述符访问,其背后是内核中的匿名 inode。 这个观点很重要,因为 pin 这个行为都是依据这个概念来的。

这样做的优点:

  • 用户空间应用程序能够使用大部分文件描述符相关的 API
  • 传递给 Unix socket 的文件描述符是透明工作等等

这样做的缺点:

文件描述符受限于进程的生命周期,使得 map 共享之类的操作非常笨重,这给某些特定的场景带来了很多复杂性。

解法

为了解决这个问题,内核实现了一个最小内核空间 BPF 文件系统,BPF map 和 BPF 程序 都可以 pin 到这个文件系统内,这个过程称为 object pinning。BPF 相关的文件系统不是单例模式(singleton),它支持多挂载实例、硬链接、软连接等等。

相应的,BPF 系统调用扩展了两个新命令,如下图所示:

  • BPF_OBJ_PIN:钉住一个对象
  • BPF_OBJ_GET:获取一个被钉住的对象
ebpf Pinning

Tail Calls

中文名

尾调用

大白话

一个 BPF 程序可以调用另一个 BPF 程序,并且调用完成后不用返回到原来的程序。

具体解释

尾调用的机制是指:一个 BPF 程序可以调用另一个 BPF 程序,并且调用完成后不用返回到原来的程序。

  • 和普通函数调用相比,这种调用方式开销最小,因为它是用长跳转(long jump)实现的,复用了原来的栈帧 (stack frame)
  • BPF 程序都是独立验证的,因此要传递状态,要么使用 per-CPU map 作为 scratch 缓冲区 ,要么如果是 tc 程序的话,还可以使用 skb 的某些字段(例如 cb[])
  • 相同类型的程序才可以尾调用,而且它们还要与 JIT 编译器相匹配,因此要么是 JIT 编译执行,要么是解释器执行(invoke interpreted programs),但不能同时使用两种方式
尾调用

Hardening

中文名

硬化 (明明说的就是安全,但是用 Hardening 这个单词,觉得有点奇怪)

大白话

硬化实际是对 epbf 运行状态的值和数据进行保护,防止以外被篡改和破坏,是一种暗转防护机制。

具体解释

在程序的生命周期内,BPF 将内核中的整个 BPF 解释器映像(struct bpf_prog)以及 JIT 编译映像(struct bpf_binary_header)锁定为只读,以防止代码被破坏。例如,由于某些内核 bug 而发生的任何损坏都会导致一般的保护故障,从而导致内核崩溃,而不是让损坏静静地发生。

对于 x86_64 JIT 编译器,如果 CONFIG_RETPOLINE 已经设置(大多数 Linux 发行版在编写时都是默认设置),则通过 retpoline 实现从使用尾部调用的间接跳转的 JIT。

在/proc/sys/net/core/bpf_jit_harden 设置为 1 的情况下,JIT 编译的额外加固步骤将对非特权用户生效。在不受信任的用户对系统进行操作的情况下,通过减少(潜在的)攻击面,可以有效地略微权衡它们的性能。与完全切换到解释器相比,程序执行时间的减少仍然会带来更好的性能。

通过将实际指令随机化,这意味着通过将值的实际负载分成两个步骤来重写指令,将操作从基于即时的源操作数转换为基于寄存器的操作数:

  1. 加载一个盲化后的(blinded)立即数 rnd ^ imm 到寄存器
  2. 将寄存器和 rnd 进行异或操作(xor)

Offloads

中文名

卸载

大白话

就是把 eBPF 的网络程序内核层 bytescode 从 CPU 运行改为由网卡的 MPU 来执行。

具体解释

eBPF 网络程序,尤其是 tc 和 XDP BPF 程序在内核中都有一个 offload 到硬件的接口,这样就可以直接在网卡上执行 BPF 程序。

Offloads

参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容