BPF程序类型及其原理

bpf都可以干啥

为了回到这个问题,我们在bpf.h中看到了一些bpf类型的枚举定义:

enum bpf_prog_type {
    BPF_PROG_TYPE_UNSPEC,
    BPF_PROG_TYPE_SOCKET_FILTER,
    BPF_PROG_TYPE_KPROBE,
    BPF_PROG_TYPE_SCHED_CLS,
    BPF_PROG_TYPE_SCHED_ACT,
    BPF_PROG_TYPE_TRACEPOINT,
    BPF_PROG_TYPE_XDP,
    BPF_PROG_TYPE_PERF_EVENT,
    BPF_PROG_TYPE_CGROUP_SKB,
    BPF_PROG_TYPE_CGROUP_SOCK,
    BPF_PROG_TYPE_LWT_IN,
    BPF_PROG_TYPE_LWT_OUT,
    BPF_PROG_TYPE_LWT_XMIT,
    BPF_PROG_TYPE_SOCK_OPS,
    BPF_PROG_TYPE_SK_SKB,
};

为了解释这些问题,我们不妨问自己几个问题:

  • 1.我用这些程序类型可以干啥?
  • 2.我如何把我的bpf程序,attach到这些类型上。
  • 3.当我attach上之后,在我的bpf程序中,我可以获取到什么上下文,以及什么参数。
  • 4.我的bpf代码在什么时候才会被执行。这一点非常重要。

1. socket相关的bpf类型–SOCKET_FILTER, SK_SKB, SOCK_OPS

首先,我们使用socket相关的程序类型,可以对socket的data进行过滤,转发和监控。
当观察网络时,我们知希望看到一部分的网络流量,比如,来自故障系统的所有流量。Filters用于描述我们希望看到的流量,理想情况下,我们希望它是快速的,并且我们希望为用户提供一组开放式的过滤选项。但是有一个问题,我们希望尽早丢弃不需要的数据,并且需要在内核上下文中进行过滤。考虑在内核实现,因为在考虑将数据包复制到用户态的成本。发明了一种安全的迷你语言,可以将高级过滤器转换为内核可以使用的字节码程序(bpf),该语言的目的是在快速且安全的同时支持一组灵活的过滤选项。可以使用诸如tcpdump之类的用户空间程序推送用这种类似于汇编语言编写的过滤器,以完成内核中的过滤。
对于套接字过滤,通常的情况是将其附加到原始套接字(SOCK_RAW),实际上,您会注意到大多数执行套接字过滤的程序都具有以下行:

s = socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL));

创建这样的套接字,我们指定域(AF_PACKET),套接字类型(SOCK_RAW)和协议(所有数据包类型)。在Linux内核中,原始数据包的接收由raw_local_deliver()函数实现。 在调用相关IP协议的处理程序之前,ip_local_deliver_finish()调用了该方法,该处理程序是将数据包传递到TCP,UDP,ICMP等的地方。因此在这里,流量还没有和具体的socket关联起来。在IP层,当计算出来packet和4层(传输层)的map时,然后再和具体的socket关联起来。你可以看到cBPF的字节码通过tcpdump的-d选项。这里,我想在wlp4s0的interface上运行tcpdump。

# tcpdump -i wlp4s0 -d 'tcp'
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 7
(002) ldb      [20]
(003) jeq      #0x6             jt 10    jf 4
(004) jeq      #0x2c            jt 5    jf 11
(005) ldb      [54]
(006) jeq      #0x6             jt 10    jf 11
(007) jeq      #0x800           jt 8    jf 11
(008) ldb      [23]
(009) jeq      #0x6             jt 10    jf 11
(010) ret      #65535
(011) ret      #0

你可能会考虑套接字与netfilter的不同之处。Netfilter用NF_HOOK()定义定义了自己的一组钩子,基于netfilter的技术(例如ipfilter)也可以用来过滤流量。你可能会想,我们可以在对应的地方用bpf,是的。在一些kernle中已经用bpfilter代替ipfilter。

