创建程序集时元数据失败 -- 拒绝访问_网红容器网络Cilium的基石--BPF

00e871aab21a711da339a7a177c73f15.png

Cilium是一个基于 eBPF 和 XDP 的高性能容器网络方案。今天我们详细讲述一下eBPF。

eBPF是什么?

BPF是一种高级虚拟机,在隔离的环境中运行代码指令。从某种意义上讲,您可以像对Java虚拟机(JVM)那样思考BPF,Java虚拟机是运行从高级编程语言编译的机器代码的专用程序。诸如LLVM和GNU编译器集合(GCC)之类的编译器将在不久的将来为BPF提供支持,从而使您可以将C代码编译为BPF指令。编译代码后,BPF使用验证程序来确保程序可以安全地由内核运行。它可以防止您运行可能会使内核崩溃而危害系统的代码。如果您的代码安全,则BPF程序将被加载到内核中。 Linux内核还为BPF指令集成了即时(JIT)编译器。程序经过验证后,JIT将直接将BPF字节码转换为机器码,从而避免了执行时间的开销。该体系结构的一个有趣方面是,您无需重新启动系统即可加载BPF程序;您可以按需加载它们,也可以编写自己的初始化脚本,这些脚本在系统启动时加载BPF程序。

在内核运行任何BPF程序之前,它需要知道该程序附加到哪个执行点。内核中有多个入口点,并且列表在不断增加。执行点由BPF程序类型定义。当选择执行点时,内核还会提供特定的函数帮助器,可用于处理程序接收的数据,从而使执行点和BPF程序紧密耦合。

BPF体系结构的最后一个组件负责在内核和用户态之间共享数据。这个组件称为BPF映射。BPF映射是共享数据的双向结构。这意味着您可以从内核和用户态这两个方面进行写入和读取。有几种类型的结构,从简单的数组和哈希映射到专用的映射,可以将整个BPF程序保存在其中。

eBPF即扩展的BPF。

程序类型

BPF所有类型都根据其主要目的分为两类。

第一类是跟踪。您编写的许多程序将帮助您更好地了解系统中正在发生的事情。它们为您提供有关系统行为及其运行硬件的直接信息。他们可以访问与特定程序有关的内存区域,并从运行进程中提取执行的跟踪信息。它们还使您可以直接访问为每个特定进程分配的资源,从文件描述符到CPU和内存使用情况。

第二类是网络。这些类型的程序使您可以检查和处理系统中的网络流量。它们使您可以过滤来自网络接口的数据包,甚至完全拒绝那些数据包。可以在内核中将不同类型的程序附加到网络处理的不同阶段。这具有优点和缺点。例如,您可以在网络驱动程序收到数据包后立即将BPF程序附加到网络事件中,但是此程序将只能访问较少的有关该数据包的信息,因为内核尚不足以为您提供信息。另一方面,您可以将BPF程序附加到网络事件上,然后再将它们传递到用户态。在这种情况下,您将获得有关数据包的更多信息,这将有助于您做出更明智的决策,但是您需要承担完全处理数据包的开销。

不同的程序类型,决定了不同的BPF代码附着的入口点。

BPF映射

BPF映射是内核和用户态之间通信的中央总线。BPF映射是驻留在内核中的键/值存储。任何BPF程序都可以访问它们。在用户态中运行的程序也可以使用文件描述符访问这些映射。只要事先正确指定数据大小,就可以在映射中存储任何类型的数据。内核将键和值视为二进制 blobs,它并不关心您在映射中保留的内容。

BPF验证程序包括多种保护措施,以确保您创建和访问映射的方式是安全的。

创建BPF映射的最直接方法是使用bpf syscall。当调用中的第一个参数是BPF_MAP_CREATE时,您将告诉内核您要创建一个新映射。此调用将返回与您刚创建的映射关联的文件描述符标识符。 syscall中的第二个参数是此映射的配置:

union bpf_attr {
        struct {
            __u32 map_type;     /* one of the values from bpf_map_type */
            __u32 key_size;     /* size of the keys, in bytes */
            __u32 value_size;   /* size of the values, in bytes */
            __u32 max_entries;  /* maximum number of entries in the map */ 
            __u32 map_flags;    /* flags to modify how we create the map */
        };
    }

