bpf的程序可以分为两大类:第一大类是tracing,可以帮助你更好的理解系统发生了什么,例如:cpu和内存的使用情况等。第二大类就是networking,主要可以帮助你检查和处理网络流量,它可以允许你filter或者reject网络数据包,不同的网络类型程序,可以发生在网络的不同阶段。
BPF_PROG_TYPE_SOCKET_FILTER是第一个被添加到内核的程序类型。当你attach一个bpf程序到socket上,你可以获取到被socket处理的所有数据包。socket过滤不允许你修改这些数据包以及这些数据包的目的地。仅仅是提供给你观察这些数据包。在你的程序中可以获取到诸如protocol type类型等。
kprobes是内核提供的动态探测的功能。BPF kprobe program 类型允许你使用BPF程序作为一个kprobe的执行程序。定义为BPF_PROG_TYPE_KPROBE,BPF虚拟机确定你的kprobe程序是否合法。
当你写一个kprobe的bpf程序类型时,你需要确定kprobe是在程序的第一条指令执行还是在最后完成时执行。例如:如果你想检查exec系统调用的参数,你就需要把它attach到程序的开始:SEC(“kprobe/sys_exec”)。当你需要检查exec的返回值时,你需要这样指定:SEC(“kretprobe/sys_exec”).
理论上/proc/kallsyms下面的方法都是可以被probe程序执行的。
BPF_PROG_TYPE_TRACEPOINT。顾名思义,该类型的程序attach到kernel预先定义好的traceponit上。相比于krobe,它是不灵活的,因为需要kernel预先定义好tracepoints。但是他们是很稳定的。
所有的traceponits在内核的/sys/kernel/debug/tracing/events下面可以看到。
比较有意思的是:BPF还定义了自己的tracepoints,因此你可以写BPF程序来检查另一个bpf程序的行为。bPF的tracepoints定义在/sys/kernel/debug/tracing/events/bpf下面。例如:有一个tracepoints叫做bpf_prog_load。这就意味着你可以写bpf的代码去检查bpf程序load的过程。
BPF_PROG_TYPE_XDP允许你的pbf程序,在网络数据包到达kernel很早的时候。在这样的bpf程序中,你仅仅可能获取到一点点的信息,因为kernel还没有足够的时间去处理。因为时间足够的早,所以你可以在网络很高的层面上去处理这些packet.
XDP定义了很多的处理方式。例如返回XDP_PASS,就意味着,你会把packet交给内核的另一个子系统去处理。XDP_DROP就意味着,内核应该丢弃这个数据包。XDP_TX意味着,你可以把这个包转发到network interface card(NIC)第一次接收到这个包的时候。
BPF_PROG_TYPE_PERF_EVENT允许你把Bpf程序添加到perf evens上。Perf是一个profiler的工具,它可以在软件和硬件上给出相关的性能数据。当你的BPF程序被attach到一个Perf的events上时,意味着Perf在每次生成数据的时候都会调用你的Bpf程序。
BPF_PROG_TYPE_CGROUP_SKB允许你过滤整个cgroup的网络流量。在这种程序类型中,你可以在网络流量到达这个cgoup中的程序前做一些控制。内核试图传递给同一cgroup中任何进程的任何数据包都将通过这些过滤器之一。同时,您可以决定cgroup中的进程通过该接口发送网络数据包时该怎么做。其实,你可以发现它和BPF_PROG_TYPE_SOCKET_FILTER的类型很类似。最大的不同是cgroup_skb是attach到这个cgroup中的所有进程,而不是特殊的进程。在container的环境中,bpf是非常有用的。Cillium,是一个开源的工程,提供了负载均衡和安全能力for Kubernetes。
BPF_PROG_TYPE_CGROUP_SOCK,这种类型的bpf程序允许你,在一个cgroup中的任何进程打开一个socket的时候,去执行你的Bpf程序.这个行为和CGROUP_SKB的行为类似,但是它是提供给你cgoup中的进程打开一个新的socket的时候的情况,而不是给你网络数据包通过的权限控制。这对于为可以打开套接字的程序组提供安全性和访问控制很有用,而不必分别限制每个进程的功能。
BPF_PROG_TYPE_SOCK_OPS这种程序类型,允许你当数据包在内核网络协议栈的各个阶段传输的时候,去修改套接字的链接选项。他们attach到cgroups上,和BPF_PROG_TYPE_CGROUP_SOCK以及BPF_PROG_TYPE_CGROUP_SKB很像,但是不同的是,他们可以在整个连接的生命周期内被调用好多次。你的bpf程序会接受到一个op的参数,该参数代表内核将通过套接字链接执行的操作。因此,你知道在链接的生命周期内何时调用该程序。另一方面,你可以获取ip地址,端口等。你还可以修改链接的链接的选项以设置超时并更改数据包的往返延迟时间。
举个例子,Facebook使用它来为同一数据中心内的连接设置短恢复时间目标(RTO)。RTO是一种时间,它指的是网络在出现故障后的恢复时间,这个指标也表示网络在受到不可接受到情况下的,不能被使用的时间。Facebook认为,在同一数据中心中,应该有一个很短的RTO,Facebook修改了这个时间,使用bpf程序。
BPF_PROG_TYPE_SK_SKB,这类程序可以让你获取socket maps和socket redirects。socket maps可以让你获得一些socket的引用。当你有了这些引用,你可以使用相关的helpers,去重定向一个incoming 的packet ,从一个socket去另外一个scoket.这在使用BPF来做负载均衡时是非常有用的。你可以在socket之间转发网络数据包,而不需要离开内核空间。Cillium和facebook的Katran 广泛的使用这种类型的程序去做流量控制。
BPF_PROG_TYPE_SK_MSG, These types of programs let you control whether a message sent to a socket should be delivered.当内核创建了一个socket,它会被存储在前面提到的map中。当你attach一个程序到这个socket map的时候,所有的被发送到那些socket的message都会被filter.在filter message之前,内核拷贝了这些data,因此你可以读取这些message,而且可以给出你的决定:例如,SK_PASS和SK_DROP。
BPF_PROG_TYPE_CGROUP_SOCK_ADDR,这种类型的程序使您可以在由特定cgroup控制的用户空间程序中操纵IP地址和端口号。 在某些情况下,当您要确保一组特定的用户空间程序使用相同的IP地址和端口时,系统将使用多个IP地址.当您将这些用户空间程序放在同一cgroup中时,这些BPF程序使您可以灵活地操作这些绑定。 这样可以确保这些应用程序的所有传入和传出连接均使用BPF程序提供的IP和端口.
SO_REUSEPORT是内核中的一个选项,它允许将同一主机中的多个进程绑定到同一端口.当您要在多个线程之间分配负载时,此选项可以在accepted网络连接时提供更高的性能。BPF_PROG_TYPE_SK_REUSEPORT程序类型使您可以编写BPF程序,这些程序嵌入到reuse的逻辑中, 如果您的BPF程序返回SK_DROP,则可以防止程序重用同一端口;当您从这些BPF程序返回SK_PASS时,也可以通知内核遵循其自己的重用例。
flow dissector 是内核的一个组件,可跟踪网络数据包从到达系统到交付给用户空间程序的时间,需要经历的不同层。 它允许您使用不同的分类方法来控制数据包的流向。 内核中的内置解剖器称为Flower分类器,防火墙和其他过滤设备使用它来决定如何处理特定的数据包。
BPF_PROG_TYPE_FLOW_DISSECTOR 类型的程序被设计来Hook到dissector的逻辑中。它们提供了内置解剖器无法提供的安全保证,例如确保程序始终终止,而内置解剖器可能无法保证。 这些BPF程序可以修改网络数据包在内核中遵循的流。
我们已经讨论过在不同环境中使用过的程序类型,但是值得注意的是,我们还没有涉及其他一些BPF程序类型.
Traffic classifier programs
BPF_PROG_TYPE_SCHED_CLS 和BPF_PROG_TYPE_SCHED_ACT,这两种程序类型允许你分类网络traffic和修改一些sk buffer中的属性。
BPF_PROG_TYPE_LIRC_MODE2程序使您可以通过将BPF程序连接到红外设备(例如遥控器)来获得乐趣
BPF_PROG_TYPE_LWT_IN,BPF_PROG_TYPE_LWT_OUT,BPF_PROG_TYPE_LWT_XMIT和BPF_PROG_TYPE_LWT_SEG6LOCAL是BPF程序的类型,可让您将代码附加到内核的轻量级隧道基础结构
这些程序是专门的,其用法尚未为社区广泛采用。 接下来,我们讨论BPF如何确保您的程序在内核加载之后不会在您的系统中引起灾难性的故障。