接下来,我们再来看看与socket相关的filter

1.1. BPF_PROG_TYPE_SOCKET_FILTER

我可以用来干啥?

filter操作包括丢弃数据包(如果程序返回0)或修改数据包(如果程序返回的长度小于原始长度)。请参见sk_filter_trim_cap()及其对bpf_prog_run_save_cb()的调用。请注意,我们不是修改或者丢弃原始的数据包,这些数据包都会到达原始的套接字。我们处理的是原始数据包元数据的copy,这些copy的数据包可以被raw socket访问到。除此之外,我们还可以做一些其他的事情。例如进行流量统计在BPF 的Maps中。

如何attach我们的program?

它可以通过SO_ATTACH_BPF setsockopt()进行attach,

我可以获取哪些context?

struct __sk_buff 包含这个packet的metadata/data。他的结构在include/linux/ bpf.h中定义,并包含来自实际sk_buff的关键字段。 bpf验证程序将对有效的__sk_buff字段的访问转换为“真实”sk_buff的偏移量,有关更多详细信息,请参见https://lwn.net/Articles/636647/。

它什么时候被执行?

socket filter过滤器在sock_queue_rcv_skb()中运行以接收消息,该协议被各种协议(TCP,UDP,ICMP,原始套接字等)调用,可用于过滤inbound的流量。

在这里我们将创建一个过滤器,该过滤器会修改基于协议类型过滤的数据包数据。 对于IPv4 TCP,我们仅获取IPv4 + TCP标头,而对于UDP,仅获取IPv4和UDP标头。我们将不处理IPv4选项,因为这是一个简单的示例,因此在所有其他情况下,我们将返回0(丢弃数据包)。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "bpf_helpers.h"

#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
/*
 * We are only interested in TCP/UDP headers, so drop every other protocol
 * and trim packets after the TCP/UDP header by returning length of
 * ether header + IPv4 header + TCP/UDP header.
 */

SEC("socket")
int bpf_prog1(struct __sk_buff *skb)
{
        int proto = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
        int size = ETH_HLEN + sizeof(struct iphdr);

        switch (proto) {
        case IPPROTO_TCP:
             size += sizeof(struct tcphdr);
             break;
        case IPPROTO_UDP:
             size += sizeof(struct udphdr);
             break;
        default:
             size = 0;
             break;
        }
        return size;
}
char _license[] SEC("license") = "GPL";

这个代码可以被LLVM/clang编译成bpf的bytecodes。它将包含一个带有“socket”的ELF section。下一步是使用BPF系统调用为程序分配文件描述符,然后将其附加到socket.在samples / bpf中,您可以看到bpf_load.c扫描了ELF节,并且名称以“ socket”为前缀的节被识别为BPF_PROG_TYPE_SOCKET_FILTER程序。 如果要添加示例,我建议包括bpf_load.h,这样您就可以在BPF程序上调用load_bpf_file()了。例如,在samples / bpf / sockex1_user.c中,我们获取程序的文件名(sockex1)并加载sockex1_kern.o; 相关的BPF程序。 然后,我们打开一个原始套接字进行环回(lo)并将程序附加到该位置:

snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
if (load_bpf_file(filename)) {
        printf("%s", bpf_log_buf);
        return 1;
}
sock = open_raw_sock("lo");
assert(setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, prog_fd,
                  sizeof(prog_fd[0])) == 0);

1.2 BPF_PROG_TYPE_SOCK_OPS

能用它来干什么?