syscall中的第三个参数是此配置属性的大小。

创建完成之后,你可以对映射执行更新,读取,查找,删除,迭代等操作。

为了满足不用的场景使用,BPF映射包括哈希表映射,数组映射,队列映射等十几种映射。

BPF和网络

从网络的角度来看,BPF程序有两个使用场景:数据包捕获和过滤。

这意味着用户态程序可以将过滤器附加到任何套接字,并从数据包提取信息,并允许/禁止/重定向某些种类的数据包。

两种常见类型的程序:

  • 与sockets相关的程序类型。
  • 基于BPF分类器实现流量控制的程序类型。

XDP

XDP是Linux网络数据路径中的一种安全,可编程,高性能,内核集成的数据包处理器,当NIC驱动程序接收到数据包时,它将执行BPF程序。这使XDP程序可以在最早的时间点做出有关接收到的数据包的决定(丢弃,修改或允许它)。

执行点并不是使XDP程序快速运行的唯一方面。以下几个方面也扮演了很重要的角色:

  • 使用XDP进行数据包处理时,没有内存分配。
  • XDP程序仅适用于线性,无碎片的数据包,并且具有数据包的开始和结束指针。
  • 无法访问完整的数据包元数据。
  • 因为它们是eBPF程序,所以XDP程序的执行时间有限,其结果是它们的使用在网络管道中具有一定的成本。

但是,XDP和eBPF之间有什么关联?

事实证明,XDP程序是通过bpf syscall控制的,并使用程序类型BPF_PROG_TYPE_XDP进行加载。同样,驱动程序钩子执行BPF字节码。

在使用XDP时,了解它已被全球各个组织使用场景案例肯定是有用的。这可以帮助您想象为什么在某些情况下使用XDP比其他技术(例如套接字过滤或流量控制)更好。

  • 监控 -- 如今,大多数网络监视系统都是通过编写内核模块或通过从用户态访问proc文件来实现的。编写,分发和编译内核模块并不是每个人的任务。这是一个危险的操作。它们也不容易维护和调试。但是,替代方案可能更糟。为了获得相同的信息,例如在一秒钟内收到一张卡有多少个数据包,您需要打开文件并拆分文件。因此,我们需要一种解决方案,使我们能够实现与内核模块类似的功能,而又不会损失性能。 XDP非常适合此操作,因为我们可以使用XDP程序发送要提取的数据到映射中。然后,加载器可以使用该映射,该加载器可以将度量标准存储到存储后端中,并对其应用算法或将结果绘制在图形中。
  • DDoS防御 -- 能够在NIC级别查看数据包可确保在第一阶段就能拦截任何可能的数据包,此时系统无需足够的计算能力来了解数据包是否对系统有用。在典型情况下,bpf映射可以让XDP程序对来自特定源的数据包 XDP_DROP。通过分析另一个映射接收到的数据包,可以在用户态中生成该数据包列表。一旦流入XDP程序的数据包与列表中的元素匹配,直接丢弃数据包。数据包被丢弃,内核甚至不需要花费CPU周期来处理它。其结果是使攻击者的目标难以实现,因为在这种情况下,DDoS无法浪费任何昂贵的计算资源。
  • 负载均衡 -- XDP程序一个有趣的用例是负载平衡。但是,XDP只能在数据包到达的同一NIC上重新传输数据包。这意味着XDP并不是实现经典负载均衡器(负载均衡器位于所有服务器之前并将流量转发到它们)的最佳选择。但是,这并不意味着XDP不适用于此场景。如果我们将负载平衡从外部服务器移到为应用程序提供服务的同一台计算机上,您将立即看到如何使用其NIC来完成这项工作。通过这种方式,我们可以创建一个分布式负载平衡器,其中承载应用程序的每台计算机都可以帮助将流量分散到适当的服务器。
  • 防火墙 -- 人们想到Linux上的防火墙时,通常会想到iptables或网络过滤器。使用XDP,您可以直接在NIC或其驱动程序中以完全可编程的方式获得相同的功能。通常,防火墙是昂贵的计算机,它们位于网络栈的顶部或节点之间,以控制其通信状态。但是,当使用XDP时,很明显,因为XDP程序非常便宜且快速,所以我们可以将防火墙逻辑直接实现到节点的NIC中,而不用拥有专用的机器。一个常见的用例是拥有一个XDP加载器,该加载器通过使用远程过程调用API更改一组规则来控制映射。然后,将映射中的规则集动态传递给加载到每台特定计算机中的XDP程序,以控制它可以从哪里以及在什么情况下接收什么。这种选择不仅可以降低防火墙的成本。它允许每个节点部署自己的防火墙级别,而无需依赖用户态软件或内核来执行此操作。当使用 offloaded XDP作为操作模式进行部署时,由于处理甚至不是由主节点CPU进行的,因此我们可以获得最大的优势。