attach a BPF程序去获取socket的一些操作,比如连接的建立,重传超时等。一旦捕捉到相关选项,也可以通过bpf setsockopt()设置,因此,例如,在来自不同子网的系统的被动连接建立时,我们可以降低MTU,这样我们就不必担心中间路由器会分割数据包。程序可以返回成功(0)或失败(负值),并且可以设置reply value以指示套接字选项的所需值(例如TCP rwnd)。有关完整的详细信息,请参见https://lwn.net/Articles/727189/,并在include/net/ tcp.h中查找tcp_call_bpf()的内联定义,以了解TCP如何处理此类程序。另一个用例是结合BPF_PROG_TYPE_SK_SKB程序进行sockmap更新。 传递到BPF_PROG_TYPE_SOCK_OPS程序中的bpf_sock_ops结构指针用于更新sockmap,为该套接字关联一个值。 以后的sk_skb程序可以引用这些值,以指定通过bpf_sk_redirect_map()调用重定向到哪个套接字。 如果这听起来令人困惑,我建议您看一下sample / sockmap中的代码。

我如何attach我的程序?

使用BPF_CGROUP_SOCK_OPS附加类型将其附加到cgroup文件描述符。

获取到怎么样的上下文?

提供的参数是上下文struct bpf_sock_ops * … Op字段指定操作,BPF_SOCK_OPS_RWND_INIT,BPF_SOCK_OPS_TCP_CONNECT_CB等。The reply field can be used to indicate to the caller a new value for a parameter set.

/* User bpf_sock_ops struct to access socket values and specify request ops
 * and their replies.
 * Some of this fields are in network (bigendian) byte order and may need
 * to be converted before use (bpf_ntohl() defined in samples/bpf/bpf_endian.h).
 * New fields can only be added at the end of this structure
 */
struct bpf_sock_ops {
    __u32 op;
    union {
        __u32 reply;
        __u32 replylong[4];
    };
    __u32 family;
    __u32 remote_ip4;    /* Stored in network byte order */
    __u32 local_ip4;    /* Stored in network byte order */
    __u32 remote_ip6[4];    /* Stored in network byte order */
    __u32 local_ip6[4];    /* Stored in network byte order */
    __u32 remote_port;    /* Stored in network byte order */
    __u32 local_port;    /* stored in host byte order */
};

什么时候运行?

与期望在代码库中的特定位置调用其他BPF程序类型不同.SOCK_OPS程序可以在不同的地方被调用,然后使用op字段去表明context.请参见include / uapi / linux / bpf.h,但其中包括重传超时,连接建立等事件。

1.3 BPF_PROG_TYPE_SK_SKB

可以用来干啥?

允许用户访问skb和套接字详细信息,例如端口和IP地址,以支持套接字之间skb的重定向。 参见https://lwn.net/Articles/731133/,此功能与sockmap结合使用(sockmap是一种特殊的BPF映射,其中包含对套接字结构和相关值的引用)。sockmaps被用来支持转发,可以使用bpf_sk_redirect_map()helper 器执行重定向。通用的方法是我们使用sock_ops BPF程序来获取socket的创建事件,将这些值与sockmap关联起来,然后使用sk_skb检测点的数据来通知套接字重定向。这种程序类型的另一个用例是在strparser框架中–https://www.kernel.org/doc/Documentation/networking/strparser.txt

如何加载我的程序?

重定向程序作为BPF_SK_SKB_STREAM_VERDICT附加到sockmap; 它应返回bpf_sk_redirect_map()的结果。 一个strparser程序通过BPF_SK_SKB_STREAM_PARSER附加,并且应返回已解析数据的长度。

能够获取什么样的context?

指向包含包元数据/数据的结构__sk_buff的指针。 但是,sk_skb程序类型可以访问更多字段。 可用的额外字段集记录在include / linux / bpf.h中,如下所示:

/* Accessed by BPF_PROG_TYPE_sk_skb types from here to ... */
__u32 family;
__u32 remote_ip4;   /* Stored in network byte order */
__u32 local_ip4;    /* Stored in network byte order */
__u32 remote_ip6[4];    /* Stored in network byte order */
__u32 local_ip6[4]; /* Stored in network byte order */
__u32 remote_port;  /* Stored in network byte order */
__u32 local_port;   /* stored in host byte order */
/* ... here. */

因此,仅从上面我们就可以看到我们可以收集有关套接字的信息,因为上面的内容代表了唯一标识套接字的关键信息(协议在__sk_buff结构的全局可访问部分中已经可用)。

什么时候会运行?
可以通过把BPF_SK_SKB_STREAM_PARSER 附加到sockmap上来把一个stream parser附加到一个socket上,然后,当socket通过、bpf/sockmap.c中的smap_parse_func_strparser() 接受的时候,就会执行。BPF_SK_SKB_STREAM_VERDICT也会附加到sockmap上,它通过smap_verdict_func()来执行。

2.tc (traffic control) subsystem programs

接下来,让我们检查与TC内核数据包调度子系统有关的程序类型。 可以通过tc(8)的manpage查看相关的具体信息。

2.1. tc_cls_act : qdisc classifier

有何用处?

tc_cls_act允许我们在Linux QoS子系统–tc中,去使用BPF程序进行相关的动作。tc(8)还支持eBpf,因此我们可以将BPF程序直接作为入站(入站)和出站(出站)流量的分类器和操作加载。有关如何使用tc的BPF功能的说明,请参见http://man7.org/linux/man-pages/man8/tc-bpf.8.html。 tc程序可以分类,修改,重定向或丢弃数据包。

如何attach 程序?

可以使用tc(8); 有关详细信息,请参见tc-bpf(8)。 基础是我们为网络设备创建一个“ clsact” qdisc,并通过指定BPF对象和相关的ELF部分来添加入口分类器/出口分类器/ actoins。 例如,要从myprog_kernel.o(bpf-bytecode编译的目标文件)的ELF节my_elf_sec中的eth0添加入口分类器:

tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj myprog_kernel.o sec my_elf_sec

提供的上下文?

指向结构__sk_buff数据包元数据/数据的指针。

什么时候运行?

如上所述,必须添加分类器qdisc,一旦添加分类器qdisc,我们就可以附加BPF程序对入站和出站流量进行分类。 在实现方面,act_bpf.c和cls_bpf.c实现操作/分类器模块。 在入口/入口sch_handle_ingress()/ egress()上,调用tcf_classify()。 在进入的情况下,我们通过核心网络接口的接收功能进行分类,因此我们在驱动程序处理完数据包之后但在IP等看到数据包之前就得到了数据包。 在出口上,过滤是在提交给设备队列进行传输之前完成的。

3.xdp : the Xpress Data Path.

XDP的主要设计目标是在网络数据路径中引入可编程性。 目的是提供尽可能靠近设备的XDP挂钩(在OS创建sk_buff元数据之前),以便在支持跨设备的通用基础结构的同时最大化性能。 要支持这种XDP,需要更改驱动程序。 有关示例,请参阅drivers / net / ethernet / broadcom / bnxt / bnxt_xdp.c。 添加了一个bpf网络设备操作(ndo_bpf)。 对于bnxt,它支持XDP_SETUP_PROG和XDP_QUERY_PROG操作; 前者将设备配置为XDP,保留rings并将程序设置为活动状态。 后者返回BPF程序ID。 BPF特定的发送和接收功能由实际的发送/接收功能提供并在需要时调用。

3.1 BPF_PROG_TYPE_XDP

可以用来干啥?

XDP允许在分配了分组元数据(结构sk_buff)之前尽早访问分组数据。 因此,这是进行DDoS缓解或负载平衡的有用位置,因为此类活动通常可以避免sk_buff分配的昂贵开销。 XDP旨在通过BPF挂钩来支持内核的运行时编程,但是要与内核本身协同工作。 即不是内核旁路机制。 支持的动作包括XDP_PASS(照常进入网络处理),XDP_DROP(丢弃),XDP_TX(发送)和XDP_REDIRECT。 请参阅include / uapi / linux / bpf.h以获取“枚举xdp_action”。