BPF的 Hello World

编写BPF程序的最常见方法是使用LLVM编译的C子集。 LLVM是一种通用编译器,可以编译出不同类型的字节码。在这种情况下,LLVM将输出BPF汇编代码,我们稍后将其加载到内核中。

内核提供syscall bpf,以便在编译程序后将程序加载到BPF VM中。除了加载程序外,该系统调用还用于其他操作。内核还提供了一些实用程序,可以为您抽象BPF程序的加载,我们使用这些帮助程序向您展示BPF的“ Hello World”示例:

#include <linux/bpf.h>

#define SEC(NAME) __attribute__((section(NAME), used))
SEC("tracepoint/syscalls/sys_enter_execve") 
int bpf_prog(void *ctx) {
    char msg[] = "Hello, BPF World!"; 
    bpf_trace_printk(msg, sizeof(msg)); 
    
    return 0;
}

char _license[] SEC("license") = "GPL";

在这个程序中有一些有趣的概念。当我们要运行此程序时,我们使用SEC属性通知BPF VM。在这种情况下,当execve 系统调用被检测到时,我们将运行此BPF程序。跟踪点是内核二进制代码中的静态标记,允许开发人员注入代码以检查内核的执行情况。因此,我们将看到消息Hello,BPF World!每次内核检测到某个程序执行另一个程序时。

在此示例的最后,我们还指定了该程序的许可证。由于Linux内核是根据GPL许可的,因此它也只能加载以GPL许可的程序。如果我们将许可证设置为其他权限,内核将拒绝加载程序。我们正在使用bpf_trace_printk在内核跟踪日志中打印一条消息;您可以在 /sys/kernel/debug/tracing/trace_pipe 中找到此日志。

我们将使用clang将第一个程序编译为有效的ELF二进制文件。这是内核期望加载的格式。我们将第一个程序保存在名为bpf_program.c的文件中,以便我们对其进行编译:

clang -O2 -target bpf -c bpf_program.c -o bpf_program.o

现在,我们已经编译了第一个BPF程序,我们需要将其加载到内核中。如前所述,我们使用内核提供的特殊帮助函数来抽象出编译和加载程序的模板。该帮助函数称为load_bpf_file,它将获取一个二进制文件,然后尝试将其加载到内核中。如下所示:

#include <stdio.h>
#include <uapi/linux/bpf.h>
#include "bpf_load.h"

int main(int argc, char **argv) {
    if (load_bpf_file("hello_world_kern.o") != 0) {
        printf("The kernel didn't load the BPF programn");

        return -1; 
    }

    read_trace_pipe(); 

    return 0;
}

我们将使用脚本来编译该程序并将其链接为ELF二进制文件。在这种情况下,我们不需要指定目标,因为该程序不会加载到BPF VM中。我们需要使用一个外部库,编写脚本可以更轻松地将所有内容组合在一起:

TOOLS=../../../tools
    INCLUDE=../../../libbpf/include
    HEADERS=../../../libbpf/src
    clang -o loader -l elf 
      -I${INCLUDE} 
      -I${HEADERS} 
      -I${TOOLS} 
      ${TOOLS}/bpf_load.c 
      loader.c

如果要运行此程序,则可以使用sudo执行此最终二进制文件:sudo ./loader。 sudo是Linux命令,它将为您提供计算机的root特权。如果您不使用sudo运行该程序,则会收到错误消息 因为大多数BPF程序只能由具有root特权的用户加载到内核中。

运行该程序时,您将开始看到我们的“ Hello, BPF World”!几秒钟后仍会显示一条消息,即使您没有使用计算机进行任何操作。这是因为在计算机屏幕后运行的程序可能正在执行其他程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值