如何attach 程序?

通过netlink套接字消息。 创建并绑定了一个netlink套接字-socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE),然后我们发送NLA_F_NESTED类型的netlink消息| 43; 这指定了XDP消息。 该消息包含BPF fd,接口索引(ifindex)。 有关示例,请参见samples / bpf / bpf_load.c。

context?

xdp元数据指针; struct xdp_md *。 XDP元数据是轻量级的; 来自include / uapi / linux / bpf.h:

/* user accessible metadata for XDP packet hook
 * new fields must be added to the end of this structure
 */
struct xdp_md {
        __u32 data;
        __u32 data_end;
};

什么时候会执行?

真实” XDP在驱动程序级别实现,并且为XDP使用预留了发送/接收ring resources。 对于驱动程序不支持XDP的情况,可以选择使用“通用” XDP,它在net / core / dev.c中实现。 缺点是我们不绕过skb分配,它只允许我们将XDP用于此类设备。

4.kprobes, tracepoints and perf events

kprobes,跟踪点和perf事件均提供内核检测。 kprobes-https://www.kernel.org/doc/Documentation/kprobes.txt-允许检测特定功能-可以通过kprobe监视功能的进入,以及功能中的大多数指令,或者可以进入/返回 通过kretprobe进行检测。 启用这些探针之一后,将保存启用点处的代码,并用断点指令替换。 当遇到此断点时,将生成陷阱指令,保存寄存器,然后跳转到相关的检测处理程序。 例如,kprobes由kprobe_dispatcher()处理,该函数获取kprobe的地址并将上下文注册为参数。 kretprobes是通过kprobes实现的; kprobe在进入时触发并修改返回地址,保存原始地址并将其替换为检测处理程序的位置。 跟踪点-https://www.kernel.org/doc/Documentation/trace/tracepoints.rst-类似,但是除了在特定说明中启用外,还可以在代码中的站点上进行显式标记,如果启用,则可以 在感兴趣的站点上收集调试信息。 可以在多个地方声明相同的跟踪点; 例如,在net / mac80211 / driver-ops.c中的多个位置调用trace_drv_return_int()。
Perf事件-https://perf.wiki.kernel.org/index.php/Main_Page-是ebpf支持这些程序类型的基础。ebpf允许我们可以将程序附加到感兴趣的perf事件上。这些事件包括kprobes,uprobes,tracepoint等以及其他软件事件,但是其实也可以监听一些硬件的事件。
这些检测点使BPF能够成为通用的跟踪工具,并成为支持原始的以网络为中心的用例(如套接字过滤)的手段。

4.1 BPF_PROG_TYPE_KPROBE

可以干什么?

通过kprobe/kerprobe,注入到一些内核函数上。 k [ret] probe_perf_func()执行附加到探测点的BPF程序。请参见https://www.kernel.org/doc/Documentation/trace/uprobetracer.txt

如何attach?

当通过sysfs创建kprobe时,它具有与之关联的ID,存储在/ sys / kernel / debug / tracing / events / [uk] probe // id中,/ sys / kernel / debug / tracing / events / [uk ] retprobe / probename / id。 https://www.kernel.org/doc/Documentation/trace/kprobetrace.txt包含有关如何使用sysfs创建kprobe的详细信息。例如,在tcp_retransmit_skb()的条目上创建一个名为“ myprobe”的探针并检索其ID:

# echo 'p:myprobe tcp_retransmit_skb' > /sys/kernel/debug/tracing/kprobe_events
# cat /sys/kernel/debug/tracing/events/kprobes/myprobe/id
2266

我们可以使用该probe id打开一个perf事件,将其启用,并将该perf事件的BPF程序设置为我们的程序。 请参阅load_and_attach()函数中的samples / bpf / bpf_load.c,了解如何针对k [ret]探针执行此操作。 该代码可能看起来像这样:

struct perf_event_attr attr;
        int eventfd, programfd;
        int probeid;

        /* Load BPF program and assign programfd to it; and get probeid of probe from sysfs */
        attr.type = PERF_TYPE_TRACEPOINT;
        attr.sample_type = PERF_SAMPLE_RAW;
        attr.sample_period = 1;
        attr.wakeup_events = 1;
        attr.config = probeid;
        eventfd = sys_perf_event_open(&attr, -1, 0, programfd, 0);
        if (eventfd < 0)
                return -errno;
        if (ioctl(eventfd, PERF_EVENT_IOC_ENABLE, 0)) {
                close(eventfd);
                return -errno;
        }
        if (ioctl(eventfd, PERF_EVENT_IOC_SET_BPF, programfd)) {
                close(eventfd);
                return -errno;
        }

context provided?

struct pt_regs * ctx,可以从中访问寄存器。 其中大部分是特定于平台的,但是存在一些通用函数,例如regs_return_value(regs),该函数返回寄存器的值,而不是保存函数返回值(x86上的regs→ax)。

什么时候执行?

当启用探针并命中断点时,k [ret] probe_perf_func()执行通过trace_call_bpf()附加到探针点的BPF程序。 u [ret] probe_perf_func()与此类似。

4.2 BPF_PROG_TYPE_TRACEPOINT

可以干啥?

内核代码中的仪器跟踪点。 可以通过sysfs启用跟踪点,这与kprobes一样,并且以类似的方式。 跟踪事件的列表可以在/ sys / kernel / debug / tracing / events下看到。

如何附加?

正如我们在上面看到的,当通过sysfs创建跟踪点时,它具有一个与之关联的ID。 我们可以使用该探针ID打开一个perf事件,将其启用,并将该perf事件的BPF程序设置为我们的程序。 有关如何对跟踪点执行此操作,请参见load_and_attach()函数中的samples / bpf / bpf_load.c。 上面针对kprobes的代码片段也适用于跟踪点。 作为显示如何启用跟踪点的示例,在这里,我们将net / net_dev_xmit跟踪点启用为“ myprobe2”并获取其ID:

# echo 'p:myprobe2 trace:net/net_dev_xmit' > /sys/kernel/debug/tracing/kprobe_events
# cat /sys/kernel/debug/tracing/events/kprobes/myprobe2/id
2270

context provid?

特定跟踪点提供的上下文; 参数和数据类型与跟踪点定义相关联。

什么时候运行?

启用并命中跟踪点后,perf_trace _()(请参见include / trace / perf.h中的定义)将调用perf_trace_run_bpf_submit(),后者将通过trace_call_bpf()调用bpf程序。

4.3.BPF_PROG_TYPE_PERF_EVENT

可以干啥?

得到软件和硬件性能事件。 这些事件包括系统调用,计时器到期,硬件事件采样等。硬件事件包括PMU事件(处理器监视单元),它告诉我们诸如已完成多少指令等信息。Perf事件监视可以针对特定的进程或组, 处理器,并且可以指定采样周期进行分析。

如何attach?

与上面的类似,我们使用perf_event_open()的一个属性类设置。通过PERF_EVENT_IOC_ENABLE ioctl()启用perf事件,并通过PERF_EVENT_IOC_SET_BPF ioctl()设置bpf程序。 对于PMU(处理器监视单元)perf事件示例,请参阅samples / bpf / sampleip_user.c中的以下片段:

struct perf_event_attr pe_sample_attr = {
                .type = PERF_TYPE_SOFTWARE,
                .freq = 1,
                .sample_period = freq,
                .config = PERF_COUNT_SW_CPU_CLOCK,
                .inherit = 1,
        };
                ...
        pmu_fd[i] = sys_perf_event_open(&pe_sample_attr, -1 /* pid */, i,
                                        -1 /* group_fd */, 0 /* flags */);
        if (pmu_fd[i] < 0) {
                fprintf(stderr, "ERROR: Initializing perf sampling\n");
                return 1;
        }

        assert(ioctl(pmu_fd[i], PERF_EVENT_IOC_SET_BPF,
                     prog_fd[0]) == 0);
        assert(ioctl(pmu_fd[i], PERF_EVENT_IOC_ENABLE, 0) == 0);
        ...

提供什么样的context?

struct bpf_perf_event_data {
    struct pt_regs regs;
     __u64 sample_period;
};

什么时候运行?

取决于perf事件触发和所选择的采样率,这些采样率由perf事件属性结构中的freq和sample_period字段指定。

5. cgroups-related program types

CGroup用于处理资源分配,允许或拒绝进程组对系统资源(如CPU,网络带宽等)的访问而其活动却被各种名称空间(网络名称空间,进程ID名称空间等)隔离。 在BPF上下文中,我们可以创建允许或拒绝访问的eBPF程序。 在include / linux / bpf-cgroup.h中,我们可以看到用于执行socket / skb程序的定义,其中__cgroup_bpf_run_filter_skb被包装,以检查是否启用了cgroup BPF:

#define BPF_CGROUP_RUN_PROG_INET_INGRESS(sk, skb)                  \
({                                                                 \
    int __ret = 0;                                                 \
    if (cgroup_bpf_enabled)                                        \
        __ret = __cgroup_bpf_run_filter_skb(sk, skb,               \
                            BPF_CGROUP_INET_INGRESS);              \
                                                                   \
    __ret;                                                         \
})

#define BPF_CGROUP_RUN_SK_PROG(sk, type)                       \
({                                                                 \
    int __ret = 0;                                                 \
    if (cgroup_bpf_enabled) {                                      \
        __ret = __cgroup_bpf_run_filter_sk(sk, type);              \
    }                                                              \
    __ret;                                                         \
})

如果启用了cgroup,我们会将程序附加到cgroup,它将在相关的挂钩点执行。 要了解钩子的完整列表,请查阅include / uapi / linux / bpf.h并检查BPF_CGROUP_ *定义的枚举类型“ bpf_attach_type”。

5.1 BPF_PROG_TYPE_CGROUP_SKB

  • 1.可以做什么?允许或拒绝IP出口/入口(BPF_CGROUP_INET_INGRESS / BPF_CGROUP_INET_EGRESS)上的网络访问。 BPF程序应返回1以允许访问。 任何其他值都会导致函数__cgroup_bpf_run_filter_skb()返回-EPERM,该值将传播到调用方,从而丢弃该数据包。

  • 2.attach 程序?该程序将附加到特定cgroup的文件描述符。

  • 3.什么时候执行?对于inet入口,sk_filter_trim_cap()(请参见上文)包含对BPF_CGROUP_RUN_PROG_INET_INGRESS(sk,skb)的调用; 如果返回非零值,则错误会传播给调用方(例如__sk_receive_skb()),并且数据包将被丢弃并释放。 在出口上也采用类似的方法,但是在ip[6]_finish_output()中。

5.2 BPF_PROG_TYPE_CGROUP_SOCK

  • 1.可以干啥?在各种与套接字相关的事件(BPF_CGROUP_INET_SOCK_CREATE,BPF_CGROUP_SOCK_OPS)上允许或拒绝网络访问。 如上所述,BPF程序应返回1以允许访问。 任何其他值都会导致函数__cgroup_bpf_run_filter_sk()返回-EPERM,该值将传播到调用方,从而丢弃该数据包。

  • 2.如何附加程序?
    该程序将附加到特定cgroup的文件描述符。提供了什么上下文? 相关的socket(sk)。

  • 3.什么时候运行?
    在套接字创建时,在inet_create()中,我们以套接字为参数调用BPF_CGROUP_RUN_PROG_INET_SOCK(),如果该函数失败,则释放套接字。

你可能感兴趣的:(linux,内核,linux)